---
title: "Logs estruturados com formato de log e níveis que você busca por campo"
description: "O logging estruturado coloca cada valor no próprio campo em vez de dentro de uma frase. Enviamos 1 pedido de 3 formas ao LogBrew e mostramos qual formato de log você encontra de novo pelo order_id."
canonical: "https://logbrew.co/pt-BR/guides/structured-logging"
last_updated: "2026-11-10"
---

# Logs estruturados com formato de log e níveis que você busca por campo

O logging estruturado coloca cada valor no próprio campo em vez de dentro de uma frase. Enviamos 1 pedido de 3 formas ao LogBrew e mostramos qual formato de log você encontra de novo pelo `order_id`.

Testado em 10 de novembro de 2026 com Python 3.13.5 e logbrew-sdk 0.1.29

Por [Furkan Erday](https://github.com/furkanerday). Furkan Erday é o fundador do LogBrew. Ele desenvolve o LogBrew com Claude Code e Codex todos os dias.

Canonical: https://logbrew.co/pt-BR/guides/structured-logging

## Qual linha de log você encontra de novo pelo `order_id`

| Onde o id foi parar | Busca por texto | Filtro por campo |
| --- | --- | --- |
| Dentro do texto da mensagem | Encontrada | Não encontrada |
| Em campos `extra={}` | Não encontrada | Encontrada |
| Em um dict dentro de `extra={}` | Não encontrada | Não encontrada |

Enviamos 1 pedido de 3 formas pelo handler Python e depois o buscamos. Uma caneca cheia significa que a busca encontrou a linha.

## Escolha um formato de log que dê para filtrar

### Mantenha a mensagem fixa, ponha os valores em campos

Um campo mantém o nome e o tipo. Um valor dentro do texto da mensagem é só uma substring. Em Python, passe os campos em `extra={}`, e a mensagem fica igual para todo pedido.

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

### Escreva linhas JSON quando um programa lê o log

Uma subclasse de `Formatter` transforma cada registro em 1 objeto JSON por linha, com os campos padrão e todo valor de `extra`. Um coletor de logs interpreta a linha sem 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})
```

Saída

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

### Envie os campos ao LogBrew

`LogBrewLoggingHandler` envia cada registro com os valores de `extra` como campos. Enviamos o mesmo pedido de 3 formas, dentro do texto da mensagem, como campos `extra` e como um dict dentro de `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")
```

Saída

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

### Encontre a linha de novo pelo campo

`GET /api/logs` recebe `attribute_filters` com caminho do campo, tipo, operador e valor. O filtro por `order_id` encontrou só a linha enviada com `extra`. O dict dentro de `extra` foi descartado, porque o handler mantém só strings, números, booleanos 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}'
```

Saída

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

## Use os níveis de log do mesmo jeito em todo serviço

### Use 4 níveis em produção

`info` para o que aconteceu, `warning` para o que deu errado mas foi tratado, `error` para uma requisição ou um job que falhou, e `critical` para o que para o serviço. Reserve `debug` para execuções locais. O handler do LogBrew envia registros DEBUG como `info`.

### Defina o nível 1 vez, na inicialização

Leia o nível do ambiente, para que 1 host rode em DEBUG enquanto os outros ficam em INFO. Em Python, `logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))` resolve.

### Registre números como números

`amount_cents: 1999` mantém o tipo, então o filtro `amount_cents >= 1000` encontra a linha. Também enviamos `amount_cents: "1999"` como texto, e o filtro numérico pulou essa linha.

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

Saída

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

## O que vimos no teste

Enviamos 1 pedido de 3 formas pelo handler Python e depois o buscamos com a API do LogBrew. A busca por texto `search=ord_42` encontrou 1 das 3 linhas, a que tinha o id dentro da mensagem.

O filtro `order_id = ord_42` encontrou 1 linha, a enviada com `extra={"order_id": "ord_42"}`. O filtro `amount_cents >= 1000` encontrou a mesma linha, porque o handler manteve 1999 como número.

A linha enviada com `extra={"order": {...}}` não apareceu em nenhuma busca. O handler envia strings, números, booleanos e `None` de `extra` e pula um dict ou uma 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}'
```

Saída

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

## Texto simples vs formato de log em JSON

Um formato simples `%(message)s` descarta todo valor de `extra`. A linha abaixo termina em `INFO order paid` sem o id do pedido, então ninguém encontra o pedido depois.

Uma linha JSON mantém os campos e os tipos, e um coletor de logs a interpreta sem regex. Uma pessoa a lê com menos facilidade, então mantenha a mensagem curta e os nomes dos campos estáveis.

O Python não tem formatter JSON embutido. A python/cpython#77691 pediu um em 2018 e foi fechada, porque a documentação já mostra como escrever um. O `Formatter` de 15 linhas acima é essa receita.

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

Saída

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

## O que o LogBrew registra

Cada log mantém o nível, o nome do logger e todo campo primitivo de `extra`, mais os campos em `metadata={}` no handler. A API filtra por caminho do campo, tipo e operador. Texto aceita `eq`, `ne`, `contains` e `starts_with`, e números aceitam `gt`, `gte`, `lt` e `lte`.

O LogBrew não indexa um campo cujo nome contenha `password`, `secret`, `token`, `cookie` ou `authorization`, então um filtro nunca o encontra. Mesmo assim, mantenha segredos fora das linhas de log.

Um log enviado dentro de um trace carrega os ids do trace e do span, então um filtro por `order_id` leva à requisição que produziu a linha.

## Perguntas frequentes

### O que é logging estruturado?

Logging em que cada valor é um campo nomeado com um tipo, como JSON ou como campos de um LogRecord, em vez de palavras dentro de uma frase. Uma ferramenta de busca então filtra pelo campo, não por substring.

### Quais são os níveis de log em ordem?

DEBUG, INFO, WARNING, ERROR, CRITICAL, com os números de 10 a 50 no Python. Um logger definido em INFO deixa passar INFO e acima e descarta DEBUG.

### Devo registrar logs em JSON ou em texto simples?

JSON quando um programa lê as linhas, como um coletor, um arquivo ou uma ferramenta de busca. Texto simples quando só uma pessoa lê o terminal. Mantenha os mesmos campos nos 2.

### Como adiciono campos a uma linha de log em Python?

Passe `extra={"order_id": "ord_42"}` à chamada de log. O formatter padrão ignora os campos extra. Um formatter JSON ou o handler do LogBrew os mantém.

### Posso buscar logs por campo no LogBrew?

Sim. `GET /api/logs` aceita até 4 `attribute_filters`, cada um com caminho do campo, tipo e um operador como `eq` ou `gte`.

## Filtre seus logs por qualquer campo

O LogBrew mantém todo campo que você registra e encontra a linha de novo pelo campo, ao lado do trace e do problema a que ela pertence. Comece no plano gratuito generoso, sem cartão.

[Comece grátis](https://logbrew.co/pt-BR)

## Fontes

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

## Mais guias para agentes e Python

- [Adicione um servidor MCP ao Claude Code e ao Codex](https://logbrew.co/pt-BR/guides/mcp-server-claude-code-codex)
- [Use AGENTS.md no Claude Code e no Codex](https://logbrew.co/pt-BR/guides/agents-md)
- [Corrija o erro 529 overloaded e o erro 429 de rate limit do Claude](https://logbrew.co/pt-BR/guides/claude-overloaded-rate-limit-error)
- [Configure o logging do Python com os níveis de log certos e captura de exceções](https://logbrew.co/pt-BR/guides/python-logging)
- [Como corrigir erros no Claude Code, Codex, JavaScript e Python](https://logbrew.co/pt-BR/guides)
