services_logger.js

/* global Bun */  // Declare Bun as a global if available.

let Database = null;
// Conditionally load bun:sqlite only if not in a browser environment.
if (typeof window === "undefined") {
    try {
        // Dynamically import bun:sqlite. This code only runs in a server context.
        Database = (await import("bun:sqlite")).Database;
    } catch (err) {
        console.error("Failed to load bun:sqlite:", err);
    }
}

/**
 * @typedef {Object} LoggerOptions
 * @property {string} [type='console'] - The logging type to use. Options: 'console', 'csv', or 'sqlite'.
 * @property {string} [filePath] - For CSV logging, the file path to store log entries.
 * @property {string} [dbPath] - For SQLite logging, the database file path.
 */

/**
 * Logging Service Class.
 *
 * This class provides a unified interface for logging messages to different targets:
 * - Console (default): Logs messages directly to the terminal.
 * - CSV: Appends log messages in CSV format to a text file.
 * - SQLite: Inserts log messages into a SQLite database for advanced querying using Bun's built-in API.
 */
class Logger {
    /**
     * Creates an instance of the Logger.
     * @param {LoggerOptions} options - Configuration options for the logger.
     */
    constructor(options = {}) {
        // Determine the logging type, defaulting to 'console' if not specified.
        this.type = options.type || "console";

        // For CSV logging, use Bun's file I/O functions.
        if (this.type === "csv") {
            if (!options.filePath) {
                throw new Error("CSV logging requires a 'filePath' option.");
            }
            this.filePath = options.filePath;
            try {
                // Check if file exists using Bun's file I/O.
                Bun.statSync(this.filePath);
            } catch {
                // File doesn't exist; create it with a header.
                Bun.writeFileSync(this.filePath, "timestamp,level,message\n");
            }
        } else if (this.type === "sqlite") {
            // For SQLite logging, ensure we're not in the browser.
            if (typeof window !== "undefined") {
                console.warn(
                    "SQLite logging is not supported in the browser. Falling back to console logging."
                );
                this.type = "console";
            } else {
                if (!options.dbPath) {
                    throw new Error("SQLite logging requires a 'dbPath' option.");
                }
                this.dbPath = options.dbPath;
                if (!Database) {
                    console.error("Database module is not available.");
                    this.type = "console";
                } else {
                    try {
                        this.db = new Database(this.dbPath);
                        // Create the logs table if it doesn't exist.
                        this.db.run(
                            `CREATE TABLE IF NOT EXISTS logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT,
                level TEXT,
                message TEXT
              )`
                        );
                    } catch (err) {
                        console.error("Failed to initialize SQLite database:", err);
                        this.type = "console";
                    }
                }
            }
        }
        // For console logging, nothing extra is needed.
    }

    /**
     * Logs a message with a specified severity level.
     *
     * @param {string} level - The severity level (e.g., 'info', 'warn', 'error', 'debug').
     * @param {string} message - The log message to be recorded.
     */
    log(level, message) {
        const timestamp = new Date().toISOString();
        const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;

        switch (this.type) {
            case "console": {
                console.log(formattedMessage);
                break;
            }
            case "csv": {
                const safeMessage = message.replace(/"/g, '""');
                const csvLine = `"${timestamp}","${level}","${safeMessage}"\n`;
                Bun.write(this.filePath, csvLine, { append: true }).catch((err) => {
                    console.error("Failed to write to the CSV file:", err);
                });
                break;
            }
            case "sqlite": {
                try {
                    this.db.run(
                        `INSERT INTO logs (timestamp, level, message) VALUES (?, ?, ?)`,
                        [timestamp, level, message]
                    );
                } catch (err) {
                    console.error("Failed to insert log into SQLite database:", err);
                }
                break;
            }
            default: {
                console.log(formattedMessage);
                break;
            }
        }
    }

    /**
     * Closes any resources used by the logger.
     *
     * For SQLite logging, this method ensures the database connection is properly terminated.
     */
    close() {
        if (this.type === "sqlite" && this.db) {
            try {
                this.db.close();
            } catch (err) {
                console.error("Error closing the SQLite database:", err);
            }
        }
    }
}

/* ========================================================================
   Examples of Usage:
   Uncomment to test different logging types.
   ======================================================================== */

// const consoleLogger = new Logger({ type: "console" });
// consoleLogger.log("info", "Logging to console.");

// const csvLogger = new Logger({ type: "csv", filePath: "./logs.csv" });
// csvLogger.log("info", "Logging to CSV.");

// const sqliteLogger = new Logger({ type: "sqlite", dbPath: "./logs.db" });
// sqliteLogger.log("info", "Logging to SQLite using Bun.");
// sqliteLogger.close();

export default Logger;