Bootcamp backendontwikkeling met Node.js · Les

Gestructureerd loggen met correlatie-ID's

Produceer machineleesbare JSON-logs en geef correlatie-ID's via async context door voor requesttracing.

Les 1 van 413 stappen

Gestructureerd loggen met correlatie-ID's is een gratis Bootcamp backendontwikkeling met Node.js-les op CoddyKit. Dit is les 1 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Bootcamp backendontwikkeling met Node.js. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Bootcamp backendontwikkeling met Node.js bevat in totaal 4 lessen.

Waarom gestructureerde logboekregistratie

In productie worden logboeken eerst door machines gelezen en pas daarna door mensen. Een regel als console.log('User ' + id + ' failed login') is een vrije tekst: om die later te vinden moet je kwetsbare reguliere expressies schrijven en kun je niet betrouwbaar groeperen of filteren.

Gestructureerde logboekregistratie legt elk logboekrecord vast als een JSON-object met consistente velden. Logboekaggregators (Loki, Elasticsearch, Datadog, CloudWatch) indexeren die velden, zodat je onmiddellijk kunt zoeken op level="error" AND userId=42.

  • Vrije tekst: eenvoudig te schrijven, lastig om in te zoeken.
  • Gestructureerd: één JSON-object per regel (NDJSON), direct te parseren.

Een minimale JSON-logboekschrijver

Het kernidee is eenvoudig: maak een gewoon object, voorzie het van een niveau en tijdstip en schrijf per regel één JSON-tekenreeks naar stdout. De runtime of container verzamelt stdout en stuurt die door naar je aggregator.

Elk logboekrecord is één regel met geldige JSON. Dit formaat heet NDJSON (JSON gescheiden door nieuwe regels) en elke moderne backend voor logboeken begrijpt het.

function log(level, message, fields = {}) {
  const entry = {
    level,
    time: new Date().toISOString(),
    message,
    ...fields,
  };
  process.stdout.write(JSON.stringify(entry) + '\n');
}

log('info', 'server started', { port: 3000 });
log('error', 'login failed', { userId: 42, reason: 'bad_password' });

Gebruik een echte logboekschrijver: pino

Zelf bouwen werkt om te leren, maar toepassingen in productie gebruiken een beproefde logboekschrijver. pino is de feitelijke standaard voor Node.js: het programma is extreem snel omdat het JSON op een voor workers geschikte manier serialiseert en asynchroon schrijft.

  • logger.info(obj, msg) — het eerste argument is het object met velden, het tweede is de berichttekst.
  • Niveaus: trace, debug, info, warn, error, fatal.
  • In productie stuur je stdout alleen in development door naar pino-pretty; onbewerkte JSON gaat naar productie.
const pino = require('pino');
const logger = pino({ level: 'info' });

logger.info({ port: 3000 }, 'server started');
logger.error({ userId: 42, reason: 'bad_password' }, 'login failed');

// Child loggers bind fields onto every subsequent log:
const reqLog = logger.child({ requestId: 'abc-123' });
reqLog.info('handling request');

Het probleem: één aanvraag traceren

Bij belasting lopen de logboekregels van honderden aanvragen door elkaar. Wanneer gebruiker 42 een fout meldt, moet je elke logboekregel zien die bij diens aanvraag hoort — over middlewarelagen, services en databaseaanroepen heen.

De oplossing is een correlatie-id (ook wel aanvraag-id of trace-id genoemd): een unieke identificatie die één keer per aanvraag wordt gegenereerd en wordt toegevoegd aan elk logboekrecord dat tijdens de afhandeling van die aanvraag wordt geschreven.

  • Zoek op correlationId="7f3a..." en de aggregator reconstrueert de volledige tijdlijn van de aanvraag.
  • Als de id binnenkomt in een inkomende header, kun je logboekregels over services heen correleren.

De id genereren en accepteren

Lees aan de rand van je systeem een binnenkomende correlatieheader als een aanroeper (gateway, upstreamservice) er al een heeft ingesteld; genereer anders een nieuwe UUID. Stuur deze altijd terug in de response, zodat clients en proxy's hem ook kunnen vastleggen.

Standaardnamen voor headers zijn x-request-id of x-correlation-id. Door een binnenkomende ID opnieuw te gebruiken, werkt tracing over servicegrenzen heen.

const { randomUUID } = require('crypto');

function correlationMiddleware(req, res, next) {
  const incoming = req.headers['x-correlation-id'];
  const correlationId = incoming || randomUUID();
  req.correlationId = correlationId;
  res.setHeader('x-correlation-id', correlationId);
  next();
}

module.exports = { correlationMiddleware };

De naïeve aanpak en de problemen ervan

De voor de hand liggende aanpak is om req.correlationId als argument door te geven aan elke functie en elke logaanroep. Dit werkt, maar schaalt niet: een diepe aanroepstack (controller → service → repository → helper) dwingt je om de ID door te geven aan functies die er verder geen reden voor hebben om ervan te weten.

Dit is prop drilling voor de backend. Je wilt dat de ID impliciet beschikbaar is voor alle code die tijdens de request wordt uitgevoerd, zonder de signatuur van elke functie te wijzigen.

AsyncLocalStorage schiet te hulp

Node.js levert AsyncLocalStorage mee (in de module async_hooks). Hiermee maak je een opslag die gebonden blijft aan de huidige asynchrone uitvoeringscontext — ook over await, callbacks, timers en promises heen — zonder gedeeld te worden tussen gelijktijdige requests.

Je kunt het zien als thread-lokale opslag met request-scope. Je roept eenmaal per request als.run(store, callback) aan; overal binnen die callback (hoe diep ook en ongeacht hoeveel awaits later) retourneert als.getStore() dezelfde opslag.

const { AsyncLocalStorage } = require('async_hooks');
const als = new AsyncLocalStorage();

async function deep() {
  await new Promise((r) => setTimeout(r, 10));
  // Same store, even after awaits and timers:
  return als.getStore().correlationId;
}

async function main() {
  await als.run({ correlationId: 'req-1' }, async () => {
    console.log('inside:', await deep());
  });
  console.log('outside:', als.getStore());
}

main();

AsyncLocalStorage aan de request koppelen

Vervang de naïeve middleware: voeg de ID niet toe aan req, maar voer de rest van de request uit binnen als.run(), met de ID in de opslag. Nu kan elke coderegel die voor deze request wordt uitgevoerd — controllers, services, databasecallbacks — de ID ophalen via getStore().

Belangrijk: je moet next() binnen de callback van run aanroepen, zodat de handlers verderop de context erven.

const { AsyncLocalStorage } = require('async_hooks');
const { randomUUID } = require('crypto');

const als = new AsyncLocalStorage();

function context() {
  return als.getStore() || {};
}

function correlationMiddleware(req, res, next) {
  const correlationId = req.headers['x-correlation-id'] || randomUUID();
  res.setHeader('x-correlation-id', correlationId);
  als.run({ correlationId }, () => next());
}

module.exports = { als, context, correlationMiddleware };

De ID automatisch aan elke log toevoegen

Het voordeel: wikkel je logger in zodat deze de correlatie-ID automatisch uit AsyncLocalStorage leest. Applicatiecode roept log.info('saved order') aan zonder ID-argument, terwijl elke uitgegeven regel de juiste correlationId bevat.

Met pino doe je dit declaratief via de optie mixin, die tijdens het schrijven extra velden samenvoegt met elk logrecord.

const pino = require('pino');
const { als } = require('./context');

const logger = pino({
  level: 'info',
  mixin() {
    const store = als.getStore();
    return store ? { correlationId: store.correlationId } : {};
  },
});

// Anywhere deep in the request, no ID passed explicitly:
function saveOrder(order) {
  logger.info({ orderId: order.id }, 'order saved');
}

module.exports = { logger, saveOrder };

De ID doorgeven aan aanroepen verderop

Correlatie strekt zich alleen over services uit als je de ID doorstuurt bij uitgaande requests. Wanneer je service een andere HTTP-API aanroept, lees je de ID uit de context en stel je deze in als header. De middleware van de downstreamservice gebruikt hem dan opnieuw, zodat de logs van beide services dezelfde ID bevatten.

Hetzelfde principe geldt voor message queues (zet de ID in de metadata van het bericht) en achtergrondtaken (sla de ID op in de payload van de taak).

const { context } = require('./context');

async function callInventoryService(sku) {
  const { correlationId } = context();
  const res = await fetch('https://inventory.internal/check', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-correlation-id': correlationId,
    },
    body: JSON.stringify({ sku }),
  });
  return res.json();
}

module.exports = { callInventoryService };

Hygiëne voor productie

Met een paar regels houd je je gestructureerde logs schoon en veilig:

  • Log nooit geheimen of persoonlijk identificeerbare informatie — wachtwoorden, tokens, volledige kaartnummers. Configureer paden voor redact in pino (bijvoorbeeld ['req.headers.authorization', '*.password']).
  • Log op het juiste niveau — info voor bedrijfsgebeurtenissen, warn voor herstelbare problemen, error met het geserialiseerde errorobject voor fouten.
  • Houd veldnamen stabiel — correlationId betekent altijd hetzelfde; dashboards en waarschuwingen zijn ervan afhankelijk.
  • Eén JSON-object per regel — maak in productie geen pretty-print; daardoor werkt het parsen van NDJSON niet meer.
const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  redact: ['req.headers.authorization', 'password', '*.password', 'creditCard'],
});

logger.info({ user: { id: 7, password: 'hunter2' } }, 'login');
// -> password is replaced with [Redacted] in the output

Korte controle

Je hebt de correlatie-ID tijdens een request nodig in elke functie van een diepe aanroepstack, zonder deze als argument door te geven en zonder dat de ID tussen gelijktijdige requests uitlekt. Wat is het juiste mechanisme in Node.js?

Samenvatting

Je hebt geleerd hoe je logs zowel door machines kunt laten parsen als traceerbaar kunt maken:

  • Gestructureerde logging schrijft één JSON-object per regel (NDJSON), zodat aggregators velden kunnen indexeren en doorzoeken.
  • pino is de standaard snelle logger voor Node.js; het veldobject komt eerst en het bericht daarna.
  • Een correlatie-ID wordt eenmaal per request gegenereerd of opnieuw gebruikt uit een binnenkomende header en teruggestuurd in de response.
  • AsyncLocalStorage geeft die ID door de hele asynchrone aanroepstack heen zonder functiesignaturen te wijzigen en isoleert gelijktijdige requests.
  • De mixin van pino voegt de ID automatisch toe aan elke log; door deze als uitgaande header door te sturen, breid je tracing uit over services heen.
  • Pas goede hygiëne toe: maskeer geheimen met redact, gebruik de juiste niveaus, houd veldnamen stabiel en maak in productie nooit pretty-print.
Gratis beginnen

Leer JavaScript met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
22
Lessen
92

Veelgestelde vragen

Is de les “Gestructureerd loggen met correlatie-ID's” gratis?

Ja — de volledige tekst van “Gestructureerd loggen met correlatie-ID's” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Bootcamp backendontwikkeling met Node.js wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Bootcamp backendontwikkeling met Node.js bevat in totaal 4 lessen.

Wat leer ik in “Gestructureerd loggen met correlatie-ID's”?

Produceer machineleesbare JSON-logs en geef correlatie-ID's via async context door voor requesttracing. Je oefent met Bootcamp backendontwikkeling met Node.js door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Bootcamp backendontwikkeling met Node.js te beginnen?

Ervaring vooraf is niet nodig. Bootcamp backendontwikkeling met Node.js op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 1 van 4.

Hoe lang duurt de les “Gestructureerd loggen met correlatie-ID's”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Bootcamp backendontwikkeling met Node.js?

Ja. Elke les over Bootcamp backendontwikkeling met Node.js bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Gestructureerd loggen met correlatie-ID's
  2. Distributed tracing met OpenTelemetry-spans
  3. Applicatiemetrics beschikbaar maken en de RED-methode
  4. Contextpropagatie met AsyncLocalStorage
← Terug naar Bootcamp backendontwikkeling met Node.js