about summary refs log tree commit diff
path: root/src/logger/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/logger/index.ts')
-rw-r--r--src/logger/index.ts290
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)