使用 JSONL 進行結構化日誌

使用 JSONL(JSON Lines)格式進行結構化日誌的完整指南。學習整合 ELK Stack、Fluentd、CloudWatch、GCP Logging 和 Azure Monitor,附帶正式環境可用的程式碼範例。

最後更新:2026 年 2 月

為什麼使用 JSONL 進行結構化日誌?

傳統的純文字日誌雖然人類可讀,但機器幾乎無法可靠地解析。像 '2024-01-15 ERROR User login failed for admin' 這樣的行將嚴重性、事件型別和使用者名稱埋在非結構化的字串中。每個日誌聚合工具都必須使用脆弱的正規表達式來提取含義,而日誌格式的任何變更都會破壞管線。

結構化日誌透過將每筆日誌條目作為具有明確定義欄位的 JSON 物件來解決這個問題。JSONL(JSON Lines)更進一步:每行一個 JSON 物件,沒有包裝陣列,沒有尾隨逗號。這使得 JSONL 日誌適合追加、可串流處理,且每個主要日誌平台都能輕鬆解析。在本指南中,你將學習如何從 Python、Node.js 和 Go 產生 JSONL 日誌,以及如何將它們傳送到 ELK、Fluentd 和三大雲端供應商。

與其他結構化格式如 CSV 或 XML 相比,JSONL 在靈活性、工具支援和人類可讀性之間提供了最佳平衡。欄位可以巢狀、型別得以保留,而且你永遠不需要預先定義固定的 schema。這就是為什麼 JSONL 已成為現代基礎設施中結構化日誌的事實標準。

各語言的日誌函式庫

大多數現代日誌函式庫原生支援結構化 JSON 輸出,或只需簡單的設定變更。以下是 Python、Node.js 和 Go 的推薦函式庫,每個都產生與 JSONL 相容的輸出。

Python:structlog

推薦

structlog 是 Python 領先的結構化日誌函式庫。它包裝標準 logging 模組,預設產生 JSON 輸出。其處理器管線讓你可以在序列化之前豐富、過濾和轉換日誌條目。

Python:structlog
import structlog
# Configure structlog for JSONL output
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.BoundLogger,
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
# Each call produces one JSONL line
log.info("user.login", user_id="u-42", ip="10.0.0.1")
# {"event":"user.login","user_id":"u-42","ip":"10.0.0.1","level":"info","timestamp":"2026-02-15T08:30:00Z"}
log.error("db.connection_failed", host="db-primary", retries=3)
# {"event":"db.connection_failed","host":"db-primary","retries":3,"level":"error","timestamp":"2026-02-15T08:30:01Z"}

Node.js:pino

最快

pino 是最快的 Node.js 日誌器,預設產生 JSONL 輸出且無需設定。它透過非同步寫入和最小化序列化路徑實現低開銷。pino-pretty 輔助套件為本機開發提供人類可讀的輸出。

Node.js:pino
import pino from 'pino';
// pino outputs JSONL by default
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
// Each call produces one JSONL line
log.info({ userId: 'u-42', ip: '10.0.0.1' }, 'user.login');
// {"level":30,"time":"2026-02-15T08:30:00.000Z","userId":"u-42","ip":"10.0.0.1","msg":"user.login"}
log.error({ host: 'db-primary', retries: 3 }, 'db.connection_failed');
// {"level":50,"time":"2026-02-15T08:30:01.000Z","host":"db-primary","retries":3,"msg":"db.connection_failed"}
// Child loggers inherit context
const reqLog = log.child({ requestId: 'req-abc-123' });
reqLog.info({ path: '/api/users' }, 'request.start');

Go:zerolog

零分配

zerolog 是 Go 的零分配 JSON 日誌器,專為高吞吐量服務的最大效能而設計。它直接寫入 io.Writer 而無需中間分配,非常適合延遲敏感型應用。其鏈式 API 兼具人體工學和效率。

Go:zerolog
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Configure JSONL output to stdout
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
// Each call produces one JSONL line
log.Info().
Str("user_id", "u-42").
Str("ip", "10.0.0.1").
Msg("user.login")
// {"level":"info","user_id":"u-42","ip":"10.0.0.1","time":1739608200,"message":"user.login"}
log.Error().
Str("host", "db-primary").
Int("retries", 3).
Msg("db.connection_failed")
}

ELK Stack 整合

ELK Stack(Elasticsearch、Logstash、Kibana)是部署最廣泛的開源日誌管理平台。JSONL 日誌可以無縫整合,因為 Logstash 和 Filebeat 可以原生解析 JSON,無需複雜的 grok 模式。

Filebeat 是輕量級的日誌收集器,用於追蹤你的 JSONL 日誌檔案並將它們轉發到 Logstash 或 Elasticsearch。將 json.keys_under_root 設為 true 可將 JSON 欄位提升為 Elasticsearch 的頂層欄位,使其可直接在 Kibana 中搜尋。

Filebeat 設定
# filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.jsonl
json.keys_under_root: true
json.add_error_key: true
json.message_key: message
json.overwrite_keys: true
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
index: "app-logs-%{+yyyy.MM.dd}"
# Optional: send to Logstash instead
# output.logstash:
# hosts: ["logstash:5044"]

如果你需要在索引之前轉換或豐富日誌,Logstash 位於 Filebeat 和 Elasticsearch 之間。json codec 自動解析每個 JSONL 行。使用 mutate 過濾器重新命名欄位、新增標籤或在儲存前移除敏感資料。

Logstash 管線
# logstash.conf
input {
beats {
port => 5044
codec => json
}
}
filter {
# Parse timestamp from the JSON field
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
# Add environment tag
mutate {
add_field => { "environment" => "production" }
remove_field => ["agent", "ecs", "host"]
}
# Route by log level
if [level] == "error" or [level] == "fatal" {
mutate { add_tag => ["alert"] }
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}

Fluentd 與 Fluent Bit

Fluentd 及其輕量級版本 Fluent Bit 是 CNCF 畢業專案,廣泛用於 Kubernetes 環境。兩者都原生處理 JSONL 日誌,可以將它們轉發到幾乎任何目的地,從 Elasticsearch 到 S3 到雲原生日誌服務。

Fluent Bit 是 Kubernetes 和資源受限環境首選的日誌收集器。它消耗約 450KB 的記憶體,每秒可處理數十萬筆記錄。json 解析器無需任何自訂設定即可處理 JSONL 輸入。

Fluent Bit 設定
[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/app/*.jsonl
Parser json
Tag app.*
Refresh_Interval 5
[FILTER]
Name modify
Match app.*
Add cluster production-us-east-1
Add service my-api
[OUTPUT]
Name es
Match app.*
Host elasticsearch
Port 9200
Index app-logs
Type _doc
Logstash_Format On

與 Fluent Bit 相比,Fluentd 提供更豐富的外掛生態系統,擁有超過 500 個社群外掛。當你需要進階路由、緩衝或轉換時使用它。in_tail 外掛搭配 format json 原生讀取 JSONL 檔案。

Fluentd 設定
# fluentd.conf
<source>
@type tail
path /var/log/app/*.jsonl
pos_file /var/log/fluentd/app.pos
tag app.logs
<parse>
@type json
time_key timestamp
time_format %Y-%m-%dT%H:%M:%S%z
</parse>
</source>
<filter app.logs>
@type record_transformer
<record>
hostname "#{Socket.gethostname}"
environment production
</record>
</filter>
<match app.logs>
@type elasticsearch
host elasticsearch
port 9200
index_name app-logs
logstash_format true
<buffer>
@type memory
flush_interval 5s
chunk_limit_size 5m
</buffer>
</match>

雲端日誌平台

三大雲端供應商都接受 JSONL 格式的日誌,並自動將 JSON 欄位解析為可搜尋、可過濾的屬性。這消除了自行運維日誌基礎設施的需要,同時提供相同的結構化查詢功能。

AWS CloudWatch Logs

AWS

CloudWatch Logs 自動解析 JSON 日誌條目,使每個欄位都可透過 CloudWatch Logs Insights 查詢。當你的應用程式在 ECS、Lambda 或配有 CloudWatch 代理的 EC2 上執行時,JSONL 輸出無需額外設定即可作為結構化資料索引。使用 Logs Insights 類 SQL 語法可在數秒內查詢數百萬筆日誌條目。

AWS CloudWatch Logs
# CloudWatch Logs Insights query examples
# Find all errors in the last hour
fields @timestamp, level, event, user_id
| filter level = "error"
| sort @timestamp desc
| limit 100
# Aggregate error counts by event type
fields event
| filter level = "error"
| stats count(*) as error_count by event
| sort error_count desc
# P99 latency by endpoint
fields path, duration_ms
| filter ispresent(duration_ms)
| stats pct(duration_ms, 99) as p99 by path
| sort p99 desc

GCP Cloud Logging

GCP

Google Cloud Logging(前身為 Stackdriver)將 JSON 酬載視為具有一流支援的結構化日誌條目。當你在 Cloud Run、GKE 或 Cloud Functions 上將 JSONL 寫入 stdout 時,日誌代理會將每行解析為結構化的 LogEntry。severity 欄位直接對應到 Cloud Logging 嚴重性等級,jsonPayload 欄位變為可索引和可搜尋的。

GCP Cloud Logging
# Python example for Cloud Run / Cloud Functions
import json
import sys
def log(severity: str, message: str, **fields):
"""Write a JSONL log entry compatible with GCP Cloud Logging."""
entry = {
"severity": severity.upper(), # Maps to Cloud Logging severity
"message": message,
**fields,
}
print(json.dumps(entry), file=sys.stdout, flush=True)
# GCP automatically parses these as structured logs
log("INFO", "user.login", user_id="u-42", ip="10.0.0.1")
log("ERROR", "db.timeout", host="db-primary", latency_ms=5200)
# Query in Cloud Logging:
# jsonPayload.user_id = "u-42"
# severity >= ERROR

Azure Monitor Logs

Azure

Azure Monitor 透過 Azure Monitor Agent 或直接 API 匯入來接收 JSONL 日誌。Application Insights 自動解析 JSON 追蹤和自訂事件。KQL(Kusto 查詢語言)提供強大的結構化日誌資料查詢,支援聚合、時間序列分析和異常偵測。

Azure Monitor Logs
// KQL queries for JSONL logs in Azure Monitor
// Find recent errors
AppTraces
| where SeverityLevel >= 3
| extend parsed = parse_json(Message)
| project TimeGenerated, parsed.event, parsed.user_id,
parsed.error_message
| order by TimeGenerated desc
| take 100
// Error rate over time (5-minute buckets)
AppTraces
| where SeverityLevel >= 3
| summarize ErrorCount = count() by bin(TimeGenerated, 5m)
| render timechart
// Top error events
AppTraces
| where SeverityLevel >= 3
| extend parsed = parse_json(Message)
| summarize Count = count() by tostring(parsed.event)
| top 10 by Count

JSONL 日誌最佳實踐

在所有服務中遵循一致的慣例,使 JSONL 日誌更容易查詢、關聯和設定告警。這些最佳實踐來自每天處理數十億筆日誌條目的正式環境系統。

使用一致的日誌等級

採用標準的嚴重性等級並在所有服務中一致使用。最常見的慣例使用五個等級:DEBUG 用於開發診斷、INFO 用於正常操作、WARN 用於可恢復的問題、ERROR 用於需要關注的故障、FATAL 用於不可恢復的崩潰。在匯入時將數字等級(如 pino 的 10-60 範圍)對應到這些字串名稱,以實現統一的查詢。

使用一致的日誌等級
{"level":"debug","event":"cache.miss","key":"user:42","timestamp":"2026-02-15T08:30:00Z"}
{"level":"info","event":"request.completed","method":"GET","path":"/api/users","status":200,"duration_ms":45,"timestamp":"2026-02-15T08:30:01Z"}
{"level":"warn","event":"rate_limit.approaching","client_id":"c-99","current":950,"limit":1000,"timestamp":"2026-02-15T08:30:02Z"}
{"level":"error","event":"payment.failed","order_id":"ord-123","error":"card_declined","timestamp":"2026-02-15T08:30:03Z"}
{"level":"fatal","event":"startup.failed","reason":"missing DATABASE_URL","timestamp":"2026-02-15T08:30:04Z"}

標準化欄位名稱

為所有服務定義共用的通用欄位 schema。欄位名稱使用 snake_case,時間戳記使用 ISO 8601,關聯識別碼使用一致的命名。每筆日誌條目至少應包含:timestamp、level、event(機器可讀的事件名稱)和 service。請求範圍的條目應包含 request_id 和 user_id 以便追蹤。

標準化欄位名稱
// Recommended field schema
{
"timestamp": "2026-02-15T08:30:00.000Z", // ISO 8601, always UTC
"level": "info", // debug|info|warn|error|fatal
"event": "order.created", // dot-notation event name
"service": "order-api", // emitting service
"version": "1.4.2", // service version
"request_id": "req-abc-123", // correlation ID
"trace_id": "4bf92f3577b34da6", // distributed trace ID
"user_id": "u-42", // authenticated user
"duration_ms": 120, // numeric, not string
"order_id": "ord-789", // domain-specific context
"items_count": 3 // domain-specific context
}

效能注意事項

結構化日誌為每次日誌呼叫增加了序列化開銷。在高吞吐量服務中,這可能變得顯著。使用非同步日誌記錄(pino、zerolog)將序列化移出關鍵路徑。避免在 INFO 層級記錄大型物件或請求/回應本文。對高流量除錯日誌使用取樣。寫入本機檔案並讓 sidecar(Filebeat、Fluent Bit)處理傳送,而不是同步透過網路發送日誌。

使用非同步日誌器(pino、zerolog)避免在 JSON 序列化期間阻塞主執行緒。

永遠不要在 INFO 層級記錄完整的請求或回應本文。使用 DEBUG 層級,僅在排錯時啟用。

對高流量事件進行取樣。記錄每 100 個健康檢查請求中的 1 個,而不是全部記錄。

將日誌寫入本機檔案,使用 Filebeat 或 Fluent Bit 非同步傳送。避免在日誌路徑中進行同步網路呼叫。

設定最大日誌行大小(例如 16KB),防止過大的條目阻塞管線。

使用 logrotate 或函式庫內建的輪替功能輪替日誌檔案,防止磁碟空間耗盡。

線上驗證你的 JSONL 日誌

使用我們的免費瀏覽器工具檢查、驗證和轉換 JSONL 日誌檔案。無需上傳,所有處理在本機進行。

在瀏覽器中檢查 JSONL 日誌

檢視、搜尋和驗證高達 1GB 的 JSONL 日誌檔案。即時載入,用戶端處理,100% 隱私保護。

常見問題

JSONL 日誌 — 結構化日誌、ELK/Fluentd 與雲端擷取 | jsonl.co