While it is not hard to write a regular expression to match this format, it is tricky to extract and save key names.
Here is the code to parse this custom format (let's call it time_key_value). It takes one optional parameter called delimiter, which is the delimiter for key/value pairs. It also takes time_format to parse the time string.
require'fluent/plugin/parser'moduleFluent::PluginclassTimeKeyValueParser<Parser# Register this parser as 'time_key_value'Fluent::Plugin.register_parser('time_key_value', self)# `delimiter` is configurable with ' ' as default config_param :delimiter,:string,default:' '# `time_format` is configurable config_param :time_format,:string,default:nildefconfigure(conf)superif @delimiter.length!=1raiseConfigError,"delimiter must be a single character. #{@delimiter} is not."end# `TimeParser` class is already available.# It takes a single argument as the time format# to parse the time string with. @time_parser =Fluent::TimeParser.new(@time_format)enddefparse(text) time, key_values = text.split(' ',2) time = @time_parser.parse(time) record = {} key_values.split(@delimiter).eachdo|kv| k, v = kv.split('=',2) record[k] = vendyield time, recordendendend
Save this code as parser_time_key_value.rb in a loadable plugin path.
Parser plugins have a method to parse input (text) data to a structured record (Hash) with time.
#parse(text, &block)
It gets input data as text, and call &block to feed the results of the parser. The input text may contain two or more records so that means the parser plugin might call the &block two or more times for one argument.
Parser plugins must implement this method.
Writing Tests
Fluentd parser plugin has one or more points to be tested. Others (parsing configurations, controlling buffers, retries, flushes, etc.) are controlled by the Fluentd core.
Fluentd also provides the test driver for plugins. You can easily write tests for your own plugins:
Overview of Tests
Testing for parser plugins is mainly for:
Validation of configuration (i.e. #configure)
Validation of the parsed time and record
To make testing easy, the plugin test driver provides a logger and the functionality to override the system and parser configurations, etc.
The lifecycle of plugin and test driver is:
Instantiate plugin driver which then instantiates the plugin
Configure plugin
Run test code
Assert results of tests by data provided by the driver
# in class definition
helpers :parser
# in #configure
@parser = parser_create(type: 'json')
# in input loop or #filter or ...
@parser.parse do |time, record|
# ...
end
# test/plugin/test_parser_your_own.rb
require 'test/unit'
require 'fluent/test/driver/parser'
# Your own plugin
require 'fluent/plugin/parser_your_own'
class ParserYourOwnTest < Test::Unit::TestCase
def setup
# Common setup
end
CONFIG = %[
pattern apache
]
def create_driver(conf = CONF)
Fluent::Test::Driver::Parser.new(Fluent::Plugin::YourOwnParser).configure(conf)
end
sub_test_case 'configured with invalid configurations' do
test 'empty' do
assert_raise(Fluent::ConfigError) do
create_driver('')
end
end
# ...
end
sub_test_case 'plugin will parse text' do
test 'record has a field' do
d = create_driver(CONFIG)
text = '192.168.0.1 - - [28/Feb/2013:12:00:00 +0900] "GET / HTTP/1.1" 200 777'
expected_time = event_time('28/Feb/2013:12:00:00 +0900', format: '%d/%b/%Y:%H:%M:%S %z')
expected_record = {
'method' => 'GET',
# ...
}
d.instance.parse(text) do |time, record|
assert_equal(expected_time, time)
assert_equal(expected_record, record)
end
end
end
end