@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-axiomnpm · 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 exitWorks 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.
enabledisfalseand 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. Passdatasetto send logs to a different dataset than your events. -
Two level gates apply in order: the logger CORE's (
LOG_LEVEL, defaultinfo) drops lines before this provider sees them; thenlevel(defaultinfo) 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; anErrorbecomeserror: { name, message, stack }). WithfieldsMode: '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 withparse_json(fields).fieldsMode: 'object'stores one column per key. -
inner: nullsends 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.warnat most once a minute per kind and never through this logger, so an unreachable Axiom cannot loop. -
Short-lived processes:
await logger.shutdown()(orflush()) 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 andmaxQueueBytes(32 MB) wait in memory; past either, NEW lines are dropped (the inner logger still gets them), and a single line larger thanmaxBatchBytes(1 MB) is never queued.