diff --git a/src/logger/__tests__/logger.test.ts b/src/logger/__tests__/logger.test.ts index e6baf96e18..385e6f7a85 100644 --- a/src/logger/__tests__/logger.test.ts +++ b/src/logger/__tests__/logger.test.ts @@ -36,11 +36,6 @@ describe('general functionality', () => { expect(logger.level).toEqual(LogLevel.Info) }) - test('passing debug contexts automatically enables debug mode', () => { - const logger = new Logger({debug: 'specific'}) - expect(logger.level).toEqual(LogLevel.Debug) - }) - test('supports extra metadata', () => { const timestamp = Date.now() const logger = new Logger({}) @@ -54,6 +49,7 @@ describe('general functionality', () => { expect(mockTransport).toHaveBeenCalledWith( LogLevel.Warn, + undefined, 'message', extra, timestamp, @@ -72,6 +68,7 @@ describe('general functionality', () => { logger.warn('a', null) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Warn, + undefined, 'a', {}, timestamp, @@ -81,6 +78,7 @@ describe('general functionality', () => { logger.warn('b', false) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Warn, + undefined, 'b', {}, timestamp, @@ -90,6 +88,7 @@ describe('general functionality', () => { logger.warn('c', 0) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Warn, + undefined, 'c', {}, timestamp, @@ -97,7 +96,7 @@ describe('general functionality', () => { remove() - logger.addTransport((level, message, metadata) => { + logger.addTransport((level, context, message, metadata) => { expect(typeof metadata).toEqual('object') }) @@ -110,7 +109,7 @@ describe('general functionality', () => { const timestamp = Date.now() const sentryTimestamp = timestamp / 1000 - sentryTransport(LogLevel.Debug, message, {}, timestamp) + sentryTransport(LogLevel.Debug, undefined, message, {}, timestamp) expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({ message, data: {}, @@ -121,6 +120,7 @@ describe('general functionality', () => { sentryTransport( LogLevel.Info, + undefined, message, {type: 'info', prop: true}, timestamp, @@ -133,7 +133,7 @@ describe('general functionality', () => { timestamp: sentryTimestamp, }) - sentryTransport(LogLevel.Log, message, {}, timestamp) + sentryTransport(LogLevel.Log, undefined, message, {}, timestamp) expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({ message, data: {}, @@ -148,7 +148,7 @@ describe('general functionality', () => { extra: {}, }) - sentryTransport(LogLevel.Warn, message, {}, timestamp) + sentryTransport(LogLevel.Warn, undefined, message, {}, timestamp) expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({ message, data: {}, @@ -170,6 +170,7 @@ describe('general functionality', () => { sentryTransport( LogLevel.Error, + undefined, e, { tags, @@ -193,6 +194,7 @@ describe('general functionality', () => { sentryTransport( LogLevel.Debug, + undefined, message, {error: new Error('foo')}, timestamp, @@ -223,6 +225,7 @@ describe('general functionality', () => { expect(mockTransport).toHaveBeenNthCalledWith( 1, LogLevel.Warn, + undefined, 'warn', {}, timestamp, @@ -230,6 +233,27 @@ describe('general functionality', () => { }) }) +describe('create', () => { + const mockTransport = jest.fn() + + test('create', () => { + const timestamp = Date.now() + const message = nanoid() + const logger = Logger.create('notifications') + + logger.addTransport(mockTransport) + logger.info(message, {}) + + expect(mockTransport).toHaveBeenCalledWith( + LogLevel.Info, + 'notifications', + message, + {}, + timestamp, + ) + }) +}) + describe('debug contexts', () => { const mockTransport = jest.fn() @@ -237,14 +261,17 @@ describe('debug contexts', () => { const timestamp = Date.now() const message = nanoid() const logger = new Logger({ - debug: 'specific', + // @ts-ignore + context: 'specific', + level: LogLevel.Debug, }) logger.addTransport(mockTransport) - logger.debug(message, {}, 'specific') + logger.debug(message, {}) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Debug, + 'specific', message, {}, timestamp, @@ -255,14 +282,17 @@ describe('debug contexts', () => { const timestamp = Date.now() const message = nanoid() const logger = new Logger({ - debug: 'namespace*', + // @ts-ignore + context: 'namespace:foo', + contextFilter: 'namespace*', }) logger.addTransport(mockTransport) - logger.debug(message, {}, 'namespace') + logger.debug(message, {}) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Debug, + 'namespace:foo', message, {}, timestamp, @@ -273,14 +303,17 @@ describe('debug contexts', () => { const timestamp = Date.now() const message = nanoid() const logger = new Logger({ - debug: 'namespace:foo:*', + // @ts-ignore + context: 'namespace:bar:baz', + contextFilter: 'namespace:foo:*', }) logger.addTransport(mockTransport) - logger.debug(message, {}, 'namespace:bar:baz') + logger.debug(message, {}) expect(mockTransport).not.toHaveBeenCalledWith( LogLevel.Debug, + 'namespace:bar:baz', message, {}, timestamp, @@ -302,6 +335,7 @@ describe('supports levels', () => { logger.debug(message) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Debug, + undefined, message, {}, timestamp, @@ -310,6 +344,7 @@ describe('supports levels', () => { logger.info(message) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Info, + undefined, message, {}, timestamp, @@ -318,6 +353,7 @@ describe('supports levels', () => { logger.warn(message) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Warn, + undefined, message, {}, timestamp, @@ -325,7 +361,13 @@ describe('supports levels', () => { const e = new Error(message) logger.error(e) - expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp) + expect(mockTransport).toHaveBeenCalledWith( + LogLevel.Error, + undefined, + e, + {}, + timestamp, + ) }) test('info', () => { @@ -344,6 +386,7 @@ describe('supports levels', () => { logger.info(message) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Info, + undefined, message, {}, timestamp, @@ -369,6 +412,7 @@ describe('supports levels', () => { logger.warn(message) expect(mockTransport).toHaveBeenCalledWith( LogLevel.Warn, + undefined, message, {}, timestamp, @@ -396,6 +440,12 @@ describe('supports levels', () => { const e = new Error('original message') logger.error(e) - expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp) + expect(mockTransport).toHaveBeenCalledWith( + LogLevel.Error, + undefined, + e, + {}, + timestamp, + ) }) }) diff --git a/src/logger/index.ts b/src/logger/index.ts index 22cf38f0c6..a3175e1257 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -2,7 +2,7 @@ import format from 'date-fns/format' import {nanoid} from 'nanoid/non-secure' import {isNetworkError} from '#/lib/strings/errors' -import {DebugContext} from '#/logger/debugContext' +import {LogContext} from '#/logger/logContext' import {add} from '#/logger/logDump' import {Sentry} from '#/logger/sentry' import * as env from '#/env' @@ -44,6 +44,7 @@ export function prepareMetadata(metadata: Metadata): Metadata { */ export const consoleTransport: Transport = ( level, + context, message, metadata, timestamp, @@ -71,6 +72,7 @@ export const consoleTransport: Transport = ( export const sentryTransport: Transport = ( level, + context, message, {type, tags, ...metadata}, timestamp, @@ -158,35 +160,45 @@ function sendQueuedMessages() { } } -/** - * Main class. Defaults are provided in the constructor so that subclasses are - * technically possible, if we need to go that route in the future. - */ export class Logger { - LogLevel = LogLevel - DebugContext = DebugContext + Level = LogLevel + Context = LogContext level: LogLevel + context: keyof typeof LogContext | undefined = undefined + contextFilter: string = '' transports: Transport[] = [] protected debugContextRegexes: RegExp[] = [] - constructor({ - level = env.LOG_LEVEL as LogLevel, - debug = env.LOG_DEBUG || '', - }: { - level?: LogLevel - debug?: string - } = {}) { - this.level = debug ? LogLevel.Debug : level ?? LogLevel.Info // default to info - this.debugContextRegexes = (debug || '').split(',').map(context => { - return new RegExp(context.replace(/[^\w:*]/, '').replace(/\*/g, '.*')) + static create(context: keyof typeof LogContext) { + return new Logger({ + level: (env.LOG_LEVEL || LogLevel.Info) as LogLevel, + context, + contextFilter: env.LOG_DEBUG || '', }) } - debug(message: string, metadata: Metadata = {}, context?: string) { - if (context && !this.debugContextRegexes.find(reg => reg.test(context))) - return + constructor({ + level = env.LOG_LEVEL as LogLevel, + context, + contextFilter = env.LOG_DEBUG || '', + }: { + level?: LogLevel + context?: keyof typeof LogContext + contextFilter?: string + } = {}) { + this.context = context + this.level = level ?? LogLevel.Info // default to info + this.contextFilter = contextFilter || '' + this.debugContextRegexes = (this.contextFilter || '') + .split(',') + .map(filter => { + return new RegExp(filter.replace(/[^\w:*]/, '').replace(/\*/g, '.*')) + }) + } + + debug(message: string, metadata: Metadata = {}) { this.transport(LogLevel.Debug, message, metadata) } @@ -218,6 +230,12 @@ export class Logger { message: string | Error, metadata: Metadata = {}, ) { + if ( + !!this.context && + !this.debugContextRegexes.find(reg => reg.test(this.context!)) + ) + return + const timestamp = Date.now() const meta = metadata || {} @@ -226,6 +244,7 @@ export class Logger { id: nanoid(), timestamp, level, + context: this.context, message, metadata: meta, }) @@ -233,7 +252,7 @@ export class Logger { if (!enabledLogLevels[this.level].includes(level)) return for (const transport of this.transports) { - transport(level, message, meta, timestamp) + transport(level, this.context, message, meta, timestamp) } } } @@ -243,7 +262,7 @@ export class Logger { * * Basic usage: * - * `logger.debug(message[, metadata, debugContext])` + * `logger.debug(message[, metadata])` * `logger.info(message[, metadata])` * `logger.warn(message[, metadata])` * `logger.error(error[, metadata])` diff --git a/src/logger/debugContext.ts b/src/logger/logContext.ts similarity index 85% rename from src/logger/debugContext.ts rename to src/logger/logContext.ts index 9971207866..610b5b4d6f 100644 --- a/src/logger/debugContext.ts +++ b/src/logger/logContext.ts @@ -5,8 +5,8 @@ * be capitalized, for parity with the `LOG_DEBUG` env var, please use all * lowercase. */ -export const DebugContext = { - // e.g. composer: 'composer' +export const LogContext = { + default: 'default', session: 'session', notifications: 'notifications', convo: 'convo', diff --git a/src/logger/types.ts b/src/logger/types.ts index 252e7373be..fc0b9e4ffa 100644 --- a/src/logger/types.ts +++ b/src/logger/types.ts @@ -1,3 +1,4 @@ +import {LogContext} from '#/logger/debugContext' import type {Sentry} from '#/logger/sentry' export enum LogLevel { @@ -10,6 +11,7 @@ export enum LogLevel { export type Transport = ( level: LogLevel, + context: keyof typeof LogContext | undefined, message: string | Error, metadata: Metadata, timestamp: number, @@ -64,6 +66,7 @@ export type ConsoleTransportEntry = { id: string timestamp: number level: LogLevel + context: keyof typeof LogContext | undefined message: string | Error metadata: Metadata }