Skip to content

Logging

Pino is the recommended first-class logger for autotel. Bunyan and Winston also work via OpenTelemetry auto-instrumentation.

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 logs
init({
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;
});
{
"level": 30,
"time": 1700000000000,
"msg": "Creating user",
"name": "Alice",
"trace_id": "a1b2c3d4e5f6",
"span_id": "1234567890ab"
}

Use auto-instrumentation for trace context injection:

instrumentation.ts
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 record
logger.info({ userId: '123' }, 'Processing request');
instrumentation.ts
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 added
logger.info('Processing request');

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.

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.