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)はこれをさらに一歩進めます:1行に1つの JSON オブジェクト、ラッピング配列なし、末尾カンマなし。これにより JSONL ログは追記しやすく、ストリーミング可能で、すべての主要ログプラットフォームで簡単にパースできます。このガイドでは、Python、Node.js、Go から JSONL ログを生成する方法と、ELK、Fluentd、3大クラウドプロバイダーへの送信方法を学びます。

CSV や XML などの他の構造化フォーマットと比較して、JSONL は柔軟性、ツールサポート、人間の可読性の最適なバランスを提供します。フィールドはネスト可能で、型は保持され、固定スキーマを事前に定義する必要がありません。これが 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 ログファイルを tail し、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 コーデックが各 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 は、Kubernetes 環境で広く使用されている CNCF 卒業プロジェクトです。両方とも 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

Fluentd は Fluent Bit と比較してより豊富なプラグインエコシステムを提供し、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>

クラウドログプラットフォーム

3大クラウドプロバイダーはすべて 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 Query Language)は集計、時系列分析、異常検知をサポートする強力な構造化ログデータのクエリを提供します。

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 ログのクエリ、相関分析、アラート設定が容易になります。これらのベストプラクティスは、毎日数十億のログエントリを処理する本番システムから導き出されたものです。

一貫したログレベルの使用

標準的な重要度スケールを採用し、すべてのサービスで一貫して使用します。最も一般的な規約では5つのレベルを使用します:開発診断用の 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"}

フィールド名の標準化

すべてのサービスで共通フィールドの共有スキーマを定義します。フィールド名には 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 レベルで大きなオブジェクトやリクエスト/レスポンスボディのログを避けます。大量のデバッグログにはサンプリングを使用します。ローカルファイルに書き込み、サイドカー(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