Jeg stoler mere på mine logs end på min hukommelse

Jeg stoler mere på mine logs end på min hukommelse

De fleste begynder først at gå op i logging den dag noget bryder sammen i produktion. Jeg synes, det er for sent.

Hvis du lærer logging for begyndere ordentligt fra start, slipper du for rigtig meget gætteri senere.

Hvorfor du overhovedet gider logge

Jeg tænker på logs som en slags tidslinje over, hvad din kode faktisk gjorde. Ikke hvad du troede, den gjorde.

Typisk bruger du logs til tre ting:

  • Debugging – hvad skete lige før fejlen?
  • Overblik – hvor tit sker noget, hvor langsomt er det?
  • Audit/sporbarhed – kan jeg forklare, hvorfor noget skete, hvis nogen spørger senere?

Som begynderen (og faktisk også som let øvet) handler det især om de to første. Få styr på, hvad der sker, og hvornår det går galt.

Problemet er bare, at mange enten logger alt for lidt (“der skete en fejl” og intet mere) eller alt for meget (hele request bodies, tokens, passwords, you name it). Begge dele rammer dig senere.

Log levels uden teori-overload

Du har sikkert set ord som debug, info, warn, error. De er bare etiketter på, hvor alvorlig en besked er.

Min helt enkle tommelfingerregel:

  • debug – detaljer du kun gider se, mens du udvikler
  • info – normale ting, der sker: en request, et login, et job der kører
  • warn – noget der ser forkert ud, men ikke vælter appen (endnu)
  • error – noget gik i stykker, der skal du kigge

I frontend bruger du typisk bare console.log, console.warn og console.error. I backend kan du bruge et logger-bibliotek, men idéen er den samme.

Hvis du bare spammer console.log til alt, ender du med et støjhelvede. Prøv at tænke “hvilket niveau er det her egentlig?” hver gang du logger.

Frontend logging – hvad giver mening?

I browseren har du mest console.* til rådighed. Det er fint. Men brug det bevidst.

Det giver mening at logge i frontend

  • Når du sender et API-kald: metode, URL (uden tokens), og hvad du forventer
  • Når et API-kald fejler: statuskode, hvilken operation det var, fejlbesked
  • Ukendte fejl der lander i en catch-blok

Eksempel på et API-kald i frontend:

async function fetchUser(id) {
  console.info('[fetchUser] start', { id });

  try {
    const res = await fetch(`/api/users/${id}`);

    if (!res.ok) {
      console.warn('[fetchUser] non-200 response', {
        id,
        status: res.status,
      });
      return null;
    }

    const data = await res.json();
    console.debug('[fetchUser] success');
    return data;
  } catch (err) {
    console.error('[fetchUser] error', err);
    return null;
  }
}

Bemærk hvad jeg ikke logger: ingen tokens, ingen hele user-objekter, ingen passwords.

Det giver sjældent mening at logge i frontend

  • Alle taste-tryk, klik og input-værdier (lyder overdrevet, men jeg har set det)
  • Store JSON-svar direkte i console.log hver gang
  • Hele fejl-objekter i produktion uden filtrering, hvis de kan indeholde hemmeligheder

Som udgangspunkt: log hvad du prøver at gøre, og hvordan det gik. Log ikke alt indholdet.

Hvis du vil dykke mere ned i fejlfinding i frontend, har jeg også skrevet om at debugge rigtigt i browseren.

Backend logging – request-id er din nye ven

På backend har du en luksus, du ikke har i frontend: du kan styre log-outputtet meget mere, og du kan samle det et sted.

Det første, jeg næsten altid gør i en lille Node/Express app, er at give hver request et request-id. Så kan jeg følge én bruger-forespørgsel igennem alle logs.

Eksempel: simpel Express-server med request-id

import express from 'express';
import { randomUUID } from 'crypto';

const app = express();

// lille middleware der giver hver request et id
app.use((req, res, next) => {
  req.id = randomUUID();
  console.info('request:start', {
    requestId: req.id,
    method: req.method,
    path: req.path,
  });

  const startedAt = Date.now();

  res.on('finish', () => {
    const durationMs = Date.now() - startedAt;
    console.info('request:end', {
      requestId: req.id,
      statusCode: res.statusCode,
      durationMs,
    });
  });

  next();
});

app.get('/api/users/:id', async (req, res) => {
  console.debug('handler:users', { requestId: req.id, userId: req.params.id });
  // ... din logik her
  res.json({ id: req.params.id, name: 'Test' });
});

app.listen(3000, () => {
  console.info('server:listening', { port: 3000 });
});

Her får du:

  • Et requestId, du kan søge efter i logs
  • En start- og slut-log for hver request
  • Response-status og hvor lang tid requesten tog

Det er guld værd, når en bruger siger “siden er langsom”. Så kan du rent faktisk se, om det er sandt.

Fejl-logging uden at lække hemmeligheder

Det mest følsomme ved logging er typisk fejl. Vi har en tendens til at logge alt, når noget går galt.

Den sikre version af fejl-logging

En fornuftig grundstruktur for en error-log kunne være:

  • request-id
  • hvilken del af koden der fejlede (fx userService.createUser)
  • en kort, menneskelig besked
  • fejltype og stacktrace (på backend, ikke vist til bruger)

Eksempel i Express:

app.use((err, req, res, next) => {
  console.error('error', {
    requestId: req.id,
    message: err.message,
    name: err.name,
    stack: err.stack,
  });

  res.status(500).json({
    error: 'Internal server error',
    requestId: req.id,
  });
});

Ingen tokens, ingen passwords, ingen fulde request bodies. Hvis du er i tvivl: log mindre, ikke mere.

På frontend kan du gøre noget lignende i en global error-boundary eller window.onerror, men vær ekstra forsigtig, hvis du sender fejl videre til et eksternt error tracking værktøj.

Strukturerede logs kontra “printf” logs

Du kan logge på to måder:

  • “printf”-stil: fritekst, som du selv formaterer
  • Struktureret: fx JSON-objekter

Eksempel på printf-stil:

console.log(`User ${userId} logged in from ${ip}`);

Eksempel på struktureret log:

console.info('user:login', {
  userId,
  ip,
});

Til små projekter kan du fint starte med printf-stil. Men jeg vil klart anbefale, at du allerede nu vænner dig til at logge noget, der ligner strukturerede logs: en kort “event key” plus et objekt.

Det gør det langt nemmere senere at smide det ind i en log-tjeneste, søge i det og filtrere på felter. Dit fremtidige jeg vil takke dig.

Hvis du er i gang med backend generelt, er det et fint tidspunkt også at læse om fx håndtering af secrets og environment variabler, for logs og secrets hænger skræmmende tæt sammen.

Et lille end-to-end log-mønster for et API-kald

Nu samler vi det. Et simpel mønster fra client til server.

Frontend: log konteksten, ikke alt

async function updateProfile(profile) {
  console.info('[updateProfile] start');

  try {
    const res = await fetch('/api/profile', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(profile),
    });

    if (!res.ok) {
      console.warn('[updateProfile] failed', { status: res.status });
      return { ok: false };
    }

    console.debug('[updateProfile] success');
    return { ok: true };
  } catch (err) {
    console.error('[updateProfile] error', err);
    return { ok: false };
  }
}

Vi logger:

  • at vi starter
  • om det fejler, med status
  • ukendte fejl med console.error

Ingen passwords i logs. Ingen tokens. Ingen hele profile-objekter.

Backend: bind request-id på

app.put('/api/profile', async (req, res, next) => {
  console.info('profile:update:start', { requestId: req.id });

  try {
    // forestil dig at req.body allerede er parsed JSON
    const userId = req.user.id; // fx fra auth-middleware

    await updateUserProfile(userId, req.body);

    console.info('profile:update:success', { requestId: req.id });
    res.sendStatus(204);
  } catch (err) {
    console.error('profile:update:error', {
      requestId: req.id,
      message: err.message,
    });
    next(err);
  }
});

Hvis du nu får en fejl, kan du:

  • tage requestId fra backend-logs
  • matche det med frontend-loggen (fx hvis du også viser det for brugeren eller sender det tilbage)

Det er sådan noget, der får dig til at ligne en der “bare lige” kan finde fejlen.

10 ting jeg aldrig logger (heller ikke for at “debugge hurtigt”)

Her er min personlige “never log” liste. Jeg mener det bogstaveligt.

  1. Passwords – hverken i klartekst eller hash
  2. Tokens – JWT, session tokens, API keys, refresh tokens
  3. Hemmeligheder – database credentials, SMTP-koder, webhook-secrets
  4. CPR-numre – uanset om det “bare er test”
  5. Kreditkortdata – fulde numre, CVC, udløbsdatoer
  6. Hele request bodies – især i auth-routes eller formularer med følsomt indhold
  7. Uploads – filer, billeder, dokumenter
  8. Private beskeder – chat, mails, fritekstfelter
  9. Personligt identificerbare data i store mængder (navn + email + telefon + adresse samlet)
  10. Produktion data på din egen maskine, hvis du dumper det via logs for “lige at kigge”

Hvis du faktisk har behov for at se noget af det til fejlfinding, så gør det kontrolleret: lokal kopi af testdata, admin-tool med adgangsstyring, eller meget kortvarig, midlertidig logging som du fjerner igen. Ikke standard-logging.

Næste skridt når du har styr på dine basale logs

Når du har de her ting nogenlunde på plads:

  • forståelse for log levels
  • bevidst console-brug i frontend
  • request-id og strukturerede logs i backend
  • en mental “never log” liste

så er du faktisk klar til at kigge på mere avancerede værktøjer uden at drukne.

Nogle begreber du vil støde på:

  • Error tracking – værktøjer der samler fejl ét sted (Sentry, Rollbar osv.)
  • Centraliseret logging – log-aggregatorer der kan søge og filtrere i dine logs
  • Metrics – målbare ting som responstid, antal errors per minut

Du behøver ikke vælge noget tungt. Ofte kan noget så simpelt som en hosted error-tracker være nok til små projekter. Fokusér på at få exceptions samlet ét sted, så du ikke er afhængig af tilfældigt at have devtools åbent.

Hvis du er i gang med at bygge små webapps og API’er, giver det også mening at koble logging sammen med de ting, du allerede arbejder med, fx deployment og debug flows. Mange af de ting, jeg skriver om på Coding Class, kan du kombinere med logging-mønstrene her.

Jeg plejer at sige det sådan: hvis du er bange for, at nogen læser dine logs, logger du forkerte ting. Logs skal kunne tåle dagslys. Det er koden, der skal skamme sig, ikke loggen.

Generer et entydigt request-id i frontend og send det som header (fx X-Request-ID) på hvert API-kald. Log det samme id i alle backend-komponenter og i eventuelle downstream-tjenester, så du kan filtrere og samle alle events for den request i din log-aggregator.
Brug struktureret logging og et filtreringslag der redigerer eller masker følsomme felter før skrivning (fx token -> *****, email -> e***@d.dk). Centraliser redaction i et bibliotek eller middleware, test det med eksempler på edge-cases, og undgå ad-hoc console.log i produktionskode.
Brug JSON-logs med faste felter som timestamp, level, service, request_id, operation og event-data i separate felter i stedet for store fritekstbeskeder. Det gør det meget nemmere at lave queries, dashboards og alerts i et logværktøj.
Brug en kombination af niveau-styring (fx info i prod, debug kun ved behov), sampling for højt volumen-events, asynkron/buffered skrivning og retention-politikker der arkiverer eller sletter gamle logs. Overvåg også logging-latency og omkostninger løbende, så du kan trimme unødvendige felter og events.

Sara Vestergaard er selvlært kode-nørd, der stille og roligt er gået fra at rode med en enkelt HTML-side til at bygge små værktøjer, scripts og hjemmesider til sig selv og vennerne. Hun startede med at lave en simpel band-hjemmeside som teenager og opdagede, hvor tilfredsstillende det er, når noget, du har skrevet, pludselig lever på skærmen.

For Sara handler kodning ikke om store ord eller imponerende titler, men om meget konkrete problemer: den kedelige opgave, der tager for lang tid, den ven der mangler en lille porteføljeside, eller den liste, der burde sortere sig selv. Hun elsker at pille ting fra hinanden – også kode – for at se, hvad der egentlig foregår, og hun har brugt utallige aftener på at google fejlbeskeder, teste små eksempler og langsomt bygge sin forståelse op.

På Coding Class deler hun den tilgang videre. Hun skriver til dig, der gerne vil lære at kode ved at gøre det i praksis: små projekter, korte kodebidder og forklaringer, der hænger sammen med det, du faktisk sidder med på skærmen. Hun skærer ind til benet, viser typiske fejl og deres løsninger og giver altid et forslag til, hvordan du kan bygge en tand videre, når grundideen først virker.

Når hun ikke skriver til Coding Class eller nørkler med nye små projekter, hænger Sara på klatrevæggen, vander sine altanplanter eller spiller gamle Nintendo-spil. Men hun ender næsten altid tilbage ved tasterne – for der er altid endnu en lille ting, der kunne være smartere, hurtigere eller bare lidt sjovere at bruge.

Send kommentar

You May Have Missed