import {nanoid} from 'nanoid/non-secure' import {LogContext} from '#/logger/logContext' import {add} from '#/logger/logDump' import {bitdriftTransport} from '#/logger/transports/bitdrift' import {consoleTransport} from '#/logger/transports/console' import {sentryTransport} from '#/logger/transports/sentry' import { ConsoleTransportEntry, LogLevel, Metadata, Transport, } from '#/logger/types' import {enabledLogLevels} from '#/logger/util' export {LogLevel} export type {ConsoleTransportEntry, Transport} const TRANSPORTS: Transport[] = (function configureTransports() { switch (process.env.NODE_ENV) { case 'production': { return [sentryTransport, bitdriftTransport] } case 'test': { return [] } default: { return [consoleTransport, bitdriftTransport] } } })() export class Logger { static Level = LogLevel static Context = LogContext level: LogLevel context: keyof typeof LogContext | undefined = undefined contextFilter: string = '' protected debugContextRegexes: RegExp[] = [] protected transports: Transport[] = [] static create(context?: keyof typeof LogContext) { const logger = new Logger({ level: (process.env.EXPO_PUBLIC_LOG_LEVEL || LogLevel.Info) as LogLevel, context, contextFilter: process.env.EXPO_PUBLIC_LOG_DEBUG || '', }) for (const transport of TRANSPORTS) { logger.addTransport(transport) } return logger } constructor({ level = process.env.EXPO_PUBLIC_LOG_LEVEL as LogLevel, context, contextFilter = process.env.EXPO_PUBLIC_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) } info(message: string, metadata: Metadata = {}) { this.transport(LogLevel.Info, message, metadata) } log(message: string, metadata: Metadata = {}) { this.transport(LogLevel.Log, message, metadata) } warn(message: string, metadata: Metadata = {}) { this.transport(LogLevel.Warn, message, metadata) } error(error: Error | string, metadata: Metadata = {}) { this.transport(LogLevel.Error, error, metadata) } addTransport(transport: Transport) { this.transports.push(transport) return () => { this.transports.splice(this.transports.indexOf(transport), 1) } } protected transport( level: LogLevel, message: string | Error, metadata: Metadata = {}, ) { if ( !!this.contextFilter && !!this.context && !this.debugContextRegexes.find(reg => reg.test(this.context!)) ) return const timestamp = Date.now() const meta = metadata || {} // send every log to syslog add({ id: nanoid(), timestamp, level, context: this.context, message, metadata: meta, }) if (!enabledLogLevels[this.level].includes(level)) return for (const transport of this.transports) { transport(level, this.context, message, meta, timestamp) } } } /** * Default logger instance. See `@/logger/README` for docs. * * Basic usage: * * `logger.debug(message[, metadata])` * `logger.info(message[, metadata])` * `logger.warn(message[, metadata])` * `logger.error(error[, metadata])` * `logger.disable()` * `logger.enable()` */ export const logger = Logger.create()