Strukturert logging på tvers av Server og Edge
Skriv korrelerte, strukturerte logger som overlever serverless-kaldstarter og Edge-begrensninger.
Strukturert logging på tvers av Server og Edge er en gratis leksjon i Next.js 15 fullstack (App Router + Server Actions) på CoddyKit. Dette er leksjon 3 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Next.js 15 fullstack (App Router + Server Actions), og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Next.js 15 fullstack (App Router + Server Actions) inneholder totalt 4 leksjoner.
Hvorfor strukturert logging er viktig i Next.js 15
Tradisjonell utskrift med console.log gir ustrukturerte strenger. I produksjonsapplikasjoner med Next.js 15 som kjører på tvers av serverless-funksjoner og Edge-runtime-miljøer, er disse strengene nesten ubrukelige: Det finnes ingen kobling mellom forespørsler, ingen maskinlesbare felt, og ved cold starts kan bufret utskrift gå tapt før den tømmes.
Strukturert logging løser dette ved å skrive ut JSON-objekter med konsekvente felt på hver logglinje:
- requestId — knytter hver logg til én HTTP-forespørsel
- timestamp — ISO-8601, alltid UTC
- level —
info/warn/error - service — hvilken rute eller funksjon som skrev loggen
- message — lesbar oppsummering
- context — vilkårlig strukturert nyttelast
Plattformer for observability (Datadog, Axiom, Grafana Loki) tar automatisk inn disse feltene, slik at du på sekunder kan filtrere og koble sammen logger på tvers av tusenvis av samtidige forespørsler.
Det grunnleggende loggergrensesnittet
Før du integrerer med et runtime-miljø, bør du definere et minimalt og portabelt loggergrensesnitt. Dette holder forretningslogikken frakoblet den konkrete loggimplementasjonen og gjør testing svært enkelt.
Opprett lib/logger/types.ts med strukturen som alle loggere må oppfylle:
// lib/logger/types.ts
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export interface LogContext {
requestId?: string;
userId?: string;
route?: string;
durationMs?: number;
[key: string]: unknown;
}
export interface Logger {
debug(message: string, context?: LogContext): void;
info(message: string, context?: LogContext): void;
warn(message: string, context?: LogContext): void;
error(message: string, context?: LogContext & { err?: unknown }): void;
}
export interface LogEntry {
timestamp: string;
level: LogLevel;
service: string;
message: string;
context: LogContext;
}Bygge en JSON-logger for Node.js-runtime-miljøet
Next.js 15 Server Components, Route Handlers og Server Actions kjører som standard i Node.js-runtime-miljøet. Her har du tilgang til process.stdout og kan trygt skrive ut JSON over flere linjer.
Den viktigste regelen er å skrive ut ett JSON-objekt per linje (NDJSON-/JSON Lines-formatet). Loggsamlere deler opp ved linjeskift, så utskrift over flere linjer ødelegger innlesingen.
Legg merke til hvordan err serialiseres manuelt — JSON.stringify utelater i stillhet Error-egenskaper som stack og message.
// lib/logger/node-logger.ts
import type { Logger, LogContext, LogEntry, LogLevel } from './types';
function serializeError(err: unknown): Record<string, unknown> {
if (err instanceof Error) {
return { name: err.name, message: err.message, stack: err.stack };
}
return { raw: String(err) };
}
function createEntry(
level: LogLevel,
service: string,
message: string,
context: LogContext = {}
): LogEntry {
return {
timestamp: new Date().toISOString(),
level,
service,
message,
context,
};
}
export function createNodeLogger(service: string): Logger {
const write = (entry: LogEntry) =>
process.stdout.write(JSON.stringify(entry) + '\n');
return {
debug: (msg, ctx) => write(createEntry('debug', service, msg, ctx)),
info: (msg, ctx) => write(createEntry('info', service, msg, ctx)),
warn: (msg, ctx) => write(createEntry('warn', service, msg, ctx)),
error: (msg, ctx) => {
const { err, ...rest } = ctx ?? {};
write(createEntry('error', service, msg, {
...rest,
...(err !== undefined ? { error: serializeError(err) } : {}),
}));
},
};
}Edge-kompatibel logger: håndtere begrensningene
Edge Runtime (som brukes av Middleware og Route Handlers med export const runtime = 'edge') fjerner de fleste Node.js-API-er. Du kan ikke bruke process.stdout.write eller fs.
Dette kan du bruke:
console.log/console.error— alltid tilgjengeligFetch API— til å videresende logger til et eksternt endepunktcrypto.randomUUID()— til forespørsels-ID-er
Trikset er å fortsatt skrive ut JSON-strenger via console.log. Vercel Edge-logger bufres linje for linje og videresendes til log drain som ren tekst, så ett JSON-objekt per linje er trygt.
// lib/logger/edge-logger.ts
import type { Logger, LogContext, LogEntry, LogLevel } from './types';
function createEntry(
level: LogLevel,
service: string,
message: string,
context: LogContext = {}
): LogEntry {
return {
timestamp: new Date().toISOString(),
level,
service,
message,
context,
};
}
export function createEdgeLogger(service: string): Logger {
const emit = (entry: LogEntry) => {
// console.log is the only safe output channel in Edge Runtime
const line = JSON.stringify(entry);
if (entry.level === 'error') {
console.error(line);
} else {
console.log(line);
}
};
return {
debug: (msg, ctx) => emit(createEntry('debug', service, msg, ctx)),
info: (msg, ctx) => emit(createEntry('info', service, msg, ctx)),
warn: (msg, ctx) => emit(createEntry('warn', service, msg, ctx)),
error: (msg, ctx) => emit(createEntry('error', service, msg, ctx)),
};
}Korrelering av forespørsler med AsyncLocalStorage
Det vanskeligste problemet ved logging i serverless-miljøer er korrelering: å knytte samme requestId til hver logg som skrives under én forespørsel, selv langt inne i hjelpefunksjoner, uten å sende ID-en som parameter overalt.
Node.js AsyncLocalStorage løser dette. Den oppretter et kontekstlager per forespørsel som automatisk videreføres gjennom asynkrone kjeder — await, Promise.then og tidsstyrte funksjoner — uten at du må sende konteksten eksplisitt videre.
Next.js 15 støtter dette innebygd i Node.js-runtime-miljøet. Opprett en lagermodul:
// lib/logger/request-store.ts
import { AsyncLocalStorage } from 'async_hooks';
export interface RequestStore {
requestId: string;
userId?: string;
startTime: number;
}
// One singleton for the process lifetime
export const requestStore = new AsyncLocalStorage<RequestStore>();
export function getRequestContext(): Partial<RequestStore> {
return requestStore.getStore() ?? {};
}
// Wrap any async work in this to bind a store
export function runWithRequestContext<T>(
store: RequestStore,
fn: () => Promise<T>
): Promise<T> {
return requestStore.run(store, fn);
}Kontekstbevisst logger ved hjelp av lageret
Koble nå AsyncLocalStorage-lageret til loggeren. Hver loggkall leser automatisk requestId og userId fra konteksten som kjører — du trenger ikke lenger å sende props gjennom mange komponentnivåer.
Oppdater lib/logger/node-logger.ts slik at det omgivende lageret flettes inn før utskrift:
// lib/logger/context-logger.ts
import { createNodeLogger } from './node-logger';
import { getRequestContext } from './request-store';
import type { Logger, LogContext } from './types';
export function createContextLogger(service: string): Logger {
const base = createNodeLogger(service);
function enrich(ctx: LogContext = {}): LogContext {
const { requestId, userId, startTime } = getRequestContext();
return {
...(requestId ? { requestId } : {}),
...(userId ? { userId } : {}),
...(startTime
? { elapsedMs: Date.now() - startTime }
: {}),
...ctx, // caller can override ambient values if needed
};
}
return {
debug: (msg, ctx) => base.debug(msg, enrich(ctx)),
info: (msg, ctx) => base.info(msg, enrich(ctx)),
warn: (msg, ctx) => base.warn(msg, enrich(ctx)),
error: (msg, ctx) => base.error(msg, enrich(ctx)),
};
}
// Singleton used everywhere in Node.js routes
export const logger = createContextLogger('nextjs-app');Sette inn kontekst i Middleware
Det ideelle stedet å tilordne en requestId er Next.js Middleware — den fanger opp hver forespørsel før rutingen. Du kan generere en ID, legge den til i en forespørselsheader (x-request-id) og sende den videre til både Server Components og API-ruter.
Merk: Middleware kjører i Edge Runtime. Bruk crypto.randomUUID() (ikke pakken uuid) og createEdgeLogger.
// middleware.ts (project root)
import { NextRequest, NextResponse } from 'next/server';
import { createEdgeLogger } from '@/lib/logger/edge-logger';
const log = createEdgeLogger('middleware');
export function middleware(req: NextRequest) {
// Honour an upstream gateway's ID if present
const requestId =
req.headers.get('x-request-id') ?? crypto.randomUUID();
const start = Date.now();
log.info('request started', {
requestId,
method: req.method,
path: req.nextUrl.pathname,
});
const response = NextResponse.next();
// Forward the ID so Route Handlers and Server Actions can read it
response.headers.set('x-request-id', requestId);
log.info('request forwarded', {
requestId,
durationMs: Date.now() - start,
});
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Initialisere AsyncLocalStorage i en Route Handler
I Node.js Route Handlers leser du x-request-id-headeren (satt av Middleware) og initialiserer AsyncLocalStorage-lageret. Alt som kalles innenfor runWithRequestContext — inkludert nestede await-uttrykk og hjelpefunksjoner — arver denne konteksten automatisk.
// app/api/orders/route.ts
import { type NextRequest, NextResponse } from 'next/server';
import { runWithRequestContext } from '@/lib/logger/request-store';
import { logger } from '@/lib/logger/context-logger';
import { fetchOrders } from '@/lib/orders';
export async function GET(req: NextRequest) {
const requestId =
req.headers.get('x-request-id') ?? crypto.randomUUID();
return runWithRequestContext(
{ requestId, startTime: Date.now() },
async () => {
logger.info('fetching orders');
// up arrow automatically includes requestId + elapsedMs
try {
const orders = await fetchOrders();
logger.info('orders fetched', { count: orders.length });
return NextResponse.json(orders);
} catch (err) {
logger.error('failed to fetch orders', { err });
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
}
}
);
}Logging i Server Actions
Server Actions i Next.js 15 kjører på serveren i Node.js-runtime-miljøet, så AsyncLocalStorage fungerer også her. Det finnes imidlertid en viktig detalj: Handlingen kalles av React-gjengiveren, ikke av en vanlig HTTP-handler, så du må selv initialisere lageret øverst i handlingen.
Et vanlig mønster er et omsluttende hjelpeverktøy som initialiserer lageret og gir tilgang til loggeren, slik at hver handling får en ryddig implementasjon:
// lib/actions/with-logging.ts
import { headers } from 'next/headers';
import { runWithRequestContext } from '@/lib/logger/request-store';
import { logger } from '@/lib/logger/context-logger';
type ActionFn<TArgs extends unknown[], TResult> =
(...args: TArgs) => Promise<TResult>;
export function withLogging<TArgs extends unknown[], TResult>(
name: string,
fn: ActionFn<TArgs, TResult>
): ActionFn<TArgs, TResult> {
return async (...args) => {
const headersList = await headers();
const requestId =
headersList.get('x-request-id') ?? crypto.randomUUID();
return runWithRequestContext(
{ requestId, startTime: Date.now() },
async () => {
logger.info('action started: ' + name, { action: name });
try {
const result = await fn(...args);
logger.info('action completed: ' + name);
return result;
} catch (err) {
logger.error('action failed: ' + name, { err });
throw err;
}
}
);
};
}
// Usage in a Server Action:
// export const submitOrder = withLogging('submitOrder', async (data) => { ... });Sampling og loggnivåer i produksjon
I en Next.js-app med mye trafikk er det kostbart å logge hver debug-melding — både når det gjelder beregningstid og kostnader for innsamling. Bruk to teknikker sammen:
- Nivåfiltrering: Kontroller
process.env.LOG_LEVELog hopp over nivåer under terskelen. I produksjon kjører man vanligvis påinfo; debug aktiveres bare under hendelser. - Sampling: For støyende
info-baner (for eksempel helsesjekker) logger du bare en prosentandel av forespørslene for å redusere mengden uten å miste hele signalet.
Her er en frittstående demonstrasjon av kjernelogikken for sampling og nivåfiltrering:
// Standalone demo — runs without any framework
const LEVELS = ['debug', 'info', 'warn', 'error'];
const MIN_LEVEL = process.env['LOG_LEVEL'] || 'info';
function shouldLog(level) {
return LEVELS.indexOf(level) >= LEVELS.indexOf(MIN_LEVEL);
}
function sample(rate) {
// rate = 0.1 means log 10% of the time
return Math.random() < rate;
}
function log(level, message, sampleRate = 1) {
if (!shouldLog(level)) return;
if (!sample(sampleRate)) return;
console.log(JSON.stringify({ level, message, ts: new Date().toISOString() }));
}
// Simulated high-frequency health-check path — logs only ~10%
for (let i = 0; i < 20; i++) {
log('info', '/api/health called', 0.1);
}
// Errors always log regardless of sample rate
log('error', 'Database connection failed');Videresende logger til et eksternt loggmål
Serverless-funksjoner er kortvarige — stdout er bare pålitelig hvis plattformen din fanger opp innholdet (Vercel gjør det, mens AWS Lambda uten ekstra oppsett ikke gjør det). Et log drain / eksternt loggmål (Axiom, Better Stack, Datadog) garanterer varig lagring.
Fra Edge Runtime kan du videresende logger ved hjelp av fetch med waitUntil, slik at HTTP-svaret returneres umiddelbart mens logg-POST-en utføres i bakgrunnen. Fra Node.js Route Handlers bruker du after() (Next.js 15) for den samme ikke-blokkerende garantien.
// lib/logger/axiom-drain.ts
// Edge-compatible log drain using fetch
const AXIOM_DATASET = process.env['AXIOM_DATASET'] ?? '';
const AXIOM_TOKEN = process.env['AXIOM_API_TOKEN'] ?? '';
export async function sendToAxiom(
entries: object[]
): Promise<void> {
if (!AXIOM_DATASET || !AXIOM_TOKEN) return; // skip in dev
await fetch(
'https://api.axiom.co/v1/datasets/' + AXIOM_DATASET + '/ingest',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-ndjson',
Authorization: 'Bearer ' + AXIOM_TOKEN,
},
// NDJSON: one JSON object per line
body: entries.map((e) => JSON.stringify(e)).join('\n'),
}
);
}
// In a Middleware or Edge Route Handler:
// context.waitUntil(sendToAxiom([entry]));
//
// In a Node.js Route Handler (Next.js 15):
// import { after } from 'next/server';
// after(() => sendToAxiom([entry]));Hurtigsjekk: Korrelering på tvers av runtime-miljøer
Du trenger at hver logglinje fra én brukerforespørsel — på tvers av Middleware (Edge), en Route Handler (Node.js) og en nestet Server Action — deler samme requestId. Hvilken fremgangsmåte er riktig?
Oppsummering av leksjonen: Strukturert logging på tvers av server og Edge
I denne leksjonen bygde du en komplett, produksjonsklar pipeline for strukturert logging i Next.js 15:
- Portabelt grensesnitt (
Logger,LogContext) holder forretningslogikken frakoblet loggbackend-en. - Runtime-spesifikke implementasjoner:
createNodeLoggerskriver NDJSON tilprocess.stdout;createEdgeLoggerbrukerconsole.log— den eneste sikre utskriftskanalen i Edge Runtime. - Korrelering via
AsyncLocalStorage: Initialiser et lager én gang (i en Route Handler eller en wrapper for en Server Action), så arver hvert nestedeawaitautomatiskrequestId,userIdog medgått tid. - Middleware som opphav for forespørsels-ID-en: Generer eller respekter en oppstrøms
x-request-id-header i Middleware, videresend den nedover, og initialiser Node.js-lageret når forespørselen ankommer. - Disiplin i produksjon: Filtrer loggnivåer med miljøvariabelen
LOG_LEVEL, bruk sampling på støyende baner, serialiserError-objekter eksplisitt, og videresend logger til et varig eksternt loggmål medwaitUntilellerafter()for å unngå å blokkere svar.
Disse mønstrene gir deg til sammen observerbarhet som er korrelert og maskinlesbar, og som tåler cold starts, samtidige forespørsler og begrensningene i både Node.js- og Edge-runtime-miljøene.
Lær deg TypeScript med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 22
- Leksjoner
- 88
Ofte stilte spørsmål
Er leksjonen «Strukturert logging på tvers av Server og Edge» gratis?
Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Next.js 15 fullstack (App Router + Server Actions), inkludert «Strukturert logging på tvers av Server og Edge», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Next.js 15 fullstack (App Router + Server Actions) inneholder totalt 4 leksjoner.
Hva lærer jeg i «Strukturert logging på tvers av Server og Edge»?
Skriv korrelerte, strukturerte logger som overlever serverless-kaldstarter og Edge-begrensninger. Du øver på Next.js 15 fullstack (App Router + Server Actions) med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med Next.js 15 fullstack (App Router + Server Actions)?
Ingen tidligere erfaring er nødvendig. Next.js 15 fullstack (App Router + Server Actions) på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.
Hvor lang tid tar leksjonen «Strukturert logging på tvers av Server og Edge»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne Next.js 15 fullstack (App Router + Server Actions)-leksjonen?
Ja. Alle Next.js 15 fullstack (App Router + Server Actions)-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- OpenTelemetry-sporing med instrumentation.ts
- Detaljerte error.tsx- og global-error-grenser
- Strukturert logging på tvers av Server og Edge
- Fange opp feil i Server Actions og telemetri