Skip to content
Draft
101 changes: 101 additions & 0 deletions lib/Logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Custom Logger for Osmosis Web Scraping
* Provides flexible logging mechanisms with configurable levels
*/
class Logger {
/**
* Logging levels from most verbose to least
* @enum {number}
*/
static LEVELS = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
SILENT: 4
};

/**
* Create a new Logger instance
* @param {Object} options - Logger configuration
* @param {number} [options.level=0] - Logging level
* @param {boolean} [options.timestamp=true] - Include timestamp in logs
* @param {function} [options.writer=console.log] - Custom log writer
*/
constructor(options = {}) {
this.level = options.level !== undefined ? options.level : Logger.LEVELS.DEBUG;
this.timestamp = options.timestamp !== false;
this.writer = options.writer || console.log;
}

/**
* Generate log message with optional timestamp
* @param {string} level - Log level name
* @param {string} message - Log message
* @param {Object} [context] - Additional logging context
* @returns {string} Formatted log message
* @private
*/
_formatMessage(level, message, context = {}) {
const timestamp = this.timestamp ? `[${new Date().toISOString()}] ` : '';
const contextStr = Object.keys(context).length > 0
? ` ${JSON.stringify(context)}`
: '';
return `[${level}] ${message}${contextStr}`;
}

/**
* Shared logging method
* @param {number} logLevel - Logging level
* @param {string} levelName - Log level name
* @param {string} message - Log message
* @param {Object} [context] - Additional context
* @private
*/
_log(logLevel, levelName, message, context) {
if (this.level <= logLevel) {
this.writer(this._formatMessage(levelName, message, context));
}
}

/**
* Log a debug message
* @param {string} message - Debug message
* @param {Object} [context] - Additional context
*/
debug(message, context) {
this._log(Logger.LEVELS.DEBUG, 'DEBUG', message, context);
}

/**
* Log an informational message
* @param {string} message - Info message
* @param {Object} [context] - Additional context
*/
info(message, context) {
this._log(Logger.LEVELS.INFO, 'INFO', message, context);
}

/**
* Log a warning message
* @param {string} message - Warning message
* @param {Object} [context] - Additional context
*/
warn(message, context) {
this._log(Logger.LEVELS.WARN, 'WARN', message, context);
}

/**
* Log an error message
* @param {string} message - Error message
* @param {Object} [context] - Additional context
*/
error(message, context) {
this._log(Logger.LEVELS.ERROR, 'ERROR', message, context);
}
}

// Export as a singleton to ensure consistent logging across the library
const loggerInstance = new Logger();
module.exports = loggerInstance;
module.exports.Logger = Logger;
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
},
"devDependencies": {
"jscs": ">=3.0.2",
"nodeunit": "0.11.3"
"nodeunit": "0.11.3",
"jest": "^29.7.0"
},
"scripts": {
"test": "node ./node_modules/.bin/nodeunit test"
"test": "jest",
"test:logger": "jest test/logger.js"
},
"license": "MIT",
"main": "index",
Expand All @@ -37,6 +39,9 @@
},
"readmeFilename": "Readme.md",
"bugs": {
"url": "https://github.com/rchipka/node-osmosis/issues"
"url": "https://github.com/rchipka/node-osmosis/issues"},
"jest": {
"testEnvironment": "node",
"testMatch": ["**/test/**/*.js"]
}
}
}
58 changes: 58 additions & 0 deletions test/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { Logger } = require('../lib/Logger');

describe('Logger', () => {
let capturedLogs = [];
const mockWriter = (message) => capturedLogs.push(message);

beforeEach(() => {
capturedLogs = [];
});

test('log messages at different levels', () => {
const logger = new Logger({
level: Logger.LEVELS.DEBUG,
timestamp: false,
writer: mockWriter
});

logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');

expect(capturedLogs.length).toBe(3);
expect(capturedLogs[0]).toBe('[DEBUG] Debug message');
expect(capturedLogs[1]).toBe('[INFO] Info message');
expect(capturedLogs[2]).toBe('[WARN] Warning message');
});

test('respect logging levels', () => {
const logger = new Logger({
level: Logger.LEVELS.WARN,
timestamp: false,
writer: mockWriter
});

logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

expect(capturedLogs.length).toBe(2);
expect(capturedLogs[0]).toBe('[WARN] Warning message');
expect(capturedLogs[1]).toBe('[ERROR] Error message');
});

test('support logging context', () => {
const logger = new Logger({
level: Logger.LEVELS.DEBUG,
timestamp: false,
writer: mockWriter
});

logger.info('Scraping page', { url: 'https://example.com', pageNumber: 1 });

expect(capturedLogs[0]).toBe(
'[INFO] Scraping page {"url":"https://example.com","pageNumber":1}'
);
});
});
4 changes: 4 additions & 0 deletions test/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Enable using describe and it globally
global.describe = describe;
global.it = it;
global.beforeEach = beforeEach;