
はじめに
モノリスでは、次のことができます。 ssh サーバーにアクセスして、 tail -f ログファイル。数百の動的ポッド上で数十のサービスが実行されているマイクロサービスでは、それはもはや不可能です。 集中ログは必須の要件です。
しかし、単にログを収集するだけでは十分ではありません。効果的なデバッグのために、ログは構造化され、クエリ可能であり、トレースにリンクされている必要があります。
1. 構造化されたロギング
1.1 なぜ構造化ログを使用するのか?
非構造化ログ — 自動的に解析するのは困難です:
2026-03-31 10:15:30 INFO Order O-001 created for customer C-042, total 500000 VND, 3 items, took 45ms
構造化ログ (JSON) — 機械可読、クエリ可能:
{
"timestamp": "2026-03-31T10:15:30.123Z",
"level": "INFO",
"service": "order-service",
"version": "1.2.3",
"traceId": "abc123def456",
"spanId": "span789abc",
"message": "Order created successfully",
"orderId": "O-001",
"customerId": "C-042",
"totalAmount": 500000,
"currency": "VND",
"itemCount": 3,
"durationMs": 45
}
1.2 ログレベル戦略
TRACE — Rất chi tiết, chỉ dùng khi debug cụ thể
Không bao giờ enable ở production
DEBUG — Thông tin debug (function calls, variable values)
Chỉ enable ở development, có thể bật tạm ở staging
INFO — Sự kiện business quan trọng (order created, payment processed)
Enable ở production — đây là log chính cần thu thập
WARN — Tình huống không mong đợi nhưng system vẫn hoạt động
(deprecated API call, slow query > 500ms, retry attempt)
ERROR — Lỗi cần xử lý nhưng service vẫn chạy
(external service timeout, validation failure)
FATAL — Lỗi nghiêm trọng, service sắp shutdown
(database connection lost, out of memory)
原則:
- INFO ログは「監査証跡」です - すべての重要なアクションには INFO ログが必要です
- エラー ログには、追加情報を必要とせずにデバッグできるスタック トレースと十分なコンテキストが含まれている必要があります
- 機密データ (パスワード、クレジット カード、PII) をログに記録しないでください。
1.3 構造化ロギングの実装
Java (Logback + Logstash エンコーダを備えた Spring Boot):
<!-- logback-spring.xml -->
<configuration>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"service":"order-service","version":"${APP_VERSION}"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON"/>
</root>
</configuration>
// Sử dụng MDC (Mapped Diagnostic Context) cho correlation
import org.slf4j.MDC;
@Component
public class RequestLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
MDC.put("traceId", extractOrGenerateTraceId(request));
MDC.put("userId", extractUserId(request));
try {
chain.doFilter(req, res);
} finally {
MDC.clear();
}
}
}
// Trong service code
@Slf4j
public class OrderService {
public Order createOrder(CreateOrderRequest req) {
log.info("Creating order", // message
kv("customerId", req.getCustomerId()),
kv("itemCount", req.getItems().size()),
kv("totalAmount", req.getTotal())
);
// ...
}
}
Node.js (Pino):
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
base: {
service: 'order-service',
version: process.env.APP_VERSION,
},
});
// Usage
logger.info({ orderId, customerId, totalAmount }, 'Order created successfully');
logger.error({ err, orderId }, 'Failed to process order');
2. ログ収集アーキテクチャ
2.1 パイプラインの概要
┌──────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Order Service│ │Payment Svc │ │ Inventory Svc│ │
│ │ (Pod) │ │ (Pod) │ │ (Pod) │ │
│ │ stdout/stderr│ │ stdout/stderr│ │ stdout/stderr│ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┴──────────────────┘ │
│ │ │
│ ┌────────────▼──────────────┐ │
│ │ Fluent Bit (DaemonSet) │ │
│ │ - Tail /var/log/pods/ │ │
│ │ - Parse JSON │ │
│ │ - Enrich (node, pod) │ │
│ │ - Buffer & retry │ │
│ └────────────┬──────────────┘ │
└───────────────────────────┼─────────────────────────────────┘
│
┌────────────▼────────────┐
│ Log Aggregation │
│ Loki / Elasticsearch │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Visualization │
│ Grafana / Kibana │
└─────────────────────────┘
2.2 滑らかなビット構成
# fluent-bit ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: monitoring
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Daemon Off
Log_Level info
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser docker
DB /run/fluent-bit/flb_kube.db
Mem_Buf_Limit 10MB
Skip_Long_Lines On
Refresh_Interval 10
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Merge_Log On
K8S-Logging.Parser On
K8S-Logging.Exclude On
[FILTER]
Name grep
Match kube.*
# Loại bỏ health check logs
Exclude log /health
[OUTPUT]
Name loki
Match kube.*
Host loki.monitoring.svc
Port 3100
Labels job=fluentbit, node=${NODE_NAME}
Label_keys $kubernetes['namespace_name'],$kubernetes['pod_name'],$kubernetes['container_name']
Remove_keys kubernetes,stream
Auto_Kubernetes_Labels On
3. Loki — ログの集約
3.1 Loki 対 Elasticsearch
| ロキ | エラスティックサーチ | |
|---|---|---|
| インデックス作成 | インデックス ラベル (メタデータ) のみ | ログ全体の全文インデックス |
| ストレージ | はるかに安い (S3/MinIO) | ストレージとメモリを消費します |
| クエリ | LogQL (シンプル、ラベル中心) | Lucene/KQL (強力なフルテキスト) |
| セットアップ | シンプル | 複雑 (クラスター、シャード) |
| 使用例 | クラウドネイティブ、コスト重視 | コンプライアンス、全文検索 |
| 統合 | グラファナネイティブ | キバナ |
Loki を選択する場合: ほとんどの場合、クラウド ネイティブです。低コストで、Grafana とネイティブに統合されており、運用ログには十分です。
Elasticsearch を選択する場合: コンプライアンス/監査ログ検索、ログコンテンツの全文検索が必要で、すでに Kibana エコシステムを備えています。
3.2 Loki アーキテクチャ
┌──────────────────────────────────────────────────┐
│ Loki │
│ │
│ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Distributor│──▶│ Ingester (in-memory) │ │
│ │ (receive) │ │ │ │
│ └─────────────┘ └───────────┬─────────────┘ │
│ │ flush │
│ ┌─────────────┐ ┌───────────▼─────────────┐ │
│ │ Querier │ │ Object Storage │ │
│ │ (read) │ │ (S3/MinIO/GCS) │ │
│ └──────┬──────┘ └─────────────────────────┘ │
│ │ │
└─────────┼────────────────────────────────────────┘
│ LogQL
▼
Grafana
3.3 Loki と Grafana のインストール
helm repo add grafana https://grafana.github.io/helm-charts
# Cài Loki (simple scalable mode)
helm upgrade --install loki grafana/loki \
--namespace monitoring \
--set loki.storage.type=s3 \
--set loki.storage.s3.bucket=loki-logs \
--set loki.storage.s3.region=ap-southeast-1
# Cài Grafana Alloy (Fluent Bit alternative từ Grafana)
helm upgrade --install alloy grafana/alloy \
--namespace monitoring
4. LogQL — ログクエリ言語
4.1 ストリームセレクター
ラベルによってログ ストリームを選択します。
# Tất cả log từ order-service
{service="order-service"}
# Log từ namespace services-prod
{namespace="services-prod"}
# Kết hợp nhiều labels
{namespace="services-prod", app="order-service", pod=~"order-service-.*"}
4.2 フィルター式
# Chứa chuỗi
{service="order-service"} |= "ERROR"
# Không chứa
{service="order-service"} != "health"
# Regex match
{service="order-service"} |~ "order.*created"
# Parse JSON và filter
{service="order-service"}
| json
| level = "ERROR"
| durationMs > 1000
# Pipeline phức tạp
{namespace="services-prod"}
| json
| level = "ERROR"
| line_format "{{.service}}: {{.message}} (trace: {{.traceId}})"
4.3 メトリッククエリ
# Đếm ERROR logs mỗi 5 phút
sum(rate({service="order-service"} |= "ERROR" [5m])) by (service)
# Log volume theo service
sum(bytes_rate({namespace="services-prod"}[5m])) by (service)
# Top 5 services nhiều lỗi nhất
topk(5,
sum(count_over_time({namespace="services-prod"} |= "ERROR" [1h]))
by (service)
)
5. トレースとのログの相関関係
目標: ログ エントリから、対応するトレースに即座にジャンプし、その逆も同様です。
5.1 トレースコンテキストをログに挿入する
// Spring Boot với Micrometer Tracing tự động inject
// Log sẽ có traceId và spanId từ MDC
// Kết quả log:
{
"timestamp": "...",
"level": "ERROR",
"service": "order-service",
"traceId": "abc123def456789", ← đây
"spanId": "span789", ← đây
"message": "Payment failed"
}
5.2 Grafana 派生フィールド
ログ内のtraceIdからJaegerへのリンクを作成するようにGrafanaを構成します。
// Trong Loki datasource config (Grafana)
{
"derivedFields": [
{
"matcherRegex": "traceId=(\\w+)",
"name": "TraceID",
"url": "http://jaeger:16686/trace/$${__value.raw}",
"datasourceUid": "jaeger"
}
]
}
Grafana でログを表示すると、traceId が自動的にクリック可能なリンクとなり、Jaeger トレースを開くことができるようになりました。
6. 保持とコストの最適化
6.1 ログ保存ポリシー
# Loki retention config
limits_config:
# Giữ log 30 ngày cho prod
retention_period: 720h
# Per-stream override
per_stream_rate_limit: 10MB
per_stream_rate_limit_burst: 30MB
compactor:
retention_enabled: true
retention_delete_delay: 2h
階層別の維持戦略:
Hot (0-7 ngày) : SSD storage — query nhanh
Warm (7-30 ngày) : HDD storage — query chậm hơn
Cold (30-365 ngày): S3 Glacier — archive only
6.2 ログ量を減らす
# Filtering trước khi ingest (Fluent Bit)
# Loại bỏ health check, static assets, debug logs từ noisy service
# Trong Fluent Bit:
[FILTER]
Name grep
Match kube.*
Exclude log (GET /health|GET /metrics|\.css|\.js|\.png)
[FILTER]
Name grep
Match kube.monitoring.*
Exclude log .* # Bỏ hoàn toàn log từ monitoring namespace
大容量サービスのサンプリング:
// Chỉ log 10% DEBUG requests bình thường, 100% errors
if (random.nextDouble() < 0.1 || isError) {
log.debug("Request processed", kv("endpoint", endpoint));
}
7. ベストプラクティス
ログチェックリスト:
□ Structured JSON logging
□ Consistent field names (snake_case) across services
□ traceId / spanId có mặt trong mọi log line
□ Correlation ID từ user request
□ Không log PII (email, phone, card numbers)
□ Error logs kèm stack trace
□ Business events quan trọng có INFO log
□ Health check endpoints bị filter khỏi logs
□ Log retention policy phù hợp với compliance
□ Alerting trên ERROR log spike
フィールドの命名規則:
{
"timestamp": "2026-03-31T10:00:00Z", // ISO 8601
"level": "INFO", // UPPERCASE
"service": "order-service", // kebab-case
"version": "1.2.3", // semver
"traceId": "abc123", // camelCase
"spanId": "def456",
"userId": "u-001", // camelCase
"message": "...", // human-readable
// Domain fields: snake_case
"order_id": "O-001",
"total_amount": 500000,
"duration_ms": 45 // unit suffix
}
概要
| コンセプト | 目的 |
|---|---|
| 構造化ロギング | 機械可読でクエリ可能なログ形式 |
| ログレベル | 環境に適した冗長性の制御 |
| 流暢なビット | DaemonSet はすべてのポッドからログを収集します。 |
| ロキ | クラウドネイティブ向けのコスト効率の高いログ集約 |
| ログQL | ラベルとフィルター式を使用してログをクエリする |
| ログ相関 | ログを分散トレースとリンクする |
| 保持ポリシー | コストとコンプライアンス要件のバランスをとる |
次の記事: 分散トレーシング — OpenTelemetry と Yeter