fluentdの設定ファイルをテストしたい

はじめに

Webサーバーからのログ収集などでfluentdを使うことがあって たまにfluentd設定ファイルを書くことがあるんですが、 たまにしか書かないので全然書き方が覚えられず苦労したりすることがあったので もうすこしどうにかならないかとツールを作ってみました。

難しさ

設定の書き方がわからないというのもあるんですが Webサーバーなど設置する環境にも不慣れなこともあって難しくなっているのかなと思います。

ログが正しく流れているのを確認するのはなかなか大変で

  • サーバーをセットアップ
  • fluentdやプラグインをサーバーにインストール
  • ログを送信するサーバーアプリケーションをデプロイ
  • アプリ操作などで実際にログを流す
  • s3などにログが流れるのを確認する

上のような作業が必要になると思います。

さらに色々なフェーズで問題があって正しく送れないことがあります。

  • fluentdの設定の問題
  • サーバーアプリケーションの設定の問題
  • s3やCloudWatchやBigQueryなどログ受信側の設定の問題
  • WebサーバーのIAM権限などの問題

ということで環境に関わる問題と切り離せないと非常につらいので できれば環境と切り離して設定をテストできるとうれしいのではないかと思います。

--dry-runのオプションである程度はテストできるのですがもう少し色々テストできて欲しいです。

あるログが入力から流れてきたときにどの出力にどんなログが流れるかというのが見られると fluentdの設定を書くのがかなりやりやすくなると思います。

どうやるか

適当にログ出力部分のメソッドを書き換えれば試せるのではないかという方針です。

Fluentd ソースコード完全解説 · GitHub

こちらの解説を見た感じInputプラグインとOutputプラグインというのがあって

という感じでしょうか。

Outputの生成にはFluent::Plugin.new_outputというメソッドが使われていたので この生成の段階でメソッド書き換えを行うことにしました。

Input側は特に書き換えなくてもemitというログを流すメソッドが呼び出せさえすればいいので 特に書き換えなくてもいいと思います。

問題

上の方針でやってみたところ一つ問題がありました。

Outputプラグインの種類によっては外部にログを投げない役割のものがありました。

例えばcopyやrelabelやforestなどです。

copyやrelabelの段階でメソッドを書き換えてしまうと その後まだログを外部に投げる段階まで来ていないところで終わってしまうので 有用な情報が得られません。

結局あまり良い方法が思いつかず copyやrelabelの場合はメソッドを書き換えないという適当な方法で対処しました。

作ったツール

https://github.com/bigsleep/logflowtester

こちらが作ったツールのソースです。

環境を切り離してテストできたほうがいいということでdockerコンテナで実行することにしました。

ruby全然知らないので色々と問題があるかもしれません。 設定ファイルやプラグインによっては上手く動かないこともあると思います。

テスト用設定

<source>
  @type forward
  port 24224
  @label @raw
</source>

<source>
  @type tail
  path /var/log/httpd-access.log
  pos_file /var/log/td-agent/httpd-access.log.pos
  tag apache.access
  format apache2
  @label @logging
</source>

<label @raw>
  <match app.**>
    @type copy
    <store>
      @type relabel
      @label @app
    </store>
    <store>
      @type relabel
      @label @logging
    </store>
  </match>
  <match **>
    @type relabel
    @label @logging
  </match>
</label>

<label @app>
  <filter **>
    @type record_transformer
    <record>
      tag ${tag}
    </record>
  </filter>

  <match app.user.**>
    @type kinesis_streams
    region ap-northeast-1
    stream_name log-stream-xxx
  </match>
  <match **>
    @type file
    path /var/log/fluentd/out_file_test
    format json
    buffer_type memory
    compress gzip
    symlink_path /path/to/symlink
    append false
  </match>
</label>

<label @logging>
  <match **>
    @type s3

    s3_bucket YOUR_S3_BUCKET_NAME

    path logs/${tag}/%Y/%m/%d/
    s3_object_key_format %{path}%{time_slice}_%{index}.%{file_extension}

    <buffer tag,time>
      @type file
      path /var/log/fluent/s3
      timekey 3600 # 1 hour partition
      timekey_wait 10m
      timekey_use_utc true # use utc
    </buffer>
  </match>
</label>

ツール実行結果

$ docker run -v $(pwd):/data logflowtester /data/td-agent.conf '[{"tag":"app.user.action","source_index":0,"record":{"user":"xxx","action":"aaa"}},{"tag":"apache.access","source_index":1,"record":{"value":"aaa"}},{"tag":"error","source_index":0,"record":{"user":"user0","error":"fatal"}}]'
Result:
  - output: #<Fluent::KinesisStreamsOutput:000000010d3198>
  - tag: app.user.action
  - es: #<Fluent::MultiEventStream:0x0000000000950380 @time_array=[#<Fluent::EventTime:0x0000000000955920 @sec=1518873206, @nsec=635850381>], @record_array=[{"user"=>"xxx", "action"=>"aaa", "tag"=>"app.user.action"}]>
Result:
  - output: #<Fluent::Plugin::S3Output:000000019eb618>
  - tag: app.user.action
  - es: #<Fluent::OneEventStream:0x0000000000955150 @time=#<Fluent::EventTime:0x0000000000955920 @sec=1518873206, @nsec=635850381>, @record={"user"=>"xxx", "action"=>"aaa"}>
Result:
  - output: #<Fluent::Plugin::S3Output:000000019eb618>
  - tag: apache.access
  - es: #<Fluent::OneEventStream:0x00000000008ad3b0 @time=#<Fluent::EventTime:0x00000000008ad428 @sec=1518873206, @nsec=636324323>, @record={"value"=>"aaa"}>
Result:
  - output: #<Fluent::Plugin::S3Output:000000019eb618>
  - tag: error
  - es: #<Fluent::OneEventStream:0x00000000008ab9e8 @time=#<Fluent::EventTime:0x00000000008aba88 @sec=1518873206, @nsec=636373197>, @record={"user"=>"user0", "error"=>"fatal"}>

まとめ

fluentd設定テスト用のツールを書いてみました。 間に合せで作ったような状態なのでよりいいツールやWeb上でテストできるサービスなどがあるとうれしいです。