import {jest} from '@jest/globals'; import { Lumbermill } from "./Lumbermill"; import { Output } from "./Output"; import { MessageEnvelope, CommonConfig } from "./types"; const consoleLogLevels = ["error", "warn", "info", "log", "debug", "trace"]; class TestLumbermill extends Lumbermill { outputDebugVarPatterns: string[] = []; loggerDebugVarPatterns: string[] = []; otherDebugVarPatterns: string[] = []; parseDebugVarPatterns(debugVar: string | undefined): string[] { if (debugVar === undefined || debugVar.length === 0) { return []; } return this[debugVar]; } getRawLogger(name: string, config?: CommonConfig) { const l = this.getOrCreateLogger(name, config); return l; } } class TestOutput extends Output { static writer = jest.fn(); writeLog(env: MessageEnvelope): Promise { TestOutput.writer(`${this.name}: ${env.args}`); // console[this.getOutputLevel(env.level)](...env.args); return Promise.resolve(); } } class TestContextOutput extends Output { static writer = jest.fn(); writeLog(env: MessageEnvelope): Promise { TestContextOutput.writer(env.ctx); // console[this.getOutputLevel(env.level)](...env.args); return Promise.resolve(); } } test("configs inherit", () => { const output = new TestOutput("test"); const root = new TestLumbermill( { logLevels: consoleLogLevels, logLevel: "warn", context: { level: "root", }, }, [output] ); const child = root.getRawLogger("blarg", { logLevel: "error", }); const childConfig = child.cfg.getConfig(); expect(childConfig).toMatchInlineSnapshot(` Object { "context": Object { "level": "root", }, "enabled": true, "logLevel": "error", "logLevels": Array [ "error", "warn", "info", "log", "debug", "trace", ], } `); }); test("context inherits", () => { const output = new TestOutput("test"); const root = new TestLumbermill( { logLevels: consoleLogLevels, logLevel: "warn", context: { level: "root", }, }, [output] ); const child = root.getRawLogger("blarg", { context: { hello: "you", }, }); const childConfig = child.cfg.getConfig(); expect(childConfig.context).toMatchInlineSnapshot(` Object { "hello": "you", "level": "root", } `); }); afterEach(() => { TestOutput.writer.mockReset(); TestContextOutput.writer.mockReset(); }); test('it will filter based on debugVar in root logger config', async () => { const output = new TestOutput("test"); const root = new TestLumbermill( { logLevels: consoleLogLevels, debugVar: 'loggerDebugVarPatterns' }, [output] ); root.loggerDebugVarPatterns = ['foo']; const fooLogger = root.getLogger('foo'); const barLogger = root.getLogger('bar'); await barLogger.info('you should not see this'); // await output.processMessage({ logger: barLogger, level: 'info', messageArgs: ['you should not see this'] }); expect(TestOutput.writer).not.toHaveBeenCalled(); await fooLogger.info('you should see this'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test: you should see this'); }); test('it will filter based on debugVar in logger config over debugVar in rootConfig', async () => { const output = new TestOutput("test"); const root = new TestLumbermill( { logLevels: consoleLogLevels, debugVar: 'loggerDebugVarPatterns' }, [output] ); root.loggerDebugVarPatterns = ['foo']; root.otherDebugVarPatterns = ['bar']; const fooLogger = root.getLogger('foo', { debugVar: 'otherDebugVarPatterns' }); const barLogger = root.getLogger('bar', { debugVar: 'otherDebugVarPatterns' }); await fooLogger.info('you should not see this'); // await output.processMessage({ logger: barLogger, level: 'info', messageArgs: ['you should not see this'] }); expect(TestOutput.writer).not.toHaveBeenCalled(); await barLogger.info('you should see this'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test: you should see this'); }); test('it will filter based on debugVar in logger config over debugVar in rootConfig', async () => { const output = new TestOutput("test"); const root = new TestLumbermill( { logLevels: consoleLogLevels, debugVar: 'loggerDebugVarPatterns' }, [output] ); root.loggerDebugVarPatterns = ['foo']; root.otherDebugVarPatterns = ['bar']; const fooLogger = root.getLogger('foo', { debugVar: 'otherDebugVarPatterns' }); const barLogger = root.getLogger('bar', { debugVar: 'otherDebugVarPatterns' }); await fooLogger.info('you should not see this'); // await output.processMessage({ logger: barLogger, level: 'info', messageArgs: ['you should not see this'] }); expect(TestOutput.writer).not.toHaveBeenCalled(); await barLogger.info('you should see this'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test: you should see this'); }); test('it will filter based on debugVar in output config over all else', async () => { const output = new TestOutput("test", { debugVar: 'outputDebugVarPatterns', }); const root = new TestLumbermill( { logLevels: consoleLogLevels, debugVar: 'loggerDebugVarPatterns' }, [output] ); root.loggerDebugVarPatterns = ['foo']; root.otherDebugVarPatterns = ['bar']; root.outputDebugVarPatterns = ['zap']; const fooLogger = root.getLogger('foo', { debugVar: 'otherDebugVarPatterns' }); const barLogger = root.getLogger('bar', { debugVar: 'otherDebugVarPatterns' }); const zapLogger = root.getLogger('zap', { debugVar: 'otherDebugVarPatterns' }); await fooLogger.info('you should not see this'); expect(TestOutput.writer).not.toHaveBeenCalled(); await barLogger.info('you should not see this'); expect(TestOutput.writer).not.toHaveBeenCalled(); await zapLogger.info('you should see this'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test: you should see this'); }); test('it will fix legacy style negative pattern', async () => { const output = new TestOutput("test"); const root = new TestLumbermill( { logLevels: consoleLogLevels, debugVar: 'loggerDebugVarPatterns' }, [output] ); root.loggerDebugVarPatterns = ['-foo:bing']; // root.outputDebugVarPatterns = ['foo:out']; const fooLogger = root.getLogger('foo:bing'); const barLogger = root.getLogger('foo:bar'); const outLogger = root.getLogger('foo:out'); await fooLogger.info('you should not see this'); expect(TestOutput.writer).not.toHaveBeenCalled(); await barLogger.info('you should see this'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test: you should see this'); await outLogger.info('you should see this'); expect(TestOutput.writer).toHaveBeenNthCalledWith(2, 'test: you should see this'); }); test('it will log with message context', async () => { const output = new TestContextOutput("testcontext"); const root = new TestLumbermill( { logLevels: consoleLogLevels, }, [output] ); const logger = root.getLogger('some:logger'); await logger.info.withContext({ some: 'context'}); expect(TestContextOutput.writer).toHaveBeenCalledWith({ some: 'context' }); }); test('it will combine message context with output, global, and logger context', async () => { const output = new TestContextOutput("testcontext", { context: { someoutput: 'contextvalue', } }); const root = new TestLumbermill( { logLevels: consoleLogLevels, context: { someglobal: 'contextvalue' }, }, [output] ); const logger = root.getLogger('some:logger', { context: { somelogger: 'contextvalue' }, }); await logger.info.withContext({ somemessage: 'contextvalue'}); expect(TestContextOutput.writer).toHaveBeenCalledWith({ someglobal: 'contextvalue', somelogger: 'contextvalue', someoutput: 'contextvalue', somemessage: 'contextvalue', }); }); test('works with multiple outputs', async () => { const output1 = new TestOutput("test1", { context: { someoutput: 'contextvalue', } }); const output2 = new TestOutput("test2", { context: { someother: 'contextvalue', } }); const root = new TestLumbermill( { logLevels: consoleLogLevels, context: { someglobal: 'contextvalue' }, }, [output1, output2] ); const logger = root.getLogger('some:logger', { context: { somelogger: 'contextvalue' }, }); await logger.info('hello'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test1: hello') expect(TestOutput.writer).toHaveBeenNthCalledWith(2, 'test2: hello') }); test('you can replace the outputs', async () => { const output1 = new TestOutput("test1", { context: { someoutput: 'contextvalue', } }); const output2 = new TestOutput("test2", { context: { someother: 'contextvalue', } }); const root = new TestLumbermill( { logLevels: consoleLogLevels, context: { someglobal: 'contextvalue' }, }, [output1] ); const logger = root.getLogger('some:logger', { context: { somelogger: 'contextvalue' }, }); await logger.info('hello'); root.setOutputs([output2]); await logger.info('hello'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test1: hello'); expect(TestOutput.writer).toHaveBeenNthCalledWith(2, 'test2: hello'); }); test('you can add outputs', async () => { const output1 = new TestOutput("test1", { context: { someoutput: 'contextvalue', } }); const output2 = new TestOutput("test2", { context: { someother: 'contextvalue', } }); const root = new TestLumbermill( { logLevels: consoleLogLevels, context: { someglobal: 'contextvalue' }, }, [output1] ); const logger = root.getLogger('some:logger', { context: { somelogger: 'contextvalue' }, }); await logger.info('hello'); root.addOutput(output2); await logger.info('hello again'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test1: hello'); expect(TestOutput.writer).toHaveBeenNthCalledWith(2, 'test1: hello again'); expect(TestOutput.writer).toHaveBeenNthCalledWith(3, 'test2: hello again'); }); test('when output is disabled, it will not write', async () => { const output1 = new TestOutput("test1", { enabled: true, }); const output2 = new TestContextOutput("test2", { enabled: false, }); const root = new TestLumbermill( { logLevels: consoleLogLevels, context: { someglobal: 'contextvalue' }, }, [output1, output2] ); const logger = root.getLogger('some:logger'); await logger.info('hello'); root.addOutput(output2); await logger.info('hello again'); expect(TestOutput.writer).toHaveBeenNthCalledWith(1, 'test1: hello'); expect(TestContextOutput.writer).not.toHaveBeenCalled(); })