require "helper" require "tempfile" require "fluent/plugin/parser_grok" def str2time(str_time, format = nil) if format Time.strptime(str_time, format).to_i else Time.parse(str_time).to_i end end class GrokParserTest < ::Test::Unit::TestCase def setup Fluent::Test.setup end class Timestamp < self def test_timestamp_iso8601 internal_test_grok_pattern("%{TIMESTAMP_ISO8601:time}", "Some stuff at 2014-01-01T00:00:00+0900", event_time("2014-01-01T00:00:00+0900"), {}) end def test_datestamp_rfc822_with_zone internal_test_grok_pattern("%{DATESTAMP_RFC822:time}", "Some stuff at Mon Aug 15 2005 15:52:01 UTC", event_time("Mon Aug 15 2005 15:52:01 UTC"), {}) end def test_datestamp_rfc822_with_numeric_zone internal_test_grok_pattern("%{DATESTAMP_RFC2822:time}", "Some stuff at Mon, 15 Aug 2005 15:52:01 +0000", event_time("Mon, 15 Aug 2005 15:52:01 +0000"), {}) end def test_syslogtimestamp internal_test_grok_pattern("%{SYSLOGTIMESTAMP:time}", "Some stuff at Aug 01 00:00:00", event_time("Aug 01 00:00:00"), {}) end end def test_call_for_grok_pattern_not_found assert_raise Fluent::Grok::GrokPatternNotFoundError do internal_test_grok_pattern("%{THIS_PATTERN_DOESNT_EXIST}", "Some stuff at somewhere", nil, {}) end end def test_call_for_multiple_fields internal_test_grok_pattern("%{MAC:mac_address} %{IP:ip_address}", "this.wont.match DEAD.BEEF.1234 127.0.0.1", nil, {"mac_address" => "DEAD.BEEF.1234", "ip_address" => "127.0.0.1"}) end def test_call_for_complex_pattern internal_test_grok_pattern("%{COMBINEDAPACHELOG}", '127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 +0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0"', str2time("28/Feb/2013:12:00:00 +0900", "%d/%b/%Y:%H:%M:%S %z"), { "clientip" => "127.0.0.1", "ident" => "192.168.0.1", "auth" => "-", "verb" => "GET", "request" => "/", "httpversion" => "1.1", "response" => "200", "bytes" => "777", "referrer" => "\"-\"", "agent" => "\"Opera/12.0\"" }, "time_key" => "timestamp", "time_format" => "%d/%b/%Y:%H:%M:%S %z" ) end def test_call_for_custom_pattern pattern_file = File.new(File.expand_path("../my_pattern", __FILE__), "w") pattern_file.write("MY_AWESOME_PATTERN %{GREEDYDATA:message}\n") pattern_file.close begin internal_test_grok_pattern("%{MY_AWESOME_PATTERN:message}", "this is awesome", nil, {"message" => "this is awesome"}, "custom_pattern_path" => pattern_file.path ) ensure File.delete(pattern_file.path) end end class OptionalType < self def test_simple internal_test_grok_pattern("%{INT:user_id:integer} paid %{NUMBER:paid_amount:float}", "12345 paid 6789.10", nil, {"user_id" => 12345, "paid_amount" => 6789.1 }) end def test_array internal_test_grok_pattern("%{GREEDYDATA:message:array}", "a,b,c,d", nil, {"message" => %w(a b c d)}) end def test_array_with_delimiter internal_test_grok_pattern("%{GREEDYDATA:message:array:|}", "a|b|c|d", nil, {"message" => %w(a b c d)}) end def test_timestamp_iso8601 internal_test_grok_pattern("%{TIMESTAMP_ISO8601:stamp:time}", "Some stuff at 2014-01-01T00:00:00+0900", nil, {"stamp" => event_time("2014-01-01T00:00:00+0900")}) end def test_datestamp_rfc822_with_zone internal_test_grok_pattern("%{DATESTAMP_RFC822:stamp:time}", "Some stuff at Mon Aug 15 2005 15:52:01 UTC", nil, {"stamp" => event_time("Mon Aug 15 2005 15:52:01 UTC")}) end def test_datestamp_rfc822_with_numeric_zone internal_test_grok_pattern("%{DATESTAMP_RFC2822:stamp:time}", "Some stuff at Mon, 15 Aug 2005 15:52:01 +0000", nil, {"stamp" => event_time("Mon, 15 Aug 2005 15:52:01 +0000")}) end def test_syslogtimestamp internal_test_grok_pattern("%{SYSLOGTIMESTAMP:stamp:time}", "Some stuff at Aug 01 00:00:00", nil, {"stamp" => event_time("Aug 01 00:00:00")}) end def test_timestamp_with_format internal_test_grok_pattern("%{TIMESTAMP_ISO8601:stamp:time:%Y-%m-%d %H%M}", "Some stuff at 2014-01-01 1000", nil, {"stamp" => event_time("2014-01-01 10:00")}) end end class NoGrokPatternMatched < self def test_with_grok_failure_key config = %[ grok_failure_key grok_failure pattern %{PATH:path} ] expected = { "grok_failure" => "No grok pattern matched", "message" => "no such pattern" } d = create_driver(config) d.instance.parse("no such pattern") do |_time, record| assert_equal(expected, record) end end def test_without_grok_failure_key config = %[ pattern %{PATH:path} ] expected = { "message" => "no such pattern" } d = create_driver(config) d.instance.parse("no such pattern") do |_time, record| assert_equal(expected, record) end end end def test_no_grok_patterns assert_raise Fluent::ConfigError do create_driver('') end end def test_invalid_config_value_type assert_raise Fluent::ConfigError do create_driver(%[ pattern %{PATH:path:foo} ]) end end def test_invalid_config_value_type_and_normal_grok_pattern d = create_driver(%[ pattern %{PATH:path:foo} pattern %{IP:ip_address} ]) assert_equal(1, d.instance.instance_variable_get(:@grok).parsers.size) logs = $log.instance_variable_get(:@logger).instance_variable_get(:@logdev).logs error_logs = logs.grep(/error_class/) assert_equal(1, error_logs.size) error_message = error_logs.first[/error="(.+)"/, 1] assert_equal("unknown value conversion for key:'path', type:'foo'", error_message) end private def create_driver(conf) Fluent::Test::Driver::Parser.new(Fluent::Plugin::GrokParser).configure(conf) end def internal_test_grok_pattern(grok_pattern, text, expected_time, expected_record, options = {}) d = create_driver({"grok_pattern" => grok_pattern}.merge(options)) # for the new API d.instance.parse(text) {|time, record| assert_equal(expected_time, time) if expected_time assert_equal(expected_record, record) } end end