追記(2/8 11:30)
- id:naoyaによる一連のまとめが【今北産業】3分で分かるLTSV業界のまとめ【LTSV】 - naoyaのはてなダイアリーにあります。
- また、仕様などをまとめるために http://ltsv.org/ を立ち上げました。
追記ここまで
Labeled Tab Separated Values (LTSV) というのは、はてなで使っているログフォーマットのことで、広く使われているTSV(Tab Separated Value)フォーマットにラベルを付けて扱い易くしたものです。はてなでは、もう3年以上、このフォーマットでログを残していて、one-linerからfluentd、Apache Hiveまで幅広く便利に使えています。
ログフォーマットに期待されることは、
- フォーマットが統一されている → 共通のツールで集計し易い
- 新しいフィールドの追加が容易 → サービス固有のデータをログに残したいことはよくある。かつ、複数サービスに渡って、別々のエンジニアが触っても混乱しない
- one linerで操作しやすい → grepした後でsortなり、uniqなりで集計するのはよくあること
の3点だと考えています。
これまでにも、ログフォーマットを扱い易くする取り組みとして、SyslogでもRFC5425で構造化の仕様が入ったり*1していますが、いまいち普及しているとは言えません。Apache標準のCombined Log Formatを独自拡張して頑張る、というのがよくある光景なんじゃないかと思います。ただ、サービスごとの要求に応じて、どんどん独自拡張していると個別にParserの定義を書かないといけなくなったり、fluentdで扱う際にも頑張ってサービスごとの定義を正規表現で書かないといけない羽目になって、どんどん辛くなります。
Labeled Tab Separated Values (LTSV) とは
そこではてなでは、3年ほど前*2からLabeled Tab Separated Values (LTSV) によるログフォーマットを全面的に採用しています。
Labeled Tab Separated Valueは、
time:[28/Feb/2013:12:00:00 +0900] host:192.168.0.1 req:GET /list HTTP/1.1 status:200 size:5316 referer:- ua:Mozilla/5.0 taken:9789 isrobot:1 dos:- harddos:- cache:-
というもので、各フィールドがタブで区切られ、フィールドの頭にラベルを付けています。
ApacheのLogFormat定義は
LogFormat "host:%h\ttime:%t\treq:%r\tstatus:%>s\tsize:%b\treferer:%{Referer}i\tua:%{User-Agent}i\ttaken:%D\tisbot:%{Isbot}e\tdos:%{SuspectDoS}e\tharddos:%{SuspectHardDoS}ecache:%{X-Cache}o" ltsv
となり、nginxでは
log_format tsv "time:$time_local\thost:$remote_addr\treq:$request\tstatus:$status\tsize:$body_bytes_sent\treferer:$http_referer\tua:$http_user_agent\treqtime:$request_time\tcache:$upstream_http_x_cache";
となります。
このLabeled Tab Separeted Valueフォーマットのメリットは、まずログを定義する時に各フィールドの順序を意識しなくてよく、各プロダクトで容易に独自拡張できる、ということにあります。上の例では、X-Cacheヘッダやmod_dosdetectorの判定結果を残したりしています。はてなでの運用では、ラベルをWikiで管理しており、新しいラベルを作る際には記録してもらうようにしています。
Labeled Tab Separated Valueを扱う
LTSVはTSVの拡張ですので、cutコマンドで簡単に切り分けられます。例えば、ステータスコードごとに数えるとすると以下のようになります。
% tail -n 100 /var/log/httpd/access_log.ltsv | cut -f 5 | sort | uniq -c 97 status:200 2 status:301 1 status:404
ただ、これだけだと、ラベルが出て、ちょっと分かり易いぐらいですかね。
では、次にfluentd pluginを見てみます。コードは、https://github.com/stanaka/fluent-plugin-tail-labeled-tsvにあります(近々Gem化します)。fluentdでの定義は、
<source> type tail_labeled_tsv path /var/log/nginx/access_log.ltsv tag access_log pos_file /tmp/fluent.log.pos </source>
のようになります。in_tail pluginのように非標準のフォーマットでも頑張って定義を正規表現で書く必要がありません。Webサーバー側での定義を変更してもfluentd側の設定を触らずにすむのも便利です。
さらにApache HadoopのHive用のSerDe(シリアライザ・デシリアライザ)も用意しています。コードは、 https://github.com/hatena/KeyValuePairsDeserializer にあります。
これにより、Amazon EMR上のHiveで簡単にテーブルを定義することができます。
ADD JAR s3://sample/_lib/hadoop/hive/serde/hatena-serde.jar; CREATE EXTERNAL TABLE logs ( time string, host string, req string, status string, size string, referer string, ua string ) PARTITIONED BY (dt string) ROW FORMAT SERDE 'jp.ne.hatena.hadoop.hive.serde.KeyValuePairsDeserializer'; ALTER TABLE sample ADD PARTITION (dt='2013-01-01') LOCATION 's3://sample/sample_log.ltsv.gz';
その後、Hiveにより、MapReduceでログ解析を行うことができます。例えば、
select count(distinct referer) from logs
のようなHiveQLを流すことで、1年分のログでも、がりっと処理することができます。
まとめ
はてなでは、Labeled Tab Separated Valueによるログフォーマットで統一していて、one-linerからfluentd、Amazon EMR上のHiveまで使えて便利!という話でした。