Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Logging

LogManager routes log messages to one or more named drivers. Drivers are resolved lazily and can be swapped at runtime with .use(). Three drivers ship out of the box: console, null, and stack.

LogProvider is included in defaultProviders, so no extra setup is required for most apps:

import { Application, defaultProviders } from "@raubjo/architect-core"

Application.configure().withProviders(defaultProviders).run()

Basic usage

Use the Log facade from any boot() hook or service:

import { Log } from "@raubjo/architect-core/support/facades"

Log.debug("Fetching user", { userId: 42 })
Log.info("User loaded")
Log.warn("Cache miss — falling back to API")
Log.error("Request failed", { status: 500, url: "/api/users" })

All four methods accept an optional structured context object as their second argument.

Drivers

DriverBehaviour
consoleWrites to the browser console using native console.debug/info/warn/error. Respects a minimum level threshold.
nullDiscards all messages. Useful in tests.
stackFans out each call to an ordered list of other drivers. Errors thrown by individual drivers are swallowed.

Configuration

Application.configure({
  config: {
    logging: {
      default: "console",
      drivers: {
        console: { level: "warn" }, // suppress debug and info in production
      },
    },
  },
})

Level threshold

ConsoleLogger supports a minimum level. Messages below the threshold are silently dropped.

LevelWhat passes
"debug"All messages (default)
"info"info, warn, error
"warn"warn, error
"error"error only

Fan-out with the stack driver

Use stack to write to multiple drivers simultaneously. Errors thrown by any individual driver are caught and swallowed — a logging failure will never crash the application.

Application.configure({
  config: {
    logging: {
      default: "stack",
      drivers: {
        stack: { drivers: ["console", "sentry"] },
        console: { level: "debug" },
        sentry: {},
      },
    },
  },
})

Registering a custom driver

Register custom drivers from a ServiceProvider’s boot() hook. The factory receives ConfigRepository and must return an object implementing the log Contract:

import { LogManager, type ServiceProviderContext } from "@raubjo/architect-core"

boot({ container }: ServiceProviderContext) {
  const manager = container.make(LogManager)

  manager.extend("sentry", (config) => {
    return new SentryLogger(config.get("logging.drivers.sentry.dsn"))
  })
}

The log Contract requires four methods:

interface Contract {
  debug(message: string, context?: Record<string, unknown>): void
  info(message: string, context?: Record<string, unknown>): void
  warn(message: string, context?: Record<string, unknown>): void
  error(message: string, context?: Record<string, unknown>): void
}

Switching drivers at runtime

import { Log } from "@raubjo/architect-core/support/facades"

Log.use("null")   // silence all output
Log.use("stack")  // restore fan-out

Using LogManager directly

import { LogManager } from "@raubjo/architect-core"

boot({ container }: ServiceProviderContext) {
  const log = container.make(LogManager)
  log.info("Provider booted")
}