Structured Loggingは、各メッセージを非構造化テキストではなく、キーと値のペアを持つ機械可読形式で表現するログ記録のアプローチです。フラットな文字列とは異なり、構造化ログにはタイムスタンプ、レベル、モジュール、リクエストIDなどのメタデータが含まれ、分析システムによってインデックス化できます。O'Reilly Effective Loggingによると、構造化フォーマットへの移行により、フィールドによるフィルタリングが可能になり、インシデントの検索時間が数時間から数分に短縮されます。これは現代のモバイルおよびサーバー開発におけるデファクトスタンダードです:JSONとlogfmtにより、ログを目ではなくプログラムで処理できます。
主要ポイント
Structured Loggingは、各メッセージに名前付きフィールドと型付き値が含まれるログ記録方法です。User 42 logged in from device ABCのような文字列の代わりに、構造化ログはフィールドのセットとして表示されます:user_id=42, event=login, device_id=ABC, timestamp=2026-07-04T10:30:00Z。
テキストログに対する構造化ログの主な利点は、プログラムによる処理が可能なことです。テキストログのパースには正規表現と文字列形式に関する前提が必要です。構造化ログは損失なくパースされます:各フィールドには既知の型と名前があり、追加処理なしで過去1時間のユーザー42の認証エラーをすべて検索のようなクエリを構築できます。
Honeycomb.io(2023年)によると、本番環境でstructured loggingを使用するチームは、テキストログとgrepに依存するチームと比較して、平均4倍速くインシデントを検出します。
Structured Loggingは複数のシリアライゼーションフォーマットをサポートしています。フォーマットの選択はインフラに依存します:JSONはElasticsearchやクラウドシステムとの統合に便利で、logfmtはコンソールでのtailやgrepによる表示に、Protocol Buffersは帯域幅制限のある高性能システムに適しています。
| フォーマット | 例 | 使用する場面 |
|---|---|---|
| JSON | {"event":"login","user_id":42} | ELK Stack、クラウドコレクター、マイクロサービス |
| Logfmt | event=login user_id=42 duration_ms=150 | コンソール、tail、heroku logs |
| MessagePack | JSONのバイナリ版 | 高負荷システム、IoT |
JSONは構造化ログで最も一般的なフォーマットです。Logstash、Fluentd、Amazon CloudWatch、Google Cloud Loggingなど、すべての収集システムでネイティブにサポートされています。JSONログは人間が読みやすく、追加ライブラリなしで任意のプログラミング言語でパースできます。主な欠点は冗長性です:各キーと値のペアには引用符とコロンが必要で、logfmtと比較して保存データ量が30–50%増加します。
LogfmtはHerokuでコンソール表示用に開発されました。JSONよりコンパクトで、人間の可読性を維持し、cutやawkで簡単にパースできます。例:ts=2026-07-04T10:30:00Z level=error module=api status=500。Logfmtはほとんどの文字のエスケープを必要とせず、コンテナでのstdoutロギングに適しています。
モバイルアプリケーションでは、Structured Loggingが3つの主要な問題を解決します:デバイス上での再現なしにクラッシュ原因を特定、ユーザーセッションの追跡、アプリバージョンごとのパフォーマンス分析。
モバイルデバイス上のテキストログはほとんど役に立ちません — 開発者はユーザーのデバイスでログをgrepできません。構造化ログはクラウドシステム(Firebase、Sentry、Datadog)に送信され、そこでインデックス化されます。iOS 17.4、アプリバージョン3.2、チェックアウトモジュールのすべてのクラッシュを表示のようなクエリを構築し、数秒で正確な選択結果を得られます。
Sentry(2024年)によると、構造化breadcrumbsを使用するアプリケーションは、エラーテキストのみをログするアプリケーションと比較して、各クラッシュレポートに60%多くのコンテキストがあります。これはバグ修正の速度に直接影響します。
ELK Stack — Elasticsearch、Logstash、Kibana — は構造化ログを扱うための標準インフラであり続けています。LogstashはJSONでログを受信し、変換してElasticsearchにインデックス用に送信し、Kibanaはクエリとダッシュボードのためのビジュアルインターフェースを提供します。
モバイルアプリケーションでは、クラウドソリューションが人気です:Firebase Crashlytics(カスタムログ付き)、Sentry(breadcrumbs付き)、Datadog(APMトラッキング付き)。これらはモバイルSDKから直接構造化ログを受け入れ、独自のバックエンドをデプロイする必要がありません。Firebaseはクラッシュレポートの無料パッケージを提供し、Sentryは分散トレーシングを追加し、DatadogはAPMと統合してクライアントとサーバーの両方でリクエストパフォーマンスを同時に追跡します。
Grafana Loki — ログに最適化されたElasticsearchの代替手段。Lokiはデフォルトでメッセージコンテンツをインデックス化せず、ラベルを使用したフィルタリングを行います。これによりストレージコストが大幅に削減され、固定フィールドセットに対するクエリが高速化されます。
// Swift Loggerを使用したJSONでの構造化ロギング
struct StructuredLog {
let event: String
let attributes: [String: Any]
let level: String
func serialize() -> String {
var base = "event=\(event) level=\(level)"
for (key, value) in attributes {
base += " \(key)=\(value)"
}
return base
}
}
構造化ロギングの第一のルール:すべてのメッセージにリクエストまたはセッション識別子を含める必要があります。コンテキストがないと、個々のログは役に立ちません — どのユーザーやリクエストに属するかがわかりません。セッション開始時にcorrelation IDを追加し、アプリケーションのすべてのレイヤーに渡します。
第二のルール:フィールドの型付け。数値フィールド(duration_ms、status_code、retry_count)は文字列ではなく数値として送信する必要があります。Elasticsearchと類似システムは数値と文字列を異なる方法でインデックス化します:数値は集計(平均、中央値、パーセンタイル)が可能で、文字列は全文検索をサポートします。誤った型付けは分析ダッシュボードの構築を不可能にします。
第三のルール:ネストされたオブジェクトを避ける。2レベルを超えるネスト深度のJSONログはフィルタリングと可視化が困難です。{"user": {"name": "Alice", "role": "admin"}}の代わりに、フラットなキーを使用します:user_name=Alice user_role=admin。
// Timber + logfmtを使用したAndroidでの構造化ロギング
class StructuredTree : Timber.Tree() {
override fun log(priority: Int, tag: String?,
message: String?, t: Throwable?) {
val level = priorityToLevel(priority)
val logfmt = "level=$level tag=$tag message=$message"
sendToRemote(logfmt)
}
}
各構造化メッセージの最小フィールドセット:ISO 8601形式のtimestamp、level(debug/info/warn/error/fatal)、logger(モジュールまたはクラス名)、message(イベントの人間可読な説明)。追加:correlation_id、user_id(既知の場合)、version(アプリバージョン)、platform(iOS/Android)、environment(dev/staging/prod)。
correlation_idがないと、構造化ログはばらばらのレコードの集まりになり、単一のユーザーシナリオにリンクできなくなります。アプリ起動時にUUIDを生成し、すべてのセッションログに追加します。実際には、correlation_idはUIイベントからネットワークリクエストやバックグラウンドタスクまで、すべてのレイヤーを通じて渡される必要があります — そうしないと、一部のログがコンテキストなしで残り、分析に参加しません。エンドツーエンドの追跡により、単一のUUIDでユーザージャーニーの完全な全体像を収集できます。
iOSでは、os_logをラップしてフィールドをlogfmt形式にシリアライズすることで構造化ロギングを実装できます。Androidでは、カスタムTreeを持つTimberを使用して、サーバーに送信する前にメッセージをJSONまたはlogfmtに変換します。
import OSLog
struct StructuredLogger {
let subsystem: String
let category: String
func log(level: OSLogType,
event: String,
context: [String: Any]) {
let oslogger = Logger(
subsystem: subsystem,
category: category
)
let fields = context.map {
"\($0.key)=\($0.value)"
}.joined(separator: " ")
oslogger.log(level: level,
"\(event) \(fields)")
}
}
構造化ログとテキストログの選択はプロジェクトの段階に依存します。初期開発段階では、テキストログの方がシンプルで高速です — 開発者は追加のラッパーなしで直接メッセージを書きます。しかし、プロジェクトが単一のチームやサーバーの範囲を超えると、構造化ログが必須になります。
| 基準 | テキストログ | 構造化ログ |
|---|---|---|
| 可読性 | コンソールで高い | 中程度(pretty-printが必要) |
| 検索 | 部分文字列によるgrep | フィールドと値によるクエリ |
| 集計 | 非対応 | 平均、中央値、パーセンタイル |
| 統合 | パースが必要 | ELK/Loki/Datadogでネイティブ |
| ストレージ容量 | 小さい(メタデータなし) | 大きい(フィールド+値) |
よくある質問
サーバー送信にはJSONを使用します — Firebase Crashlytics、Sentry、Datadogでネイティブにサポートされています。XcodeやAndroid Studioのログでローカルに表示するには、logfmtを使用します — よりコンパクトで、フォーマットなしで読めます。
はい、クライアント上の構造化ログにより、各クラッシュレポートにコンテキスト(OSバージョン、ネットワーク状態、ユーザーの最後の操作)を追加できます。構造化breadcrumbsがないと、クラッシュレポートにはユーザーシナリオなしのコールスタックのみが含まれます。
Logfmtはよりコンパクト(30–50%容量削減)で、ターミナルで読みやすいです。JSONはネストされたオブジェクトと配列をサポートしますが、引用符のエスケープが必要です。選択はインフラに依存します:ELKにはJSON、コンソール表示にはlogfmt。
アプリ起動時にUUIDの単一インスタンスを作成し、シングルトンまたはDIコンテナに保存し、コンストラクタを介してすべてのロガーに渡します。代替として、KotlinコルーチンでスレッドローカルまたはContinuation Local Storageを使用します。
可能ですが、推奨されません — 混在すると自動インデックス化の機能が失われます。一部のログがテキストベースの場合、正規表現でパースする必要があり、検索のパフォーマンスと信頼性が低下します。すべてのログを構造化フォーマットに移行することをお勧めします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。