# Cầu nối logging: đưa log của logger bạn đang dùng vào OpenTelemetry

> Pino, Bunyan, Winston, logging của Python, slog, Logback và console của Deno thành bản ghi log có mức độ, thuộc tính và trace ID.

Một dòng log hữu ích cạnh trace khi nó là một **bản ghi log** OpenTelemetry: có mức độ, nội dung, thuộc tính có cấu trúc, và trace ID cùng span ID của request mà nó được ghi trong đó. *Cầu nối* (bridge) biến các lệnh gọi logger của bạn thành các bản ghi đó, trong khi đầu ra thường (stdout, file) vẫn giữ nguyên. Bạn giữ logger của mình; không cần ghi log qua API OpenTelemetry.

## Theo runtime

| Runtime | Logger | Cách nối | Belvador đã chạy |
|---|---|---|---|
| Node.js | Pino | Tự động cùng auto-instrumentations | Có |
| Node.js | Bunyan | Tự động cùng auto-instrumentations | Không |
| Node.js | Winston | Cài `@opentelemetry/winston-transport` cạnh Winston | Không |
| Node.js | `console.*` | Không thu — hãy dùng logger | — |
| Python | `logging` (và thư viện dựng trên nó) | `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true` cùng `opentelemetry-instrument` | Có |
| Go | `log/slog` | Handler `otelslog`; gọi các hàm `…Context` với context của request | Có |
| Java | `java.util.logging` | Tự động cùng Java agent | Có |
| Java | Logback, Log4j 2 | Tự động cùng Java agent | Không |
| Deno | `console.*` | Tự động với `OTEL_DENO=true` | Có |
| Bất kỳ | File log | Receiver `filelog` của Collector | Không |

"Belvador đã chạy" nghĩa là lần chứng nhận dùng logger đó và kiểm mức độ cùng việc gắn trace; các dòng còn lại dùng cùng cơ chế của cùng gói nhưng không nằm trong lần chạy. Lệnh cài cho từng loại nằm ở trang runtime: [Node.js](https://belvador.dev/vi/docs/otel/node#logs), [Python](https://belvador.dev/vi/docs/otel/python#logs), [Go](https://belvador.dev/vi/docs/otel/go), [Java](https://belvador.dev/vi/docs/otel/java#logs), [Deno](https://belvador.dev/vi/docs/otel/deno#logs), [Collector](https://belvador.dev/vi/docs/otel/collector#file-log).

## Điều gì làm log gắn với trace

Cầu nối chép ID của span *đang chạy* vào bản ghi. Vì vậy bản ghi chỉ mang trace ID khi được ghi lúc có span đang chạy ở đoạn code đó:

- **Trong handler của request** — span server của request đang chạy, nên các dòng ghi ở đó được gắn.
- **Trong việc chạy ngầm** (timer, consumer của hàng đợi, lúc khởi động) thường không có span nào, nên dòng log không có trace ID. Điều đó bình thường; chúng vẫn được lưu và tìm được.
- **Go** không có context ngầm: truyền `ctx` của request (`logger.InfoContext(ctx, …)`).
- **Chữ có nhắc trace ID** không phải là liên kết. Logger ghi `trace_id=…` vào nội dung thông điệp thì các trường trace của chính bản ghi vẫn trống.

Trong Belvador, bản ghi đã gắn hiện trace của nó ở trang **Logs**, và xuất hiện ở tab **Logs** của trang trace cùng mục **Logs quanh lỗi** của issue.

## Mức độ

Cầu nối ánh xạ các mức của từng logger sang mức độ OpenTelemetry (`TRACE` … `FATAL`); bộ lọc mức độ của Belvador dùng chúng. Mức tự định nghĩa được ánh xạ sang mức chuẩn gần nhất, nên hãy kiểm một lần nếu logger của bạn có mức riêng.

## Trường có cấu trúc

Các trường bạn truyền dưới dạng object (`logger.info({ orderId }, "paid")`, `logging.info("paid", extra={"order_id": …})`, `slog.Int("order_id", …)`) trở thành thuộc tính lọc được, và quy tắc quyền riêng tư nhìn thấy tên của chúng — xem [Quyền riêng tư](https://belvador.dev/vi/docs/otel/privacy#dong-log). Giá trị chèn vào chữ của thông điệp thì chỉ là chữ.

## Gửi bao nhiêu

Cầu nối gửi những gì mức của logger cho qua. Ở production hãy để `info` hoặc `warn`; logs không giảm theo lấy mẫu trace ([Lấy mẫu và chi phí](https://belvador.dev/vi/docs/otel/sampling-and-cost)).
