構造化ログのフォーマット logfmt vs JSON lines

構造化ログのプラクティスをあちこちで調べていたら、logfmtを推奨する記事を見つけたので調べてみました。

先に結論を言うと、JSON linesを使っておくのが良さそうです。

logfmt について

logfmtとはスペース区切りで key=value を並べたフォーマットです。文字列にはクォートとエスケープによってスペースや改行を含められます。

at=info method=GET path=/ host=mutelight.org fwd="124.133.52.161"
  dyno=web.2 connect=4ms service=8ms status=200 bytes=1653

(logfmt から引用)

あちこちで logfmt のリファレンスとして紹介されているのはこの記事です。

https://brandur.org/logfmt

発明されたのはどこか分かりませんが、流行り始めたのはHerokuの標準フォーマットとして使われていたからのようです。

アプリケーションのログ記録のためのベストプラクティスを記述する | Heroku Dev Center

JSONに比べて目に優しいのがメリットです。とくに key がクォートされていないのが良いですね。

ログ集約サービスのlogfmt対応状況

個人的に興味があって調べた範囲です。有名なサービスを網羅しているわけではありません。

ということで、JSONに比べると十分に標準化されているとは言えず、とはいえlogfmtに対応していないサービスには変換して送れば使える状態です。

logfmtの問題点 -- 信頼できる仕様がない

信頼できるところがフォーマットの明確な仕様を決めていません。

例えばあちこちでlogfmtの一次ソースのように紹介されている https://brandur.org/logfmt では次のように書いてある部分があります。

Splunk also recommends the same format under their best practices so we can be sure that it can be used to search and analyze all our logs in the long term.

で、リンク先を見てみると、Splunkが推奨している key-value 記法にはなんとカンマ区切りがついています。この記事の著者が単にカンマを見逃したのか、そもそもlogfmtを厳密な仕様をもったフォーマットとして考えていないのかは分かりません。

key1=value1, key2=value2, key3=value3 . . .

(https://dev.splunk.com/enterprise/docs/developapps/addsupport/logging/loggingbestpractices/ より)

フォーマットが決まっていないことで弊害もあります。 Python の構造化ロギングライブラリとして有名なstructlogですが、未リリースのバグ修正としてlogfmtエスケープ漏れがあります。現時点の最新版である24.1.0ではエスケープが不足していると言うことです。

structlog/CHANGELOG.md at 555c3878a98e5b95986ca5cc36959d4eeaff8397 · hynek/structlog · GitHub

このバグ修正で、structlogの作者がlogfmtの仕様がどこにあるのか質問して回答されたのはGoのライブラリに含まれるEBNFです。

LogfmtRenderer has a bug with escaped double quotes · Issue #511 · hynek/structlog · GitHub

logfmt package - github.com/kr/logfmt - Go Packages

しかし、このGoのライブラリがリファレンス実装でGrafana等で使われていると言っていますが、実際にGrafanaで使われているライブラリはこちらでした。

https://github.com/go-logfmt/logfmt

このライブラリのREADMEでは、logfmtのリファレンスとしては冒頭の https://brandur.org/logfmt を紹介した上で、仕様が標準化されていないと述べた上で、 https://github.com/kr/logfmt を一番信頼できる仕様だと述べています。

ということで、一応 https://github.com/kr/logfmt が定義している仕様が一番信頼できそうではあるけれども、ほとんどの場所でlogfmtのリファレンスは https://brandur.org/logfmt になっているために、各実装が https://github.com/kr/logfmt に準拠しているかは確認してみないと分かりません。

たとえば、 key=value ではなく flag という記法を flag=true と同じ意味として扱うケースがありますし、そもそもboolが true/false なのか True/False なのかといったことも決まっていません。

まとめ

logfmtには信頼できる仕様がないため、あちこちのシリアライザとパーサーに互換性があるか信頼できません。

また、BigQuery や CloudWatch logsといった大手のサービスでプレインテキスト扱いになります。

他にもJSONに比べるとネストができない、配列の書き方が決まっていないなどの問題があります。

Herokuを使う場合や、コンソールやテキストファイルのログがメインの場合はlogfmtでもいいかもしれませんが、最初から構造化ログを外部サービスに送る前提で使う場合は、最初からJSONを使っておいた方が良いと思います。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。