pino
pino copied to clipboard
Per-request logger for nextjs
I'm trying to do something similar to #1936, i.e. provide a global logger that injects a request ID field for log correlation. This request ID is read from a header, which since nextjs 15 is only possible using the async headers function:
export const getRequestId = async () => {
const clientHeaders = await headers();
const requestId = clientHeaders.get('x-request-id');
if (requestId === null) {
throw new Error('Request ID not present');
}
return requestId;
};
I believe the author of #1936 was using an older nextjs version with a synchronous headers.
Using a mixin in pino wouldn't work since that needs to be synchronous, so my current (admittedly hacky) solution is using the hooks option:
export const logger = pino(
{
hooks: {
async logMethod(args, method) {
return method.apply(this.child({ request_id: await getRequestId() }), args);
},
},
},
);
This technically works, however likely for good reason this is also not recommended:
Hook functions must be synchronous functions.
The only "proper" way I see would be to await a new logger every time it's used:
// inside some API handler or server component
const logger = await getRequestLogger();
// [...]
logger.debug(...);
This feels a bit cumbersome given that pino has several extension points that are better suited for this, none of which seem to work for this simple problem. I'm starting to believe it's a con of nextjs itself to not provide some kind of request context in its architecture.
What's the best way to inject request-specific information from nextjs in pino? Is there a way to inject asynchronously returned values while logging?
My personal recommendation is to ask the maintainers of the framework you are using.
Thanks, I added my use case to this discussion: https://github.com/vercel/next.js/discussions/77534
Have you looked into using AsyncLocalStorage? See this article
Have you looked into using
AsyncLocalStorage? See this article
We make use of Nextjs' workUnit, which headers() use under water
import {
throwForMissingRequestStore,
workUnitAsyncStorage,
} from 'next/dist/server/app-render/work-unit-async-storage.external';
// @ts-expect-error
import defaultConfig from 'next-logger/lib/defaultPinoConfig.js';
import type { LoggerOptions } from 'pino';
// Copy of next-logger, can't easily plug into their pino config
export const pinoConfig: LoggerOptions = {
...defaultConfig,
mixin() {
try {
// Reference for the workUnitStore implementation: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/request/headers.ts
const workUnitStore = workUnitAsyncStorage.getStore();
if (!workUnitStore || workUnitStore.type !== 'request') {
throwForMissingRequestStore('logger');
}
return {
url: workUnitStore.url,
referer: workUnitStore.headers.get('referrer'),
};
} catch {
return {};
}
},
};