2015年5月18日月曜日

log4j2 で出力した Json ログを LogStash でパースしてみた

概要

第一弾としてElasticSearch + LogStash の連携を紹介しました
第二弾ではlog4j2でJson形式のログを出力できるようにしました
そして今回第三弾としてlog4j2で出力したJsonログをLogStashで拾ってElasticSearchに投げるところまでやってみたいと思います

環境

  • CentOS 6.6 64bit
  • Java 1.8.0_25
  • ElasticSearch 1.5.2
  • LogStash 1.4.2
  • log4j2 2.3

事前準備

ElasticSearch + LogStashのインストールおよび設定は第一弾の記事を参考に構築してください
log4j2のJSONLayoutの設定は第二弾を参考にコーディングしてください
今回は上記が完了している体で話を進めます

log4j2でファイルにJsonログを出力できるようにする

log4j2.xmlappendersタグに以下を追加します

<RollingFile name="RollingFile" fileName="logs/rolling_app.json" filePattern="logs/rolling_app_%d{yyyy-MM-dd}.json">
  <JSONLayout compact="true" eventEol="true" locationInfo="true">
  </JSONLayout>
  <Policies>
    <TimeBasedTriggeringPolicy />
  </Policies>
</RollingFile>

このAppenderはJsonのログを出力する専用のAppenderになります
日時でローテートするように設定しています

loggersには「RollingFile」という名前のappenderを参照するloggerを定義しましょう

<appender-ref ref="RollingFile" />

パッケージやログレベルは適宜調整してください

これを定義した状態で一度実行してみましょう
うまくいっていればプロジェクト配下に logs/rolling_app.json ができています

messageのフォーマットを決定する

今回はJava側でロギングするときにはスペースで区切らたデータをロギングするようにします
例えば以下のような感じで3つの項目をロギングするようにします

log.trace("kakakikikeke 20 Japan");

(この辺がだいぶいけてないのですが)上記のようにロギングすると「message」というフィールドの値としてJson出力されます
値は必ず文字列として出力されます

LogStash でJsonログをパースするconfigファイルを作成する

Java側が出力するJsonログをパースするconfigファイルは以下のような感じです

input {
  file {
    type => 'my-type'
    path => '/path/to/log/rolling_app.json'
    codec => json
  }
}

filter {
  date {
    match => [ "timeMillis", "UNIX_MS" ]
    target => "@timestamp"
    locale => "ja"
    timezone => "Asia/Tokyo"
  }
  grok {
    match => [ "message", "%{GREEDYDATA:username} %{GREEDYDATA:age} %{GREEDYDATA:country} ]
  }
}

output {
  stdout {
    codec => rubydebug
  }
  elasticsearch {
    host => localhost
  }
}

ポイントはfilter -> grok -> matchの部分です
Jsonロギングされたmessageフィールドの情報を正規表現を使ってマッチさせることができます
今回はスペースで区切られたフィールドをパースするだけなので正規表現は使っていません
「GREEDYDATA」の部分はパースした値の型を指定することができます
今回は全部 GREEDYDATA を指定していますが数字ならば「NUMBER」短い単語ならば「WORD」なども使うことができます
試していて「_grokparsefailure」のエラーになる場合は全部「GREEDYDATA」にしておけばOKかと思います
(もしかしたらちゃんと型を指定することで何かしらの恩知が受けれるかもしれません)

各種起動

ElasticSearch -> LogStash -> log4j2 の順番で起動していきます
ElasticSearchは特に設定ファイル等は何もいじっていないのでそのまま起動すればOKです
LogStashは作成した上記の設定ファイルを指定して起動しましょう

ElasticSearch と LogStash が起動できたらJsonロギングできるJavaアプリを起動させてJsonロギングさせてみましょう
LogStashは「rubydebug」しているのでログをうまくパースできればコンソール上に表示されると思います
最終的にはElasticSearchにちゃんとデータが入っているか確認したほうがいいでしょう

curl 'http://localhost:9200/_search?pretty'

とりあえずこれでデータが入っていればひと通りの連携は完了です

最後に

いけてないのはJsonロギングするJava側が必ず同じフォーマットでロギングしなければいけない点です
今回は3項目出力しましたが、これが4つとかになったらコードを改修しなければいけないですしLogStash側のconfigファイルも修正が必要です
ネットを調べているとmessageフィールドにJSONオブジェクトを出力させてLogStash側でRubyスクリプトを組んでログをパースする方法が紹介されていました
http://kapaski.github.io/blog/2014/07/24/logstash-to-parse-json-with-json-arrays-in-values/
これ自体もかなり力技なのですが、log4j2を使ってJson出力するとmessageフィールドが必ず文字列で出力されてしまいます
なので上記の方法すら使うことができません

またLogStash側のgrokもmessageフィールド以外をパースすることができない(?)みたいでlog4j2のThreadContextを使って別のフィールドにJsonオブジェクトを出力してもそれをパースすることができません
(ちょっとこの辺は調査不足かもしれませんが自分はできずに諦めました)

という理由もあって今回はスペースで区切った値をロギングをすることでそれをLogStash側でパースしたのですが、そもそもスペースで区切るならロギングするJava側もJsonで出力する必要はなかったんですよね
スペース区切りのロギングをしてそのファイルをLogStash側でパースするだけなので
わざわざJsonにして良かった点はLogStashのinputのcodecにjsonが使えたことくらいでしょうか

0 件のコメント:

コメントを投稿