---
title: "Logging strutturato con un formato dei log e livelli che puoi cercare per campo"
description: "Il logging strutturato mette ogni valore in un campo a sé invece che dentro una frase. Abbiamo inviato 1 ordine in 3 modi a LogBrew e mostriamo quale formato di log ritrovi tramite order_id."
canonical: "https://logbrew.co/it/guides/structured-logging"
last_updated: "2026-11-10"
---

# Logging strutturato con un formato dei log e livelli che puoi cercare per campo

Il logging strutturato mette ogni valore in un campo a sé invece che dentro una frase. Abbiamo inviato 1 ordine in 3 modi a LogBrew e mostriamo quale formato di log ritrovi tramite `order_id`.

Testato il 10 novembre 2026 con Python 3.13.5 e logbrew-sdk 0.1.29

Di [Furkan Erday](https://github.com/furkanerday). Furkan Erday è il fondatore di LogBrew. Sviluppa LogBrew ogni giorno con Claude Code e Codex.

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

## Quale riga di log ritrovi tramite `order_id`

| Dove è finito l’id | Ricerca testuale | Filtro per campo |
| --- | --- | --- |
| Dentro il testo del messaggio | Trovata | Non trovata |
| Nei campi `extra={}` | Non trovata | Trovata |
| In un dict dentro `extra={}` | Non trovata | Non trovata |

Abbiamo inviato 1 ordine in 3 modi attraverso l’handler Python, poi l’abbiamo cercato. Una tazza piena significa che la ricerca ha trovato la riga.

## Scegli un formato di log che puoi filtrare

### Tieni fisso il messaggio, metti i valori nei campi

Un campo conserva il suo nome e il suo tipo. Un valore dentro il testo del messaggio è solo una sottostringa. In Python, passa i campi in `extra={}`, e il messaggio resta uguale per ogni ordine.

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

### Scrivi righe JSON quando a leggere il log è un programma

Una sottoclasse di `Formatter` trasforma ogni record in 1 oggetto JSON per riga, con i campi standard e ogni valore `extra`. Un collector di log analizza la riga senza 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})
```

Output

```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}
```

### Invia i campi a LogBrew

`LogBrewLoggingHandler` invia ogni record con i suoi valori `extra` come campi. Abbiamo inviato lo stesso ordine in 3 modi, dentro il testo del messaggio, come campi `extra` e come dict dentro `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")
```

Output

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

### Ritrova la riga per campo

`GET /api/logs` accetta `attribute_filters` con un percorso di campo, un tipo, un operatore e un valore. Il filtro su `order_id` ha trovato solo la riga inviata con `extra`. Il dict dentro `extra` è stato scartato, perché l’handler conserva solo stringhe, numeri, booleani e `None`.

```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}'
```

Output

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

## Usa i livelli di log allo stesso modo in ogni servizio

### Usa 4 livelli in produzione

`info` per cosa è successo, `warning` per cosa è andato storto ma è stato gestito, `error` per una richiesta o un job fallito e `critical` per cosa ferma il servizio. Tieni `debug` per le esecuzioni locali. L’handler di LogBrew invia i record DEBUG come `info`.

### Imposta il livello una volta, all’avvio

Leggi il livello dall’ambiente, così 1 host può girare a DEBUG mentre gli altri restano a INFO. In Python lo fa `logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))`.

### Registra i numeri come numeri

`amount_cents: 1999` conserva il suo tipo, quindi il filtro `amount_cents >= 1000` lo trova. Abbiamo inviato anche `amount_cents: "1999"` come testo, e il filtro numerico ha saltato quella riga.

```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}'
```

Output

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

## Cosa abbiamo visto nei test

Abbiamo inviato 1 ordine in 3 modi attraverso l’handler Python, poi l’abbiamo cercato con l’API di LogBrew. La ricerca testuale `search=ord_42` ha trovato 1 delle 3 righe, quella con l’id dentro il messaggio.

Il filtro `order_id = ord_42` ha trovato 1 riga, quella inviata con `extra={"order_id": "ord_42"}`. Il filtro `amount_cents >= 1000` ha trovato la stessa riga, perché l’handler ha conservato 1999 come numero.

La riga inviata con `extra={"order": {...}}` non ha trovato nulla. L’handler invia stringhe, numeri, booleani e `None` da `extra` e salta un dict o una lista.

```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}'
```

Output

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

## Testo semplice o JSON come formato dei log

Un formato `%(message)s` semplice scarta ogni valore `extra`. La riga qui sotto finisce con `INFO order paid` senza id dell’ordine, quindi nessuno ritrova l’ordine dopo.

Una riga JSON conserva i campi e i loro tipi, e un collector di log la analizza senza regex. Una persona la legge meno facilmente, quindi tieni il messaggio corto e i nomi dei campi stabili.

Python non ha un formatter JSON integrato. python/cpython#77691 lo ha chiesto nel 2018 ed è stato chiuso, perché la documentazione mostra già come scriverne uno. Il `Formatter` di 15 righe qui sopra è quella ricetta.

```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})
```

Output

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

## Cosa registra LogBrew

Ogni log conserva livello, nome del logger e ogni campo primitivo di `extra`, più i campi in `metadata={}` sull’handler. L’API filtra per percorso di campo, tipo e operatore. Il testo accetta `eq`, `ne`, `contains` e `starts_with`, i numeri accettano `gt`, `gte`, `lt` e `lte`.

LogBrew non indicizza un campo il cui nome contiene `password`, `secret`, `token`, `cookie` o `authorization`, quindi un filtro non lo trova mai. Tieni comunque i segreti fuori dalle righe di log.

Un log inviato dentro una traccia porta gli id di traccia e di span, quindi un filtro per `order_id` porta alla richiesta che ha prodotto la riga.

## Domande frequenti

### Cos’è il logging strutturato?

Un logging in cui ogni valore è un campo con nome e tipo, come JSON o come campi su un record di log, invece di parole dentro una frase. Uno strumento di ricerca filtra poi per campo, non per sottostringa.

### In che ordine vanno i livelli di log?

DEBUG, INFO, WARNING, ERROR, CRITICAL, con i numeri da 10 a 50 in Python. Un logger impostato a INFO lascia passare INFO e superiori e scarta DEBUG.

### Meglio scrivere i log come JSON o come testo semplice?

JSON quando a leggere le righe è un programma, come un collector, un file o uno strumento di ricerca. Testo semplice quando a leggere il terminale è solo una persona. Tieni gli stessi campi in entrambi.

### Come aggiungo campi a una riga di log Python?

Passa `extra={"order_id": "ord_42"}` alla chiamata di log. Il formatter standard ignora i campi extra. Un formatter JSON o l’handler di LogBrew li conserva.

### Posso cercare i log per campo in LogBrew?

Sì. `GET /api/logs` accetta fino a 4 `attribute_filters`, ognuno con un percorso di campo, un tipo e un operatore come `eq` o `gte`.

## Filtra i tuoi log per qualsiasi campo

LogBrew conserva ogni campo che logghi e ritrova la riga per campo, accanto alla traccia e al problema a cui appartiene. Inizia con il generoso piano gratuito, senza carta.

[Inizia gratis](https://logbrew.co/it)

## Fonti

- [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)

## Altre guide per gli agenti e Python

- [Aggiungi un server MCP a Claude Code e Codex](https://logbrew.co/it/guides/mcp-server-claude-code-codex)
- [Usa AGENTS.md in Claude Code e Codex](https://logbrew.co/it/guides/agents-md)
- [Risolvi l’errore overloaded 529 e l’errore di rate limit 429 di Claude](https://logbrew.co/it/guides/claude-overloaded-rate-limit-error)
- [Configura il logging di Python con i livelli di log giusti e la cattura delle eccezioni](https://logbrew.co/it/guides/python-logging)
- [Come risolvere gli errori in Claude Code, Codex, JavaScript e Python](https://logbrew.co/it/guides)
