Logging
Pino is the recommended first-class logger for autotel. Bunyan and Winston also work via OpenTelemetry auto-instrumentation.
Pino (Recommended)
Section titled “Pino (Recommended)”Pino is first-class: pass a single instance to init() for both autotel internal logs and app logs.
import pino from 'pino';import { init, trace, withTracing } from 'autotel';
const logger = pino({ name: 'my-app', level: 'info', transport: { target: 'pino-pretty', options: { colorize: true }, },});
// One logger for autotel + app logsinit({ service: 'my-app', logger, endpoint: process.env.OTLP_ENDPOINT,});
const createUser = withTracing({})((ctx) => async (name: string) => { logger.info({ name }, 'Creating user'); // trace_id, span_id automatically injected into log output const user = await db.users.create({ name }); logger.info({ userId: user.id }, 'User created'); return user;});Log Output
Section titled “Log Output”{ "level": 30, "time": 1700000000000, "msg": "Creating user", "name": "Alice", "trace_id": "a1b2c3d4e5f6", "span_id": "1234567890ab"}Bunyan
Section titled “Bunyan”Use auto-instrumentation for trace context injection:
import { init } from 'autotel';
init({ service: 'my-app', autoInstrumentations: ['bunyan'],});import bunyan from 'bunyan';
const logger = bunyan.createLogger({ name: 'my-app' });
// trace_id, span_id automatically added to every log recordlogger.info({ userId: '123' }, 'Processing request');Winston
Section titled “Winston”import { init } from 'autotel';
init({ service: 'my-app', autoInstrumentations: ['winston'],});import winston from 'winston';
const logger = winston.createLogger({ transports: [new winston.transports.Console()],});
// trace_id, span_id automatically addedlogger.info('Processing request');Request Logger vs Pino
Section titled “Request Logger vs Pino”They serve different purposes:
| Feature | getRequestLogger() |
Pino/Bunyan/Winston |
|---|---|---|
| Purpose | One wide event per request | Continuous structured logging |
| Output | Attributes on the span | Log stream (stdout/files) |
| When | .emitNow() at end of request |
Throughout request |
| Use case | Canonical log lines / wide events | Debugging, audit trails |
Use both together: Pino for continuous logging + getRequestLogger() for the one snapshot at the end of each request.
Snapshot severity
Section titled “Snapshot severity”The request snapshot carries an autotel.log.level attribute. Calling
.warn() sets it to warn and .error() sets it to error, so most requests
never need you to touch it.
Two cases need you to say it out loud. A request that degraded without logging anything, and a request that logged a warning you do not want colouring the whole snapshot:
const log = getRequestLogger(ctx);
const inventory = await checkStock(sku);if (inventory.stale) { log.setLevel('warn'); // served from a stale cache, nothing to log}setLevel() takes 'debug' | 'info' | 'warn' | 'error', adds no log event, and
records no exception. An explicit level wins: a later .warn() or .error()
leaves your choice alone rather than overwriting it. Calls after .emitNow()
are ignored, because the snapshot has already left.
Use it to make severity searchable when the interesting thing is the outcome rather than a message.
Examples
Section titled “Examples”example-pino: Pino as first-class logger withinit({ logger })and trace context injection.example-bunyan: Bunyan with auto-instrumentation.example-winston: Winston with auto-instrumentation.