diff options
author | Eric Bailey <git@esb.lol> | 2025-02-28 12:09:36 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-28 12:09:36 -0600 |
commit | 7c36ea115855050f319be19bb74d6f7fd80f8eed (patch) | |
tree | ed32d674b1b74dca813ad9cac44a621313431270 /src/logger/index.ts | |
parent | 9e9ffd5c6e9e5c672f60aa10d60c6628a15ae783 (diff) | |
download | voidsky-7c36ea115855050f319be19bb74d6f7fd80f8eed.tar.zst |
Logger improvements (#7729)
* Remove enablement * Refactor context and filtering * Fix imports, simplify transports config * Migrate usages of debug context * Re-org, add colors and grouping to console logging * Remove temp default context * Remove manual prefix * Move colorizing out of console transport body * Reduce reuse * Pass through context * Ensure bitdrift is enabled in dev * Enable Sentry on web only * Clean up types * Docs * Format * Update tests * Clean up tests * No positional args * Revert Sentry changes * Clean up context, use it, pass metadata through to Bitdrift * Fix up debugging * Clean up metadata before passing to Bitdrift * Correct transports * Reserve context prop on metadata and include in transports * Update tests
Diffstat (limited to 'src/logger/index.ts')
-rw-r--r-- | src/logger/index.ts | 290 |
1 files changed, 76 insertions, 214 deletions
diff --git a/src/logger/index.ts b/src/logger/index.ts index 102bccef7..70b7ee8be 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,213 +1,89 @@ -import format from 'date-fns/format' import {nanoid} from 'nanoid/non-secure' -import {isNetworkError} from '#/lib/strings/errors' -import {DebugContext} from '#/logger/debugContext' import {add} from '#/logger/logDump' -import {Sentry} from '#/logger/sentry' -import * as env from '#/env' -import {createBitdriftTransport} from './bitdriftTransport' -import {Metadata} from './types' -import {ConsoleTransportEntry, LogLevel, Transport} from './types' - -export {LogLevel} -export type {ConsoleTransportEntry, Transport} - -const enabledLogLevels: { - [key in LogLevel]: LogLevel[] -} = { - [LogLevel.Debug]: [ - LogLevel.Debug, - LogLevel.Info, - LogLevel.Log, - LogLevel.Warn, - LogLevel.Error, - ], - [LogLevel.Info]: [LogLevel.Info, LogLevel.Log, LogLevel.Warn, LogLevel.Error], - [LogLevel.Log]: [LogLevel.Log, LogLevel.Warn, LogLevel.Error], - [LogLevel.Warn]: [LogLevel.Warn, LogLevel.Error], - [LogLevel.Error]: [LogLevel.Error], -} - -export function prepareMetadata(metadata: Metadata): Metadata { - return Object.keys(metadata).reduce((acc, key) => { - let value = metadata[key] - if (value instanceof Error) { - value = value.toString() +import {bitdriftTransport} from '#/logger/transports/bitdrift' +import {consoleTransport} from '#/logger/transports/console' +import {sentryTransport} from '#/logger/transports/sentry' +import {LogContext, LogLevel, Metadata, Transport} from '#/logger/types' +import {enabledLogLevels} from '#/logger/util' + +const TRANSPORTS: Transport[] = (function configureTransports() { + switch (process.env.NODE_ENV) { + case 'production': { + return [sentryTransport, bitdriftTransport].filter(Boolean) as Transport[] } - return {...acc, [key]: value} - }, {}) -} - -/** - * Used in dev mode to nicely log to the console - */ -export const consoleTransport: Transport = ( - level, - message, - metadata, - timestamp, -) => { - const extra = Object.keys(metadata).length - ? ' ' + JSON.stringify(prepareMetadata(metadata), null, ' ') - : '' - const log = { - [LogLevel.Debug]: console.debug, - [LogLevel.Info]: console.info, - [LogLevel.Log]: console.log, - [LogLevel.Warn]: console.warn, - [LogLevel.Error]: console.error, - }[level] - - if (message instanceof Error) { - console.info( - `${format(timestamp, 'HH:mm:ss')} ${message.toString()}${extra}`, - ) - log(message) - } else { - log(`${format(timestamp, 'HH:mm:ss')} ${message.toString()}${extra}`) - } -} - -export const sentryTransport: Transport = ( - level, - message, - {type, tags, ...metadata}, - timestamp, -) => { - const meta = prepareMetadata(metadata) - - /** - * If a string, report a breadcrumb - */ - if (typeof message === 'string') { - const severity = ( - { - [LogLevel.Debug]: 'debug', - [LogLevel.Info]: 'info', - [LogLevel.Log]: 'log', // Sentry value here is undefined - [LogLevel.Warn]: 'warning', - [LogLevel.Error]: 'error', - } as const - )[level] - - Sentry.addBreadcrumb({ - message, - data: meta, - type: type || 'default', - level: severity, - timestamp: timestamp / 1000, // Sentry expects seconds - }) - - // We don't want to send any network errors to sentry - if (isNetworkError(message)) { - return + case 'test': { + return [] } - - /** - * Send all higher levels with `captureMessage`, with appropriate severity - * level - */ - if (level === 'error' || level === 'warn' || level === 'log') { - const messageLevel = ({ - [LogLevel.Log]: 'log', - [LogLevel.Warn]: 'warning', - [LogLevel.Error]: 'error', - }[level] || 'log') as Sentry.Breadcrumb['level'] - // Defer non-critical messages so they're sent in a batch - queueMessageForSentry(message, { - level: messageLevel, - tags, - extra: meta, - }) + default: { + return [consoleTransport] } - } else { - /** - * It's otherwise an Error and should be reported with captureException - */ - Sentry.captureException(message, { - tags, - extra: meta, - }) } -} +})() -const queuedMessages: [string, Parameters<typeof Sentry.captureMessage>[1]][] = - [] -let sentrySendTimeout: ReturnType<typeof setTimeout> | null = null -function queueMessageForSentry( - message: string, - captureContext: Parameters<typeof Sentry.captureMessage>[1], -) { - queuedMessages.push([message, captureContext]) - if (!sentrySendTimeout) { - // Throttle sending messages with a leading delay - // so that we can get Sentry out of the critical path. - sentrySendTimeout = setTimeout(() => { - sentrySendTimeout = null - sendQueuedMessages() - }, 7000) - } -} -function sendQueuedMessages() { - while (queuedMessages.length > 0) { - const record = queuedMessages.shift() - if (record) { - Sentry.captureMessage(record[0], record[1]) - } - } -} - -/** - * 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 + static Level = LogLevel + static Context = LogContext - enabled: boolean level: LogLevel - transports: Transport[] = [] + context: LogContext | undefined = undefined + contextFilter: string = '' protected debugContextRegexes: RegExp[] = [] + protected transports: Transport[] = [] + + static create(context?: LogContext) { + const logger = new Logger({ + level: process.env.EXPO_PUBLIC_LOG_LEVEL as LogLevel, + context, + contextFilter: process.env.EXPO_PUBLIC_LOG_DEBUG || '', + }) + for (const transport of TRANSPORTS) { + logger.addTransport(transport) + } + return logger + } constructor({ - enabled = process.env.NODE_ENV !== 'test', - level = env.LOG_LEVEL as LogLevel, - debug = env.LOG_DEBUG || '', + level, + context, + contextFilter, }: { - enabled?: boolean level?: LogLevel - debug?: string + context?: LogContext + contextFilter?: string } = {}) { - this.enabled = enabled !== false - 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, '.*')) - }) + this.context = context + this.level = level || LogLevel.Info + this.contextFilter = contextFilter || '' + if (this.contextFilter) { + this.level = LogLevel.Debug + } + this.debugContextRegexes = (this.contextFilter || '') + .split(',') + .map(filter => { + return new RegExp(filter.replace(/[^\w:*-]/, '').replace(/\*/g, '.*')) + }) } - debug(message: string, metadata: Metadata = {}, context?: string) { - if (context && !this.debugContextRegexes.find(reg => reg.test(context))) - return - this.transport(LogLevel.Debug, message, metadata) + debug(message: string, metadata: Metadata = {}) { + this.transport({level: LogLevel.Debug, message, metadata}) } info(message: string, metadata: Metadata = {}) { - this.transport(LogLevel.Info, message, metadata) + this.transport({level: LogLevel.Info, message, metadata}) } log(message: string, metadata: Metadata = {}) { - this.transport(LogLevel.Log, message, metadata) + this.transport({level: LogLevel.Log, message, metadata}) } warn(message: string, metadata: Metadata = {}) { - this.transport(LogLevel.Warn, message, metadata) + this.transport({level: LogLevel.Warn, message, metadata}) } error(error: Error | string, metadata: Metadata = {}) { - this.transport(LogLevel.Error, error, metadata) + this.transport({level: LogLevel.Error, message: error, metadata}) } addTransport(transport: Transport) { @@ -217,20 +93,22 @@ export class Logger { } } - disable() { - this.enabled = false - } - - enable() { - this.enabled = true - } - - protected transport( - level: LogLevel, - message: string | Error, - metadata: Metadata = {}, - ) { - if (!this.enabled) return + protected transport({ + level, + message, + metadata = {}, + }: { + level: LogLevel + message: string | Error + metadata: Metadata + }) { + if ( + level === LogLevel.Debug && + !!this.contextFilter && + !!this.context && + !this.debugContextRegexes.find(reg => reg.test(this.context!)) + ) + return const timestamp = Date.now() const meta = metadata || {} @@ -240,6 +118,7 @@ export class Logger { id: nanoid(), timestamp, level, + context: this.context, message, metadata: meta, }) @@ -247,37 +126,20 @@ 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) } } } /** - * Logger instance. See `@/logger/README` for docs. + * Default logger instance. See `@/logger/README` for docs. * * Basic usage: * - * `logger.debug(message[, metadata, debugContext])` + * `logger.debug(message[, metadata])` * `logger.info(message[, metadata])` + * `logger.log(message[, metadata])` * `logger.warn(message[, metadata])` * `logger.error(error[, metadata])` - * `logger.disable()` - * `logger.enable()` */ -export const logger = new Logger() - -if (process.env.NODE_ENV !== 'test') { - logger.addTransport(createBitdriftTransport()) -} - -if (process.env.NODE_ENV !== 'test') { - if (__DEV__) { - logger.addTransport(consoleTransport) - /* - * Comment this out to enable Sentry transport in dev - */ - // logger.addTransport(sentryTransport) - } else { - logger.addTransport(sentryTransport) - } -} +export const logger = Logger.create(Logger.Context.Default) |