Refactor context and filtering

This commit is contained in:
Eric Bailey
2025-02-10 15:28:38 -05:00
parent 4f3dbd2102
commit 5df4ded61e
4 changed files with 113 additions and 41 deletions
+67 -17
View File
@@ -36,11 +36,6 @@ describe('general functionality', () => {
expect(logger.level).toEqual(LogLevel.Info) 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', () => { test('supports extra metadata', () => {
const timestamp = Date.now() const timestamp = Date.now()
const logger = new Logger({}) const logger = new Logger({})
@@ -54,6 +49,7 @@ describe('general functionality', () => {
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn, LogLevel.Warn,
undefined,
'message', 'message',
extra, extra,
timestamp, timestamp,
@@ -72,6 +68,7 @@ describe('general functionality', () => {
logger.warn('a', null) logger.warn('a', null)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn, LogLevel.Warn,
undefined,
'a', 'a',
{}, {},
timestamp, timestamp,
@@ -81,6 +78,7 @@ describe('general functionality', () => {
logger.warn('b', false) logger.warn('b', false)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn, LogLevel.Warn,
undefined,
'b', 'b',
{}, {},
timestamp, timestamp,
@@ -90,6 +88,7 @@ describe('general functionality', () => {
logger.warn('c', 0) logger.warn('c', 0)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn, LogLevel.Warn,
undefined,
'c', 'c',
{}, {},
timestamp, timestamp,
@@ -97,7 +96,7 @@ describe('general functionality', () => {
remove() remove()
logger.addTransport((level, message, metadata) => { logger.addTransport((level, context, message, metadata) => {
expect(typeof metadata).toEqual('object') expect(typeof metadata).toEqual('object')
}) })
@@ -110,7 +109,7 @@ describe('general functionality', () => {
const timestamp = Date.now() const timestamp = Date.now()
const sentryTimestamp = timestamp / 1000 const sentryTimestamp = timestamp / 1000
sentryTransport(LogLevel.Debug, message, {}, timestamp) sentryTransport(LogLevel.Debug, undefined, message, {}, timestamp)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({ expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
message, message,
data: {}, data: {},
@@ -121,6 +120,7 @@ describe('general functionality', () => {
sentryTransport( sentryTransport(
LogLevel.Info, LogLevel.Info,
undefined,
message, message,
{type: 'info', prop: true}, {type: 'info', prop: true},
timestamp, timestamp,
@@ -133,7 +133,7 @@ describe('general functionality', () => {
timestamp: sentryTimestamp, timestamp: sentryTimestamp,
}) })
sentryTransport(LogLevel.Log, message, {}, timestamp) sentryTransport(LogLevel.Log, undefined, message, {}, timestamp)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({ expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
message, message,
data: {}, data: {},
@@ -148,7 +148,7 @@ describe('general functionality', () => {
extra: {}, extra: {},
}) })
sentryTransport(LogLevel.Warn, message, {}, timestamp) sentryTransport(LogLevel.Warn, undefined, message, {}, timestamp)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({ expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
message, message,
data: {}, data: {},
@@ -170,6 +170,7 @@ describe('general functionality', () => {
sentryTransport( sentryTransport(
LogLevel.Error, LogLevel.Error,
undefined,
e, e,
{ {
tags, tags,
@@ -193,6 +194,7 @@ describe('general functionality', () => {
sentryTransport( sentryTransport(
LogLevel.Debug, LogLevel.Debug,
undefined,
message, message,
{error: new Error('foo')}, {error: new Error('foo')},
timestamp, timestamp,
@@ -223,6 +225,7 @@ describe('general functionality', () => {
expect(mockTransport).toHaveBeenNthCalledWith( expect(mockTransport).toHaveBeenNthCalledWith(
1, 1,
LogLevel.Warn, LogLevel.Warn,
undefined,
'warn', 'warn',
{}, {},
timestamp, 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', () => { describe('debug contexts', () => {
const mockTransport = jest.fn() const mockTransport = jest.fn()
@@ -237,14 +261,17 @@ describe('debug contexts', () => {
const timestamp = Date.now() const timestamp = Date.now()
const message = nanoid() const message = nanoid()
const logger = new Logger({ const logger = new Logger({
debug: 'specific', // @ts-ignore
context: 'specific',
level: LogLevel.Debug,
}) })
logger.addTransport(mockTransport) logger.addTransport(mockTransport)
logger.debug(message, {}, 'specific') logger.debug(message, {})
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug, LogLevel.Debug,
'specific',
message, message,
{}, {},
timestamp, timestamp,
@@ -255,14 +282,17 @@ describe('debug contexts', () => {
const timestamp = Date.now() const timestamp = Date.now()
const message = nanoid() const message = nanoid()
const logger = new Logger({ const logger = new Logger({
debug: 'namespace*', // @ts-ignore
context: 'namespace:foo',
contextFilter: 'namespace*',
}) })
logger.addTransport(mockTransport) logger.addTransport(mockTransport)
logger.debug(message, {}, 'namespace') logger.debug(message, {})
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug, LogLevel.Debug,
'namespace:foo',
message, message,
{}, {},
timestamp, timestamp,
@@ -273,14 +303,17 @@ describe('debug contexts', () => {
const timestamp = Date.now() const timestamp = Date.now()
const message = nanoid() const message = nanoid()
const logger = new Logger({ const logger = new Logger({
debug: 'namespace:foo:*', // @ts-ignore
context: 'namespace:bar:baz',
contextFilter: 'namespace:foo:*',
}) })
logger.addTransport(mockTransport) logger.addTransport(mockTransport)
logger.debug(message, {}, 'namespace:bar:baz') logger.debug(message, {})
expect(mockTransport).not.toHaveBeenCalledWith( expect(mockTransport).not.toHaveBeenCalledWith(
LogLevel.Debug, LogLevel.Debug,
'namespace:bar:baz',
message, message,
{}, {},
timestamp, timestamp,
@@ -302,6 +335,7 @@ describe('supports levels', () => {
logger.debug(message) logger.debug(message)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug, LogLevel.Debug,
undefined,
message, message,
{}, {},
timestamp, timestamp,
@@ -310,6 +344,7 @@ describe('supports levels', () => {
logger.info(message) logger.info(message)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info, LogLevel.Info,
undefined,
message, message,
{}, {},
timestamp, timestamp,
@@ -318,6 +353,7 @@ describe('supports levels', () => {
logger.warn(message) logger.warn(message)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn, LogLevel.Warn,
undefined,
message, message,
{}, {},
timestamp, timestamp,
@@ -325,7 +361,13 @@ describe('supports levels', () => {
const e = new Error(message) const e = new Error(message)
logger.error(e) logger.error(e)
expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp) expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Error,
undefined,
e,
{},
timestamp,
)
}) })
test('info', () => { test('info', () => {
@@ -344,6 +386,7 @@ describe('supports levels', () => {
logger.info(message) logger.info(message)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info, LogLevel.Info,
undefined,
message, message,
{}, {},
timestamp, timestamp,
@@ -369,6 +412,7 @@ describe('supports levels', () => {
logger.warn(message) logger.warn(message)
expect(mockTransport).toHaveBeenCalledWith( expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn, LogLevel.Warn,
undefined,
message, message,
{}, {},
timestamp, timestamp,
@@ -396,6 +440,12 @@ describe('supports levels', () => {
const e = new Error('original message') const e = new Error('original message')
logger.error(e) logger.error(e)
expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp) expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Error,
undefined,
e,
{},
timestamp,
)
}) })
}) })
+41 -22
View File
@@ -2,7 +2,7 @@ import format from 'date-fns/format'
import {nanoid} from 'nanoid/non-secure' import {nanoid} from 'nanoid/non-secure'
import {isNetworkError} from '#/lib/strings/errors' import {isNetworkError} from '#/lib/strings/errors'
import {DebugContext} from '#/logger/debugContext' import {LogContext} from '#/logger/logContext'
import {add} from '#/logger/logDump' import {add} from '#/logger/logDump'
import {Sentry} from '#/logger/sentry' import {Sentry} from '#/logger/sentry'
import * as env from '#/env' import * as env from '#/env'
@@ -44,6 +44,7 @@ export function prepareMetadata(metadata: Metadata): Metadata {
*/ */
export const consoleTransport: Transport = ( export const consoleTransport: Transport = (
level, level,
context,
message, message,
metadata, metadata,
timestamp, timestamp,
@@ -71,6 +72,7 @@ export const consoleTransport: Transport = (
export const sentryTransport: Transport = ( export const sentryTransport: Transport = (
level, level,
context,
message, message,
{type, tags, ...metadata}, {type, tags, ...metadata},
timestamp, 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 { export class Logger {
LogLevel = LogLevel Level = LogLevel
DebugContext = DebugContext Context = LogContext
level: LogLevel level: LogLevel
context: keyof typeof LogContext | undefined = undefined
contextFilter: string = ''
transports: Transport[] = [] transports: Transport[] = []
protected debugContextRegexes: RegExp[] = [] protected debugContextRegexes: RegExp[] = []
constructor({ static create(context: keyof typeof LogContext) {
level = env.LOG_LEVEL as LogLevel, return new Logger({
debug = env.LOG_DEBUG || '', level: (env.LOG_LEVEL || LogLevel.Info) as LogLevel,
}: { context,
level?: LogLevel contextFilter: env.LOG_DEBUG || '',
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, '.*'))
}) })
} }
debug(message: string, metadata: Metadata = {}, context?: string) { constructor({
if (context && !this.debugContextRegexes.find(reg => reg.test(context))) level = env.LOG_LEVEL as LogLevel,
return 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) this.transport(LogLevel.Debug, message, metadata)
} }
@@ -218,6 +230,12 @@ export class Logger {
message: string | Error, message: string | Error,
metadata: Metadata = {}, metadata: Metadata = {},
) { ) {
if (
!!this.context &&
!this.debugContextRegexes.find(reg => reg.test(this.context!))
)
return
const timestamp = Date.now() const timestamp = Date.now()
const meta = metadata || {} const meta = metadata || {}
@@ -226,6 +244,7 @@ export class Logger {
id: nanoid(), id: nanoid(),
timestamp, timestamp,
level, level,
context: this.context,
message, message,
metadata: meta, metadata: meta,
}) })
@@ -233,7 +252,7 @@ export class Logger {
if (!enabledLogLevels[this.level].includes(level)) return if (!enabledLogLevels[this.level].includes(level)) return
for (const transport of this.transports) { 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: * Basic usage:
* *
* `logger.debug(message[, metadata, debugContext])` * `logger.debug(message[, metadata])`
* `logger.info(message[, metadata])` * `logger.info(message[, metadata])`
* `logger.warn(message[, metadata])` * `logger.warn(message[, metadata])`
* `logger.error(error[, metadata])` * `logger.error(error[, metadata])`
@@ -5,8 +5,8 @@
* be capitalized, for parity with the `LOG_DEBUG` env var, please use all * be capitalized, for parity with the `LOG_DEBUG` env var, please use all
* lowercase. * lowercase.
*/ */
export const DebugContext = { export const LogContext = {
// e.g. composer: 'composer' default: 'default',
session: 'session', session: 'session',
notifications: 'notifications', notifications: 'notifications',
convo: 'convo', convo: 'convo',
+3
View File
@@ -1,3 +1,4 @@
import {LogContext} from '#/logger/debugContext'
import type {Sentry} from '#/logger/sentry' import type {Sentry} from '#/logger/sentry'
export enum LogLevel { export enum LogLevel {
@@ -10,6 +11,7 @@ export enum LogLevel {
export type Transport = ( export type Transport = (
level: LogLevel, level: LogLevel,
context: keyof typeof LogContext | undefined,
message: string | Error, message: string | Error,
metadata: Metadata, metadata: Metadata,
timestamp: number, timestamp: number,
@@ -64,6 +66,7 @@ export type ConsoleTransportEntry = {
id: string id: string
timestamp: number timestamp: number
level: LogLevel level: LogLevel
context: keyof typeof LogContext | undefined
message: string | Error message: string | Error
metadata: Metadata metadata: Metadata
} }