Update version to 0.0.16, add Appender and AppenderFactory classes, refactor LoggerFactory methods, and implement logging functionality with tests.

This commit is contained in:
2025-10-04 09:20:27 -04:00
parent ea8fd991a0
commit 07485bd0c3
14 changed files with 789 additions and 15 deletions

227
tests/Logger.test.ts Normal file
View File

@@ -0,0 +1,227 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'bun:test';
import Logger from '../src/Logger';
import { LoggingLevel } from '../src/level/LoggingLevel';
import Threshold from '../src/level/Threshold';
import type IAppender from '../src/appenders/IAppender';
describe('Logger', () => {
let mockAppender: IAppender;
let threshold: Threshold;
let logger: Logger;
beforeEach(() => {
mockAppender = {
log: vi.fn(),
};
threshold = new Threshold(LoggingLevel.Info);
logger = new Logger('test-category', threshold, new Set([mockAppender]));
});
describe('threshold filtering', () => {
it('should log info messages when threshold is Info', () => {
logger.info('test message');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String), // timestamp
'Info',
'test-category',
'test message'
);
});
it('should log warn messages when threshold is Info', () => {
logger.warn('test message');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Warn',
'test-category',
'test message'
);
});
it('should log error messages when threshold is Info', () => {
logger.error('test message');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Error',
'test-category',
'test message'
);
});
it('should not log debug messages when threshold is Info', () => {
logger.debug('test message');
expect(mockAppender.log).not.toHaveBeenCalled();
});
it('should not log trace messages when threshold is Info', () => {
logger.trace('test message');
expect(mockAppender.log).not.toHaveBeenCalled();
});
it('should not log silly messages when threshold is Info', () => {
logger.silly('test message');
expect(mockAppender.log).not.toHaveBeenCalled();
});
it('should log all messages when threshold is Debug', () => {
threshold.value = LoggingLevel.Debug;
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
expect(mockAppender.log).toHaveBeenCalledTimes(4);
});
it('should log only error messages when threshold is Error', () => {
threshold.value = LoggingLevel.Error;
logger.error('error message');
logger.warn('warn message');
logger.info('info message');
expect(mockAppender.log).toHaveBeenCalledTimes(1);
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Error',
'test-category',
'error message'
);
});
});
describe('message formatting', () => {
beforeEach(() => {
threshold.value = LoggingLevel.Debug; // Allow all messages for formatting tests
});
it('should format messages with %s placeholder', () => {
logger.info('Hello %s', 'world');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Hello world'
);
});
it('should format messages with %d placeholder', () => {
logger.info('Count: %d', 42);
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Count: 42'
);
});
it('should format messages with %j placeholder', () => {
logger.info('Object: %j', { key: 'value' });
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Object: {"key":"value"}'
);
});
it('should handle circular references in %j placeholder', () => {
const obj: any = { key: 'value' };
obj.self = obj;
logger.info('Circular: %j', obj);
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Circular: [Circular]'
);
});
it('should handle multiple placeholders', () => {
logger.info('User %s has %d items: %j', 'Alice', 5, ['a', 'b']);
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'User Alice has 5 items: ["a","b"]'
);
});
it('should handle more placeholders than arguments', () => {
logger.info('Hello %s %s', 'world');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Hello world %s'
);
});
it('should handle fewer placeholders than arguments', () => {
logger.info('Hello %s', 'world', 'extra');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Hello world'
);
});
it('should handle non-number values for %d', () => {
logger.info('Value: %d', 'not-a-number');
expect(mockAppender.log).toHaveBeenCalledWith(
expect.any(String),
'Info',
'test-category',
'Value: NaN'
);
});
});
describe('timestamp formatting', () => {
it('should include ISO timestamp in log messages', () => {
const before = new Date().toISOString();
logger.info('test');
const after = new Date().toISOString();
const call = (mockAppender.log as Mock).mock.calls[0];
const timestamp = call[0];
expect(timestamp).toBeDefined();
expect(() => new Date(timestamp)).not.toThrow();
expect(timestamp >= before || timestamp <= after).toBe(true);
});
});
describe('multiple appenders', () => {
it('should call all appenders', () => {
const mockAppender2: IAppender = {
log: vi.fn(),
};
const loggerWithMultiple = new Logger('test', threshold, new Set([mockAppender, mockAppender2]));
loggerWithMultiple.info('test message');
expect(mockAppender.log).toHaveBeenCalledTimes(1);
expect(mockAppender2.log).toHaveBeenCalledTimes(1);
});
});
});