Logging (pino)
Structured logging is the difference between observable and opaque - and the diff in incident response time is measured in hours.
Node logging with pino
EXAMPLE
// npm install pino pino-http pino-pretty
// logger.js
import pino from 'pino';
const isDev = process.env.NODE_ENV !== 'production';
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
redact: {
paths: ['req.headers.authorization', 'req.headers.cookie', '*.password', '*.token'],
censor: '[REDACTED]',
},
transport: isDev
? { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss', colorize: true } }
: undefined, // JSON to stdout in prod
});
// server.js
import express from 'express';
import pinoHttp from 'pino-http';
import { logger } from './logger.js';
import { randomUUID } from 'crypto';
const app = express();
app.use(pinoHttp({
logger,
genReqId: (req) => req.headers['x-request-id'] || randomUUID(),
customLogLevel: (req, res, err) => {
if (res.statusCode >= 500 || err) return 'error';
if (res.statusCode >= 400) return 'warn';
return 'info';
},
serializers: {
req: (req) => ({ method: req.method, url: req.url, id: req.id }),
res: (res) => ({ status: res.statusCode }),
},
}));
app.get('/users/:id', async (req, res) => {
req.log.info({ userId: req.params.id }, 'looking up user');
const user = await db.users.findById(req.params.id);
if (!user) {
req.log.warn({ userId: req.params.id }, 'user not found');
return res.status(404).send();
}
res.json(user);
});
// child loggers carry context
async function chargeOrder(order) {
const log = logger.child({ orderId: order.id, customerId: order.customerId });
log.info('charging');
try {
await stripe.charges.create({ amount: order.total });
log.info({ amount: order.total }, 'charged');
} catch (err) {
log.error({ err }, 'charge failed');
throw err;
}
}
app.listen(3000, () => logger.info('listening on 3000'));
Why it matters
Log JSON to stdout. Let your platform (Datadog, Loki, CloudWatch) parse and index it. Always carry a request ID so you can trace a request across services. Always redact secrets, even in dev - habit makes leaks less likely.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import pino from 'pino';
const log = pino({ level: 'info' });
log.info({ user: 'ada' }, 'login');
Try it Yourself »
Discussion
Loading…