Skip to main content
@faremeter/logs provides a lightweight logging abstraction used across all faremeter packages. It supports pluggable backends, log levels, and structured context objects.
pnpm add @faremeter/logs

Basic usage

Get a logger without any explicit configuration. The library auto-resolves a backend (LogtapeBackend if @logtape/logtape is installed, otherwise ConsoleBackend).
import { getLogger } from "@faremeter/logs"

const logger = await getLogger(["my-app"])

logger.info("Server started", { port: 3000 })
logger.error("Request failed", { status: 500, path: "/api/resource" })
The subsystem array passed to getLogger identifies the component producing the log entry. Backends use this for filtering and categorization.

Configuring the app

Call configureApp once at startup to set the log level and optionally choose a backend:
import { configureApp, getLogger } from "@faremeter/logs"

await configureApp({ level: "debug" })

const logger = await getLogger(["my-app"])
logger.debug("This will now appear")

ConfigureAppArgs

OptionTypeDefaultDescription
levelLogLevel"info"Minimum log level to emit.
backendLoggingBackendAuto-resolvedLogging backend to use.

Log levels

The LogLevels constant defines all available levels in order of increasing severity:
import { LogLevels } from "@faremeter/logs"

// ["trace", "debug", "info", "warning", "error", "fatal"]
LevelUse case
traceFine-grained debugging (RPC calls, serialization).
debugDevelopment diagnostics.
infoNormal operational events.
warningRecoverable issues that may need attention.
errorFailures that prevent an operation from completing.
fatalUnrecoverable errors requiring shutdown.
Messages below the configured level are suppressed.

Context objects

Every log method accepts an optional context object as the second argument:
logger.info("Payment settled", {
  txHash: "5xK9...",
  network: "devnet",
  amount: 0.01,
})
Context values can be any serializable type (Record<string, unknown>). Backends determine how context is rendered.

Custom backend

Implement the LoggingBackend interface to route logs to any destination:
import type { LoggingBackend, LogLevel, LogArgs } from "@faremeter/logs"

const myBackend: LoggingBackend = {
  async configureApp({ level }: { level: LogLevel }) {
    // Store the configured level, initialize your transport, etc.
  },

  getLogger(subsystem: readonly string[]) {
    const prefix = subsystem.join(":")
    return {
      debug: (msg, ctx?) => console.debug(`[${prefix}]`, msg, ctx),
      info: (msg, ctx?) => console.info(`[${prefix}]`, msg, ctx),
      warning: (msg, ctx?) => console.warn(`[${prefix}]`, msg, ctx),
      error: (msg, ctx?) => console.error(`[${prefix}]`, msg, ctx),
      fatal: (msg, ctx?) => console.error(`[FATAL][${prefix}]`, msg, ctx),
    }
  },
}
Pass it to configureApp:
import { configureApp } from "@faremeter/logs"

await configureApp({ level: "debug", backend: myBackend })

LogtapeBackend

For structured logging in production, use the LogtapeBackend from @faremeter/logs/logtape. This is the default backend when @logtape/logtape is installed.
pnpm add @logtape/logtape
import { configureApp } from "@faremeter/logs"
import { LogtapeBackend } from "@faremeter/logs/logtape"

await configureApp({ level: "info", backend: LogtapeBackend })
LogtapeBackend integrates with the logtape ecosystem, enabling file sinks, JSON formatting, and other logtape transports.

Further reading