fluentd

fluentdでout_fileが出力するファイルのパスを固定するpluginを作成する

背景、目的

WindowsServer上のシステムでログファイルが複数個に分かれているものがありました。
それぞれのログファイルを監視したいが、監視システムで監視対象ファイルを追加するためには追加費用が必要なため、ログファイルを一つに集約してファイル監視を行うことにしました。

fluentdの標準範囲で設定をしてみたところ、Merge.log.20171020.logというように出力日付がログファイル名に自動的に付与されます。
これでは、監視システムで監視対象のファイル名を固定することができません。

非Windows環境では、symlink_pathでアクティブなファイルへのシンボリックリンクが作成され、ファイル名を固定化できるようですが、Windows環境は未対応のようです。

fluentdでout_fileで出力されるファイルのパスを固定する方法でプラグインを作られている方がいましたので使わさせて頂きましたが、何かエラーが出たので諦めました。

ちょうどいい解決策を見つけられなかったので、プラグインを書こうと思いここまでに至りました。

環境

  • Windows
  • fluentd-0.14.13
  • git

作っていきます

gemスケルトン作成(Generating plugin project skeleton)

Td-agent Command Prompt から実行していきます。

C:\opt\td-agent>fluent-plugin-generate output fixfile
License: Apache-2.0
        create Gemfile
        create README.md
        create Rakefile
        create fluent-plugin-fixfile.gemspec
        create lib/fluent/plugin/out_fixfile.rb
        create test/helper.rb
        create test/plugin/test_out_fixfile.rb
Initialized empty Git repository in C:/opt/td-agent/fluent-plugin-fixfile/.git/

C:\opt\td-agent>cd fluent-plugin-out-fixfile
C:\opt\td-agent\fluent-plugin-fixfile>git add --all
warning: LF will be replaced by CRLF in Gemfile.
The file will have its original line endings in your working directory.
warning: LF will be replaced by CRLF in Rakefile.
The file will have its original line endings in your working directory.

C:\opt\td-agent\fluent-plugin-fixfile>git commit -m "initial commit"
[master (root-commit) 6545bd6] initial commit
 8 files changed, 338 insertions(+)
 create mode 100644 Gemfile
 create mode 100644 LICENSE
 create mode 100644 README.md
 create mode 100644 Rakefile
 create mode 100644 fluent-plugin-fixfile.gemspec
 create mode 100644 lib/fluent/plugin/out_fixfile.rb
 create mode 100644 test/helper.rb
 create mode 100644 test/plugin/test_out_fixfile.rb

 C:\opt\td-agent\fluent-plugin-fixfile>

テストを書く

スケルトンでテストを実行してみました

C:\opt\td-agent\fluent-plugin-fixfile>rake test
C:/opt/td-agent/embedded/bin/ruby.exe -w -I"lib;lib;test" -I"C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib" "C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib/rake/rake_test_loader.rb" "test/plugin/test_out_fixfile.rb"
Loaded suite C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib/rake/rake_test_loader
Started
F
================================================================================

Failure: Flunked.
test: failure(FixfileOutputTest)
C:/opt/td-agent/fluent-plugin-fixfile/test/plugin/test_out_fixfile.rb:27:in `block in <class:FixfileOutputTest>'
     24:   }
     25:
     26:   test "failure" do
  => 27:     flunk
     28:   end
     29:
     30:   private
================================================================================



Finished in 0.050005 seconds.
--------------------------------------------------------------------------------

1 tests, 1 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
0% passed
--------------------------------------------------------------------------------

20.00 tests/s, 20.00 assertions/s
rake aborted!
Command failed with status (1): [ruby -w -I"lib;lib;test" -I"C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib" "C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib/rake/rake_test_loader.rb" "test/plugin/test_out_fixfile.rb" ]

Tasks: TOP => test
(See full trace by running task with --trace)

C:\opt\td-agent\fluent-plugin-fixfile>

out_fileのテストを参考に書いてみます。

test_out_fixfile.rb
require "helper"
require "fluent/plugin/out_fixfile.rb"
require "fileutils"
require "time"
require "timecop"
require "zlib"

class FixfileOutputTest < Test::Unit::TestCase
  setup do
    Fluent::Test.setup
    FileUtils.rm_rf(TMP_DIR)
    FileUtils.mkdir_p(TMP_DIR)
  end

  TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp/out_fixfile#{ENV['TEST_ENV_NUMBER']}")

  CONFIG = %{
    path #{TMP_DIR}/out_fixfile_test
    compress gz
    utc
    <buffer>
      timekey_use_utc true
    </buffer>
  }

  def check_gzipped_result(path, expect)
    result = ''
    File.open(path, "rb") { |io|
      loop do
        gzr = Zlib::GzipReader.new(io)
        result << gzr.read
        unused = gzr.unused
        gzr.finish
        break if unused.nil?
        io.pos -= unused.length
      end
    }
    assert_equal expect, result
  end

  sub_test_case 'write' do
    test 'basic case' do
      d = create_driver

      assert_false File.exist?("#{TMP_DIR}/out_fixfile_test.20110102_0.log.gz")

      time = event_time("2011-01-02 13:14:15 UTC")
      d.run(default_tag: 'test') do
        d.feed(time, {"a"=>1})
        d.feed(time, {"a"=>2})
      end

      assert File.exist?("#{TMP_DIR}/out_fixfile_test.20110102_0.log.gz")
      check_gzipped_result("#{TMP_DIR}/out_fixfile_test.20110102_0.log.gz", %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] + %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n])
    end
  end

  private

  def create_driver(conf = CONFIG, opts = {})
    Fluent::Test::Driver::Output.new(Fluent::Plugin::FixfileOutput).configure(conf)
  end
end

実行してみます

C:\opt\td-agent\fluent-plugin-fixfile>rake test
C:/opt/td-agent/embedded/bin/ruby.exe -w -I"lib;lib;test" -I"C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib" "C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib/rake/rake_test_loader.rb" "test/plugin/test_out_fixfile.rb"
Loaded suite C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib/rake/rake_test_loader
Started
E
================================================================================

Error: test: basic case(FixfileOutputTest::write): RuntimeError: BUG: output plugin must implement some methods. see developer documents.
C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/fluentd-0.14.13/lib/fluent/plugin/output.rb:228:in `configure'
C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/fluentd-0.14.13/lib/fluent/test/driver/base_owner.rb:57:in `configure'
C:/opt/td-agent/fluent-plugin-fixfile/test/plugin/test_out_fixfile.rb:61:in `create_driver'
C:/opt/td-agent/fluent-plugin-fixfile/test/plugin/test_out_fixfile.rb:43:in `block (2 levels) in <class:FixfileOutputTest>'
================================================================================



Finished in 0.023002 seconds.
--------------------------------------------------------------------------------

1 tests, 0 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications
0% passed
--------------------------------------------------------------------------------

43.47 tests/s, 0.00 assertions/s
rake aborted!
Command failed with status (1): [ruby -w -I"lib;lib;test" -I"C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib" "C:/opt/td-agent/embedded/lib/ruby/gems/2.3.0/gems/rake-10.4.2/lib/rake/rake_test_loader.rb" "test/plugin/test_out_fixfile.rb" ]

Tasks: TOP => test
(See full trace by running task with --trace)

C:\opt\td-agent\fluent-plugin-fixfile>

本体にメソッドを定義しろと言われたので書いていきます。
書くのは、最低限の枠組みだけです。
本件では、out_fileの出力先を固定したいだけなので、FileOutputを継承する形で作ります。

out_fixfile.rb
require "fluent/plugin/output"
require "fluent/plugin/out_file"

module Fluent
  module Plugin
    class FixfileOutput < Fluent::Plugin::FileOutput
      Fluent::Plugin.register_output("fixfile", self)

      def configure(conf)
        super
      end

      def start
        super
      end

      def shutdown
        super
      end

      def format(tag, time, record)
        super
      end

      def write(chunk)
        super
      end
    end
  end
end

テストを実行し、OKだったので本来の実装を始めます。

Started
.

Finished in 1.393139 seconds.
--------------------------------------------------------------------------------

1 tests, 3 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
--------------------------------------------------------------------------------

0.72 tests/s, 2.15 assertions/s

実装した

https://github.com/soulhead1mg/fluent-plugin-fixfile

gemをビルドする

C:\opt\td-agent\fluent-plugin-fixfile>rake build
fluent-plugin-fixfile 0.1.0 built to pkg/fluent-plugin-fixfile-0.1.0.gem.

C:\opt\td-agent\fluent-plugin-fixfile>

td-agent にインストールする

C:\opt\td-agent\fluent-plugin-fixfile>fluent-gem install pkg/fluent-plugin-fixfi
le-0.1.0.gem
Successfully installed fluent-plugin-fixfile-0.1.0
Parsing documentation for fluent-plugin-fixfile-0.1.0
Installing ri documentation for fluent-plugin-fixfile-0.1.0
Done installing documentation for fluent-plugin-fixfile after 3 seconds
1 gem installed

C:\opt\td-agent\fluent-plugin-fixfile>

参考にしたサイト

その他

  • ruby, gem などなどよくわかってない状態から手探りで作りました
  • bundle gemで始めて一通り作りました
  • ここに書くためにもう一度初めから参考サイトなどを見ながらもう一度やり直したら、公式ドキュメントのfluent-plugin-generateに気づきやり方を変えました
  • bundle gemでやった時に苦労したところに遭遇しないようになりました。以下苦労したところ
lib/fluent/plugin/buf_file.rb
# line 95
def buffer_path_for_test?
  caller_locations.each do |location|
    # Thread::Backtrace::Location#path returns base filename or absolute path.
    # #absolute_path returns absolute_path always.
    # https://bugs.ruby-lang.org/issues/12159
    if location.absolute_path =~ /\/test_[^\/]+\.rb$/ # location.path =~ /test_.+\.rb$/
      return true
    end
  end
  false
end