---
title: "Structured logging met een logformaat en logniveaus die je per veld doorzoekt"
description: "Structured logging zet elke waarde in een eigen veld in plaats van in een zin. We stuurden 1 order op 3 manieren naar LogBrew en laten zien welk logformaat je terugvindt via order_id."
canonical: "https://logbrew.co/nl/guides/structured-logging"
last_updated: "2026-11-10"
---

# Structured logging met een logformaat en logniveaus die je per veld doorzoekt

Structured logging zet elke waarde in een eigen veld in plaats van in een zin. We stuurden 1 order op 3 manieren naar LogBrew en laten zien welk logformaat je terugvindt via `order_id`.

Getest op 10 november 2026 met Python 3.13.5 en logbrew-sdk 0.1.29

Door [Furkan Erday](https://github.com/furkanerday). Furkan Erday is de oprichter van LogBrew. Hij bouwt LogBrew elke dag met Claude Code en Codex.

Canonical: https://logbrew.co/nl/guides/structured-logging

## Welke logregel je terugvindt via `order_id`

| Waar het id stond | Zoeken op tekst | Filter op veld |
| --- | --- | --- |
| In de berichttekst | Gevonden | Niet gevonden |
| In `extra={}`-velden | Niet gevonden | Gevonden |
| In een dict binnen `extra={}` | Niet gevonden | Niet gevonden |

We stuurden 1 order op 3 manieren via de Python-handler en zochten hem daarna op. Een volle mok betekent dat de zoekopdracht de regel vond.

## Kies een logformaat waarop je kunt filteren

### Houd het bericht vast, zet de waarden in velden

Een veld houdt zijn naam en zijn type. Een waarde in de berichttekst is alleen een substring. Geef in Python de velden mee in `extra={}`, dan blijft het bericht voor elke order hetzelfde.

```bash
log.info("order paid", extra={"order_id": "ord_42", "amount_cents": 1999})
```

### Schrijf JSON-regels als een programma de log leest

Een subklasse van `Formatter` maakt van elk record 1 JSON-object per regel, met de standaardvelden en elke `extra`-waarde. Een logcollector parseert de regel zonder regex.

```bash
import json
import logging

RECORD_FIELDS = set(vars(logging.LogRecord("", 0, "", 0, "", (), None))) | {"message", "asctime"}


class JsonFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        line = {
            "time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        line.update({k: v for k, v in vars(record).items() if k not in RECORD_FIELDS})
        if record.exc_info:
            line["exception"] = self.formatException(record.exc_info)
        return json.dumps(line)


handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])
log = logging.getLogger("taproom")

log.info("order paid", extra={"order_id": "ord_42", "amount_cents": 1999})
log.warning("keg below 10%", extra={"keg": "ipa", "percent": 8})
```

Uitvoer

```text
{"time": "2026-11-10T08:34:04+0300", "level": "INFO", "logger": "taproom", "message": "order paid", "order_id": "ord_42", "amount_cents": 1999}
{"time": "2026-11-10T08:34:04+0300", "level": "WARNING", "logger": "taproom", "message": "keg below 10%", "keg": "ipa", "percent": 8}
```

### Stuur de velden naar LogBrew

`LogBrewLoggingHandler` stuurt elk record met zijn `extra`-waarden als velden. We stuurden dezelfde order op 3 manieren, in de berichttekst, als `extra`-velden en als een dict binnen `extra`.

```bash
import logging
import os

from logbrew_sdk import HttpTransport, LogBrewClient, LogBrewLoggingHandler

client = LogBrewClient.create(api_key=os.environ["LOGBREW_SERVER_API_KEY"])
transport = HttpTransport()
log = logging.getLogger("taproom")
log.addHandler(LogBrewLoggingHandler(client, transport, metadata={"service": "taproom"}))
log.setLevel(logging.INFO)

logging.getLogger("taproom.text").info("order %s paid, %d cents", "ord_42", 1999)
logging.getLogger("taproom.fields").info(
    "order paid", extra={"order_id": "ord_42", "amount_cents": 1999}
)
logging.getLogger("taproom.nested").info(
    "order paid", extra={"order": {"id": "ord_42", "amount_cents": 1999}}
)

response = client.flush(transport)
print("HTTP", response.status_code, "accepted", response.accepted_events, "logs")
```

Uitvoer

```text
HTTP 200 accepted 3 logs
```

### Vind de regel terug per veld

`GET /api/logs` neemt `attribute_filters` met een veldpad, een type, een operator en een waarde. Het filter op `order_id` vond alleen de regel die met `extra` verstuurd was. De dict binnen `extra` viel weg, omdat de handler alleen strings, getallen, booleans en `None` bewaart.

```bash
curl -sS -G "https://api.logbrew.co/api/logs" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$LOGBREW_PROJECT" \
  --data-urlencode "since=2026-11-10T05:31:33Z" \
  --data-urlencode 'attribute_filters=[{"path":"/metadata/order_id","type":"string","operator":"eq","value":"ord_42"}]' \
  | jq -c '.[] | {message, order_id: .attributes.metadata.order_id}'
```

Uitvoer

```text
{"message":"order paid","order_id":"ord_42"}
```

## Gebruik logniveaus in elke service op dezelfde manier

### Gebruik 4 niveaus in productie

`info` voor wat er gebeurde, `warning` voor wat misging maar afgehandeld werd, `error` voor een mislukte request of job en `critical` voor wat de service stopt. Houd `debug` voor lokale runs. De LogBrew-handler stuurt DEBUG-records als `info`.

### Stel het niveau 1 keer in, bij het opstarten

Lees het niveau uit de omgeving, zodat 1 host op DEBUG kan draaien terwijl de rest op INFO blijft. In Python doet `logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))` dat.

### Log getallen als getallen

`amount_cents: 1999` houdt zijn type, dus het filter `amount_cents >= 1000` vindt het. We stuurden ook `amount_cents: "1999"` als tekst, en het getalfilter sloeg die regel over.

```bash
curl -sS -G "https://api.logbrew.co/api/logs" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$LOGBREW_PROJECT" \
  --data-urlencode "since=2026-11-10T05:31:33Z" \
  --data-urlencode 'attribute_filters=[{"path":"/metadata/amount_cents","type":"number","operator":"gte","value":1000}]' \
  | jq -c '.[] | {message, amount_cents: .attributes.metadata.amount_cents}'
```

Uitvoer

```text
{"message":"order paid","amount_cents":1999}
```

## Wat we zagen in onze test

We stuurden 1 order op 3 manieren via de Python-handler en zochten hem daarna op met de LogBrew API. De tekstzoekopdracht `search=ord_42` vond 1 van de 3 regels, die met het id in het bericht.

Het filter `order_id = ord_42` vond 1 regel, die verstuurd was met `extra={"order_id": "ord_42"}`. Het filter `amount_cents >= 1000` vond dezelfde regel, omdat de handler 1999 als getal bewaarde.

De regel die verstuurd was met `extra={"order": {...}}` vond niets. De handler stuurt strings, getallen, booleans en `None` uit `extra` en slaat een dict of een list over.

```bash
curl -sS -G "https://api.logbrew.co/api/logs" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$LOGBREW_PROJECT" \
  --data-urlencode "since=2026-11-10T05:31:33Z" \
  --data-urlencode "search=ord_42" \
  | jq -c '.[] | {message, order_id: .attributes.metadata.order_id}'
```

Uitvoer

```text
{"message":"order ord_42 paid, 1999 cents","order_id":null}
```

## Platte tekst vs JSON als logformaat

Een plat `%(message)s`-formaat laat elke `extra`-waarde weg. De regel hieronder eindigt op `INFO order paid` zonder order-id, dus niemand vindt de order later terug.

Een JSON-regel houdt de velden en hun types, en een logcollector parseert hem zonder regex. Een mens leest hem minder makkelijk, dus houd het bericht kort en de veldnamen stabiel.

Python heeft geen ingebouwde JSON-formatter. python/cpython#77691 vroeg er in 2018 om en werd gesloten, omdat de documentatie al laat zien hoe je er een schrijft. De `Formatter` van 15 regels hierboven is dat recept.

```bash
import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("taproom")

log.info("order paid", extra={"order_id": "ord_42", "amount_cents": 1999})
```

Uitvoer

```text
2026-11-10 08:34:04,955 INFO order paid
```

## Wat LogBrew vastlegt

Elke log houdt zijn niveau, loggernaam en elk primitief veld uit `extra`, plus de velden in `metadata={}` op de handler. De API filtert op veldpad, type en operator. Tekst neemt `eq`, `ne`, `contains` en `starts_with`, en getallen nemen `gt`, `gte`, `lt` en `lte`.

LogBrew indexeert geen veld waarvan de naam `password`, `secret`, `token`, `cookie` of `authorization` bevat, dus een filter vindt het nooit. Houd geheimen sowieso uit logregels.

Een log die binnen een trace verstuurd is draagt de trace- en span-id's, dus een filter op `order_id` leidt naar het request dat de regel maakte.

## Veelgestelde vragen

### Wat is structured logging?

Loggen waarbij elke waarde een benoemd veld met een type is, als JSON of als velden op een logrecord, in plaats van woorden in een zin. Een zoektool filtert dan op het veld, niet op substring.

### Wat zijn de logniveaus op volgorde?

DEBUG, INFO, WARNING, ERROR, CRITICAL, met de nummers 10 tot 50 in Python. Een logger op INFO laat INFO en hoger door en laat DEBUG weg.

### Moet ik loggen in JSON of in platte tekst?

JSON als een programma de regels leest, zoals een collector, een bestand of een zoektool. Platte tekst als alleen een mens de terminal leest. Houd in beide dezelfde velden.

### Hoe voeg ik velden toe aan een Python-logregel?

Geef `extra={"order_id": "ord_42"}` mee aan de logaanroep. De standaardformatter negeert de extra velden. Een JSON-formatter of de LogBrew-handler bewaart ze.

### Kan ik in LogBrew logs doorzoeken per veld?

Ja. `GET /api/logs` neemt tot 4 `attribute_filters`, elk met een veldpad, een type en een operator zoals `eq` of `gte`.

## Filter je logs op elk veld

LogBrew bewaart elk veld dat je logt en vindt de regel terug per veld, naast de trace en het issue waar hij bij hoort. Begin met het ruime gratis abonnement, zonder creditcard.

[Gratis beginnen](https://logbrew.co/nl)

## Bronnen

- [Python docs, Logging Cookbook](https://docs.python.org/3/howto/logging-cookbook.html)
- [Python docs, LogRecord attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes)
- [python/cpython#77691](https://github.com/python/cpython/issues/77691)

## Meer handleidingen voor agents en Python

- [Een MCP-server toevoegen aan Claude Code en Codex](https://logbrew.co/nl/guides/mcp-server-claude-code-codex)
- [AGENTS.md gebruiken in Claude Code en Codex](https://logbrew.co/nl/guides/agents-md)
- [Claude overloaded error 529 en rate limit error 429 oplossen](https://logbrew.co/nl/guides/claude-overloaded-rate-limit-error)
- [Python-logging instellen met de juiste logniveaus en exceptions met traceback](https://logbrew.co/nl/guides/python-logging)
- [Fouten oplossen in Claude Code, Codex, JavaScript en Python](https://logbrew.co/nl/guides)
