---
title: "Structured logging với định dạng log và mức log bạn tìm được theo trường"
description: "Structured logging đặt mỗi giá trị vào một trường riêng thay vì nhét vào câu. Chúng tôi gửi 1 đơn hàng theo 3 cách tới LogBrew và cho thấy định dạng log nào bạn tìm lại được theo order_id."
canonical: "https://logbrew.co/vi/guides/structured-logging"
last_updated: "2026-11-10"
---

# Structured logging với định dạng log và mức log bạn tìm được theo trường

Structured logging đặt mỗi giá trị vào một trường riêng thay vì nhét vào câu. Chúng tôi gửi 1 đơn hàng theo 3 cách tới LogBrew và cho thấy định dạng log nào bạn tìm lại được theo `order_id`.

Đã thử nghiệm ngày 10 tháng 11, 2026 với Python 3.13.5 và logbrew-sdk 0.1.29

Tác giả [Furkan Erday](https://github.com/furkanerday). Furkan Erday là nhà sáng lập LogBrew. Anh xây dựng LogBrew bằng Claude Code và Codex mỗi ngày.

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

## Dòng log nào bạn tìm lại được theo `order_id`

| Id nằm ở đâu | Tìm theo văn bản | Lọc theo trường |
| --- | --- | --- |
| Trong văn bản thông điệp | Thấy | Không thấy |
| Trong các trường `extra={}` | Không thấy | Thấy |
| Trong một dict bên trong `extra={}` | Không thấy | Không thấy |

Chúng tôi gửi 1 đơn hàng theo 3 cách qua handler Python, rồi tìm nó. Cốc đầy nghĩa là tìm kiếm thấy được dòng đó.

## Chọn định dạng log mà bạn lọc được

### Giữ thông điệp cố định, đưa giá trị vào trường

Một trường giữ tên và kiểu của nó. Một giá trị nằm trong văn bản thông điệp chỉ là một chuỗi con. Trong Python, truyền các trường qua `extra={}`, và thông điệp giữ nguyên cho mọi đơn hàng.

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

### Viết dòng JSON khi một chương trình đọc log

Một lớp con của `Formatter` biến mỗi bản ghi thành 1 đối tượng JSON trên mỗi dòng, với các trường chuẩn và mọi giá trị `extra`. Bộ thu thập log phân tích dòng đó mà không cần 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})
```

Kết quả

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

### Gửi các trường tới LogBrew

`LogBrewLoggingHandler` gửi mỗi bản ghi với các giá trị `extra` dưới dạng trường. Chúng tôi gửi cùng một đơn hàng theo 3 cách, trong văn bản thông điệp, dưới dạng trường `extra` và dưới dạng một dict bên trong `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")
```

Kết quả

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

### Tìm lại dòng log theo trường

`GET /api/logs` nhận `attribute_filters` gồm đường dẫn trường, kiểu, toán tử và giá trị. Bộ lọc theo `order_id` chỉ tìm thấy dòng gửi bằng `extra`. Dict bên trong `extra` bị loại, vì handler chỉ giữ chuỗi, số, boolean và `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}'
```

Kết quả

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

## Dùng mức log theo cùng một cách ở mọi dịch vụ

### Dùng 4 mức trên production

`info` cho việc đã xảy ra, `warning` cho việc trục trặc nhưng đã được xử lý, `error` cho một request hay job thất bại, và `critical` cho việc làm dịch vụ dừng. Giữ `debug` cho các lượt chạy cục bộ. Handler của LogBrew gửi bản ghi DEBUG dưới dạng `info`.

### Đặt mức log 1 lần, lúc khởi động

Đọc mức log từ biến môi trường, để 1 host có thể chạy ở DEBUG trong khi phần còn lại giữ INFO. Trong Python, `logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))` là đủ.

### Ghi số dưới dạng số

`amount_cents: 1999` giữ kiểu của nó, nên bộ lọc `amount_cents >= 1000` khớp với nó. Chúng tôi cũng gửi `amount_cents: "1999"` dưới dạng văn bản, và bộ lọc số bỏ qua dòng đó.

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

Kết quả

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

## Kết quả khi chúng tôi thử nghiệm

Chúng tôi gửi 1 đơn hàng theo 3 cách qua handler Python, rồi tìm nó bằng LogBrew API. Tìm kiếm văn bản `search=ord_42` khớp 1 trong 3 dòng, dòng có id nằm trong thông điệp.

Bộ lọc `order_id = ord_42` khớp 1 dòng, dòng gửi bằng `extra={"order_id": "ord_42"}`. Bộ lọc `amount_cents >= 1000` khớp cùng dòng đó, vì handler giữ 1999 dưới dạng số.

Dòng gửi bằng `extra={"order": {...}}` không khớp gì cả. Handler gửi chuỗi, số, boolean và `None` từ `extra` và bỏ qua dict hay list.

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

Kết quả

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

## Văn bản thuần và định dạng log JSON

Định dạng thuần `%(message)s` bỏ mọi giá trị `extra`. Dòng bên dưới kết thúc bằng `INFO order paid` mà không có id đơn hàng, nên sau này không ai tìm được đơn hàng đó.

Một dòng JSON giữ các trường và kiểu của chúng, và bộ thu thập log phân tích nó mà không cần regex. Người đọc sẽ khó đọc hơn, nên hãy giữ thông điệp ngắn và tên trường ổn định.

Python không có formatter JSON tích hợp sẵn. python/cpython#77691 đã đề nghị thêm vào năm 2018 và bị đóng, vì tài liệu đã chỉ cách tự viết. `Formatter` 15 dòng ở trên chính là công thức đó.

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

Kết quả

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

## LogBrew ghi nhận những gì

Mỗi log giữ mức log, tên logger và mọi trường nguyên thủy từ `extra`, cộng thêm các trường trong `metadata={}` trên handler. API lọc theo đường dẫn trường, kiểu và toán tử. Văn bản nhận `eq`, `ne`, `contains` và `starts_with`, còn số nhận `gt`, `gte`, `lt` và `lte`.

LogBrew không đánh chỉ mục trường có tên chứa `password`, `secret`, `token`, `cookie` hay `authorization`, nên bộ lọc không bao giờ khớp với nó. Dù sao cũng hãy giữ thông tin bí mật ngoài các dòng log.

Một log gửi bên trong một trace mang theo id của trace và span, nên bộ lọc theo `order_id` dẫn tới request đã tạo ra dòng đó.

## Câu hỏi thường gặp

### Structured logging là gì?

Cách ghi log trong đó mỗi giá trị là một trường có tên và có kiểu, dưới dạng JSON hoặc các trường trên một bản ghi log, thay vì từ ngữ trong câu. Công cụ tìm kiếm khi đó lọc theo trường, không phải theo chuỗi con.

### Các mức log theo thứ tự là gì?

DEBUG, INFO, WARNING, ERROR, CRITICAL, với số từ 10 đến 50 trong Python. Logger đặt ở INFO cho INFO trở lên đi qua và bỏ qua DEBUG.

### Nên ghi log bằng JSON hay văn bản thuần?

JSON khi một chương trình đọc các dòng, như bộ thu thập, file hay công cụ tìm kiếm. Văn bản thuần khi chỉ có người đọc terminal. Giữ cùng các trường ở cả 2.

### Làm sao thêm trường vào dòng log Python?

Truyền `extra={"order_id": "ord_42"}` vào lời gọi log. Formatter chuẩn bỏ qua các trường extra. Formatter JSON hoặc handler của LogBrew giữ chúng lại.

### Tôi có thể tìm log theo trường trong LogBrew không?

Có. `GET /api/logs` nhận tối đa 4 `attribute_filters`, mỗi bộ gồm đường dẫn trường, kiểu và một toán tử như `eq` hay `gte`.

## Lọc log của bạn theo bất kỳ trường nào

LogBrew giữ mọi trường bạn ghi và tìm lại dòng log theo trường, ngay cạnh trace và vấn đề mà nó thuộc về. Bắt đầu với gói miễn phí rộng rãi, không cần thẻ.

[Bắt đầu miễn phí](https://logbrew.co/vi)

## Nguồn

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

## Thêm hướng dẫn cho agent và Python

- [Thêm máy chủ MCP vào Claude Code và Codex](https://logbrew.co/vi/guides/mcp-server-claude-code-codex)
- [Dùng AGENTS.md trong Claude Code và Codex](https://logbrew.co/vi/guides/agents-md)
- [Khắc phục lỗi Claude overloaded 529 và lỗi rate limit 429](https://logbrew.co/vi/guides/claude-overloaded-rate-limit-error)
- [Thiết lập logging Python với đúng mức log và bắt ngoại lệ kèm traceback](https://logbrew.co/vi/guides/python-logging)
- [Cách sửa lỗi trong Claude Code, Codex, JavaScript và Python](https://logbrew.co/vi/guides)
