背景、目的
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
のテストを参考に書いてみます。
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
を継承する形で作ります。
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
実装した
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]他言語使用者のためのRuby入門知識まとめ
- すっかり時代に乗り遅れてるけど fluentd が必要になったので test も書きながら output プラグインを書いているその記録
- fluentd/test/plugin/test_out_file.rb
- Writing plugins
その他
- ruby, gem などなどよくわかってない状態から手探りで作りました
-
bundle gem
で始めて一通り作りました - ここに書くためにもう一度初めから参考サイトなどを見ながらもう一度やり直したら、公式ドキュメントの
fluent-plugin-generate
に気づきやり方を変えました -
bundle gem
でやった時に苦労したところに遭遇しないようになりました。以下苦労したところ - テストコードで
helper
を読み込めず?event_time
が使えない →time = Time.parse("2011-01-02 13:14:15 UTC").to_i
と書いた -
create_driver
を2回以上呼び出すとsame buffer
とエラーになる → テストのファイル名を*_test.rb
としておりテストコードと認識されなかった。正しくは、test_*.rb
と名前をつける必要があった。
http://www.rubydoc.info/gems/fluentd/0.14.4/Fluent/Plugin/FileBuffer#buffer_path_for_test%3F-instance_method
# 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