diff --git a/lib/Logger.js b/lib/Logger.js new file mode 100644 index 0000000..1833a58 --- /dev/null +++ b/lib/Logger.js @@ -0,0 +1,67 @@ +const fs = require('fs'); +const path = require('path'); + +class Logger { + constructor(options = {}) { + this.logLevel = options.logLevel || 'info'; + this.logFile = options.logFile || path.join(process.cwd(), 'osmosis.log'); + this.logLevels = ['error', 'warn', 'info', 'debug']; + } + + _shouldLog(level) { + return this.logLevels.indexOf(level) <= this.logLevels.indexOf(this.logLevel); + } + + _log(level, message, metadata = {}) { + if (!this._shouldLog(level)) return; + + const timestamp = new Date().toISOString(); + const logEntry = JSON.stringify({ + timestamp, + level, + message, + ...metadata + }) + '\n'; + + // Console output + switch(level) { + case 'error': + console.error(logEntry); + break; + case 'warn': + console.warn(logEntry); + break; + case 'info': + console.info(logEntry); + break; + case 'debug': + console.debug(logEntry); + break; + } + + // File logging + try { + fs.appendFileSync(this.logFile, logEntry); + } catch (err) { + console.error('Failed to write to log file:', err); + } + } + + error(message, metadata = {}) { + this._log('error', message, metadata); + } + + warn(message, metadata = {}) { + this._log('warn', message, metadata); + } + + info(message, metadata = {}) { + this._log('info', message, metadata); + } + + debug(message, metadata = {}) { + this._log('debug', message, metadata); + } +} + +module.exports = Logger; \ No newline at end of file diff --git a/test/logger.js b/test/logger.js new file mode 100644 index 0000000..724719b --- /dev/null +++ b/test/logger.js @@ -0,0 +1,67 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const Logger = require('../lib/Logger'); + +describe('Logger', () => { + const logFile = path.join(process.cwd(), 'test-osmosis.log'); + + afterEach(() => { + // Clean up log file after each test + try { + fs.unlinkSync(logFile); + } catch (err) { + // Ignore if file doesn't exist + } + }); + + it('should create a log file when logging', () => { + const logger = new Logger({ logFile }); + logger.info('Test log message'); + + assert(fs.existsSync(logFile), 'Log file was not created'); + }); + + it('should log messages at the correct log level', () => { + const consoleLog = console.log; + const consoleError = console.error; + let loggedMessages = []; + + // Mock console methods + console.log = console.error = (msg) => { + loggedMessages.push(msg); + }; + + const logger = new Logger({ + logFile, + logLevel: 'warn' + }); + + logger.debug('Debug message'); + logger.info('Info message'); + logger.warn('Warning message'); + logger.error('Error message'); + + console.log = consoleLog; + console.error = consoleError; + + const logContent = fs.readFileSync(logFile, 'utf-8'); + + assert(logContent.includes('Warning message'), 'Warning message should be logged'); + assert(logContent.includes('Error message'), 'Error message should be logged'); + assert(!logContent.includes('Debug message'), 'Debug message should not be logged'); + assert(!logContent.includes('Info message'), 'Info message should not be logged'); + }); + + it('should include metadata in log entries', () => { + const logger = new Logger({ logFile }); + logger.info('Test with metadata', { url: 'https://example.com', status: 200 }); + + const logContent = fs.readFileSync(logFile, 'utf-8'); + const logEntry = JSON.parse(logContent.trim()); + + assert.strictEqual(logEntry.message, 'Test with metadata'); + assert.strictEqual(logEntry.url, 'https://example.com'); + assert.strictEqual(logEntry.status, 200); + }); +}); \ No newline at end of file diff --git a/test/logger.test.js b/test/logger.test.js new file mode 100644 index 0000000..94f7c32 --- /dev/null +++ b/test/logger.test.js @@ -0,0 +1,67 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import fs from 'fs'; +import path from 'path'; +import Logger from '../lib/Logger.js'; + +describe('Logger', () => { + const logFile = path.join(process.cwd(), 'test-osmosis.log'); + + afterEach(() => { + // Clean up log file after each test + try { + fs.unlinkSync(logFile); + } catch (err) { + // Ignore if file doesn't exist + } + }); + + it('should create a log file when logging', () => { + const logger = new Logger({ logFile }); + logger.info('Test log message'); + + expect(fs.existsSync(logFile)).toBe(true); + }); + + it('should log messages at the correct log level', () => { + const consoleLog = console.log; + const consoleError = console.error; + let loggedMessages = []; + + // Mock console methods + console.log = console.error = (msg) => { + loggedMessages.push(msg); + }; + + const logger = new Logger({ + logFile, + logLevel: 'warn' + }); + + logger.debug('Debug message'); + logger.info('Info message'); + logger.warn('Warning message'); + logger.error('Error message'); + + console.log = consoleLog; + console.error = consoleError; + + const logContent = fs.readFileSync(logFile, 'utf-8'); + + expect(logContent).toContain('Warning message'); + expect(logContent).toContain('Error message'); + expect(logContent).not.toContain('Debug message'); + expect(logContent).not.toContain('Info message'); + }); + + it('should include metadata in log entries', () => { + const logger = new Logger({ logFile }); + logger.info('Test with metadata', { url: 'https://example.com', status: 200 }); + + const logContent = fs.readFileSync(logFile, 'utf-8'); + const logEntry = JSON.parse(logContent.trim()); + + expect(logEntry.message).toBe('Test with metadata'); + expect(logEntry.url).toBe('https://example.com'); + expect(logEntry.status).toBe(200); + }); +}); \ No newline at end of file