@molecule/api-logger-axiom

Provider bond · logger · API (Node) · v1.0.2 · Apache-2.0

Axiom logger provider for molecule.dev — mirrors log lines with structured fields into an Axiom dataset while still printing them locally.

npm install @molecule/api-logger-axiom

npm · Source on GitHub · Implements @molecule/api-logger

How it works

@molecule/api-logger-axiom is a provider bond on the API (Node) side: it implements the logger core interface (@molecule/api-logger) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

import { logger, setLogger } from '@molecule/api-logger'
import { createLogger } from '@molecule/api-logger-axiom'

const axiomLogger = createLogger({
  // token / dataset default to AXIOM_TOKEN / AXIOM_DATASET
  level: 'warn', // mirror warn + error only; everything still prints locally
  service: 'my-api',
})
setLogger(axiomLogger)

logger.warn('Payment webhook retried', { provider: 'stripe', attempt: 2 })
// Axiom event: { _time, kind: 'log', level: 'warn',
//   message: 'Payment webhook retried',
//   fields: '{"provider":"stripe","attempt":2}', service: 'my-api', env }

await axiomLogger.shutdown() // before exit

Works with: @molecule/api-logger

Secrets: AXIOM_TOKEN (optional), AXIOM_DATASET (optional), AXIOM_ORG_ID (optional), AXIOM_EDGE_URL (optional)

Reference

Axiom logger provider for molecule.dev.

Every log call is written to an inner logger (the console by default) exactly as before, and lines at or above a level are also queued as structured events in an Axiom dataset. Sending is batched and asynchronous through the same ingest client as @molecule/api-analytics-axiom, so a log call never waits on the network.

Quick Start

import { logger, setLogger } from '@molecule/api-logger'
import { createLogger } from '@molecule/api-logger-axiom'

const axiomLogger = createLogger({
  // token / dataset default to AXIOM_TOKEN / AXIOM_DATASET
  level: 'warn', // mirror warn + error only; everything still prints locally
  service: 'my-api',
})
setLogger(axiomLogger)

logger.warn('Payment webhook retried', { provider: 'stripe', attempt: 2 })
// Axiom event: { _time, kind: 'log', level: 'warn',
//   message: 'Payment webhook retried',
//   fields: '{"provider":"stripe","attempt":2}', service: 'my-api', env }

await axiomLogger.shutdown() // before exit

Type

provider

Installation

npm install @molecule/api-logger-axiom @molecule/api-analytics-axiom @molecule/api-logger

API

Interfaces

AxiomLogger

A logger that mirrors into Axiom, with its ingester's lifecycle exposed.

interface AxiomLogger extends Logger {
  /** Whether a token and dataset are configured. When false lines only reach `inner`. */
  readonly enabled: boolean
  /** Send every queued line now. */
  flush(): Promise<void>
  /** Stop the flush timer and send what is left within `deadlineMs` (default 5000). */
  shutdown(options?: AxiomShutdownOptions): Promise<void>
}

AxiomLoggerOptions

Options for createLogger. Ingest fields fall back to the AXIOM_* env vars.

interface AxiomLoggerOptions extends AxiomIngesterOptions {
  /** Where every line is ALSO written, unchanged. Defaults to the console. Pass `null` to send to Axiom only. */
  inner?: Logger | null
  /** Lowest level mirrored to Axiom (default `'info'`). Lines below it still reach `inner`. */
  level?: Exclude<LogLevel, 'silent'>
  /**
   * `'json'` (default) stores a line's fields as one JSON string field, `fields`, so arbitrary
   * keys never add dataset columns (query with `parse_json(fields)`); `'object'` stores them as a
   * nested object, one column per key.
   */
  fieldsMode?: AxiomLogFieldsMode
  /** Stamped as `service` on every line. */
  service?: string
  /** Stamped as `env`. Defaults to `$NODE_ENV`. */
  env?: string
  /** Stamped as `region`. */
  region?: string
  /** Stamped as `version`. */
  version?: string
}

Types

AxiomLogFieldsMode

How a log line's structured fields are stored in Axiom.

type AxiomLogFieldsMode = 'json' | 'object'

Functions

createLogger(options)

Creates an Axiom logger.

function createLogger(options?: AxiomLoggerOptions): AxiomLogger
  • options — Ingest settings, the inner logger, the mirrored level and stamped fields.

Returns: The logger. Without a token and dataset it only writes to inner.

splitLogArgs(args)

Splits logger arguments into a message and structured fields: strings, numbers and booleans join into message; plain objects merge into fields; an Error becomes fields.error (or fields.errors for several); anything else is kept in fields.args.

function splitLogArgs(args: unknown[]): { message: string; fields: Record<string, unknown> }
  • args — The arguments passed to a log call.

Returns: The message and fields.

Constants

provider

The default logger: console output plus an Axiom mirror configured from AXIOM_TOKEN / AXIOM_DATASET / AXIOM_ORG_ID / AXIOM_EDGE_URL. Created on first use, so env loaded after import still applies.

const provider: AxiomLogger

Core Interface

Implements @molecule/api-logger interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setLogger } from '@molecule/api-logger'
import { provider } from '@molecule/api-logger-axiom'

export function setupLoggerAxiom(): void {
  setLogger(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-logger ^1.0.1

Environment Variables

  • AXIOM_TOKEN (optional) — Axiom API token
    • Setup: Create an API token with ingest permission for the dataset in Axiom → Settings → API tokens. Unset disables sending.
    • Get it here: https://app.axiom.co/settings/api-tokens
    • Example: xaat-...
  • AXIOM_DATASET (optional) — Axiom dataset
    • Setup: Name of the Axiom dataset events are sent to. Unset disables sending.
    • Example: my-app-prod
  • AXIOM_ORG_ID (optional) — Axiom org id
    • Setup: Only needed with a personal token; API tokens carry their org.
  • AXIOM_EDGE_URL (optional) — Axiom edge URL
    • Setup: Edge deployment base URL for region-local ingest; unset uses https://api.axiom.co.
    • Example: https://eu-central-1.aws.edge.axiom.co

Runtime Dependencies

  • @molecule/api-analytics-axiom

  • @molecule/api-logger

  • No token or no dataset → console only. enabled is false and nothing is queued; the inner logger still gets every line. Same env vars as @molecule/api-analytics-axiom: AXIOM_TOKEN, AXIOM_DATASET, AXIOM_ORG_ID, AXIOM_EDGE_URL. Pass dataset to send logs to a different dataset than your events.

  • Two level gates apply in order: the logger CORE's (LOG_LEVEL, default info) drops lines before this provider sees them; then level (default info) decides which of the remaining lines are mirrored to Axiom.

  • Arguments are split into message (strings, numbers, booleans joined by spaces) and fields (plain objects merged; an Error becomes error: { name, message, stack }). With fieldsMode: 'json' (the default) fields are ONE string field, so arbitrary log keys never add columns to the dataset — Axiom caps fields per dataset (256 on the free plan) — and you query them with parse_json(fields). fieldsMode: 'object' stores one column per key.

  • inner: null sends to Axiom only. The default inner logger writes to the console; pass another provider (pino, winston) to keep its formatting.

  • Ingest failures warn through console.warn at most once a minute per kind and never through this logger, so an unreachable Axiom cannot loop.

  • Short-lived processes: await logger.shutdown() (or flush()) before exit. shutdown({ deadlineMs }) bounds the final drain (default 5000 ms; raise it if your host waits longer before killing the process): at the deadline every send still running stops, including one already in flight, and unsent lines are counted as dropped with one warning.

  • Memory is bounded twice: at most maxQueueEvents (10 000) lines and maxQueueBytes (32 MB) wait in memory; past either, NEW lines are dropped (the inner logger still gets them), and a single line larger than maxBatchBytes (1 MB) is never queued.