超初級!Fluentdのプラグインを書きたくなった時の下地づくり

fluentdをまともに動かしたことないけど
プラグイン書いたらわかるのでは!!?
と思い立って取り敢えず下地だけつくったのでメモ。

参考

基本はもりす先生の手順にそって行えば問題なし。
参考にオフィシャルのドキュメントと
@fujiwaraさんのシンプルなプラグイン
@studio3104さんの下地を見ながら書くとなおよし。

また、rake testまで通したものを

https://github.com/kenjiskywalker/fluent-plugin-hoge

こちらに上げておきました。ここから遊んでみても良いのではないかと思います。

作成手順

bunldeで作成

$ bundle gem fluent-plugin-hoge
$ cd fluent-plugin-hoge/
$ mkdir -p lib/fluent/plugin
$ mv lib/fluent-plugin-hoge.rb lib/fluent/plugin/out_hoge.rb
$ rm lib/fluent-plugin-hoge/version.rb
$ rmdir lib/fluent-plugin-hoge
$ mkdir -p test/plugin
$ touch test/plugin/test_out_hoge.rb

gemspecファイルの修正

fluent-plugin-hoge.gemspec

# -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |gem|
  gem.name          = "fluent-plugin-hoge"
  gem.version       = "0.0.1"
  gem.authors       = ["kenjiskywalker"]
  gem.email         = ["git@kenjiskywalker.org"]
  gem.description   = %q{TODO: Write a gem description}
  gem.summary       = %q{TODO: Write a gem summary}
  gem.homepage      = ""

  gem.files         = `git ls-files`.split($/)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.require_paths = ["lib"]
  gem.add_development_dependency "fluentd"
  gem.add_runtime_dependency "fluentd"
end

version周りの設定の変更とfluentdの依存周りの設定の追加

out_hoge.rbの作成

fluent-plugin-hoge/lib/fluent/plugin/out_hoge.rb

class Fluent::HogeOutput < Fluent::BufferedOutput
  Fluent::Plugin.register_output('hoge', self)

  include Fluent::SetTagKeyMixin
  config_set_default :include_tag_key, false

  include Fluent::SetTimeKeyMixin
  config_set_default :include_time_key, true

  # config_param :hoge, :string, :default => 'hoge'

  def initialize
    super
    # require 'hogepos'
  end

  def configure(conf)
    super
    # @path = conf['path']
  end

  def start
    super
    # init
  end

  def shutdown
    super
    # destroy
  end

  def format(tag, time, record)
    [tag, time, record].to_msgpack
  end

  def write(chunk)
    records = []
    chunk.msgpack_each { |record|
      # records << record
    }
    # write records
  end
end

test周りの設定

fluent-plugin-hoge/test/helper.rb

require 'rubygems'
require 'bundler'
begin
  Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
  $stderr.puts e.message
  $stderr.puts "Run `bundle install` to install missing gems"
  exit e.status_code
end
require 'test/unit'

$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'fluent/test'
unless ENV.has_key?('VERBOSE')
  nulllogger = Object.new
  nulllogger.instance_eval {|obj|
    def method_missing(method, *args)
      # pass
    end
  }
  $log = nulllogger
end

require 'fluent/plugin/out_hoge'

class Test::Unit::TestCase
end

fluent-plugin-hoge/test/plugin/test_out_hoge.rb

require 'helper'
# require 'time'

class HogeOutputTest < Test::Unit::TestCase
  # TMP_DIR = File.dirname(__FILE__) + "/../tmp"

  def setup
    Fluent::Test.setup
    # FileUtils.rm_rf(TMP_DIR)
    # FileUtils.mkdir_p(TMP_DIR)
  end

  CONFIG = %[
  ]
  # CONFIG = %[
  #   path #{TMP_DIR}/out_file_test
  #   compress gz
  #   utc
  # ]

  def create_driver(conf = CONFIG)
    Fluent::Test::BufferedOutputTestDriver.new(Fluent::HogeOutput).configure(conf)
  end

  def test_configure
    #### set configurations
    # d = create_driver %[
    #   path test_path
    #   compress gz
    # ]
    #### check configurations
    # assert_equal 'test_path', d.instance.path
    # assert_equal :gz, d.instance.compress
  end

  def test_format
    d = create_driver

    # time = Time.parse("2011-01-02 13:14:15 UTC").to_i
    # d.emit({"a"=>1}, time)
    # d.emit({"a"=>2}, time)

    # d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n]
    # d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]

    # d.run
  end

  def test_write
    d = create_driver

    # time = Time.parse("2011-01-02 13:14:15 UTC").to_i
    # d.emit({"a"=>1}, time)
    # d.emit({"a"=>2}, time)

    # ### FileOutput#write returns path
    # path = d.run
    # expect_path = "#{TMP_DIR}/out_file_test._0.log.gz"
    # assert_equal expect_path, path

    # data = Zlib::GzipReader.open(expect_path) {|f| f.read }
    # assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
    #                 %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n],
    #              data
  end
end

Rakefileの設定

Rakefile

#!/usr/bin/env rake
require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new(:test) do |test|
  test.libs << 'lib' << 'test'
  test.pattern = 'test/**/test_*.rb'
  test.verbose = true
end

task :default => :test

rake testの実行

$ rake test
# Running tests:

...

Finished tests in 0.065174s, 46.0306 tests/s, 0.0000 assertions/s.

3 tests, 0 assertions, 0 failures, 0 errors, 0 skips

うおおおおおおおおお!!!自分で何も書いてないけどテストが通った喜びッッ!!!

ということで、ここからつくりあげていく感じになるでしょうか。
当初、何もわかっていない状態でfluent-plugin-postfix-loggerなどと
ドヤ顔でつくりはじめたのですが、ファイルの関係性とか全く理解していない状態で
ややこしいファイル名にすると間違っている箇所が特定しづらいので
最初はfluent-plugin-hogeなどと作成して、最低限自分が確認したい状態まで
持っていった上で確認していくのがいいという、初心者っぽい気付きがありました。

初心者ながら地道にやっていきたいということで超初級の恥ずかしいメモを
アップしました。誤りなどありましたら教えてくださいませ!