fluent-plugin-geoip 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,9 +1,20 @@
1
+ sudo: required
1
2
  language: ruby
2
3
 
3
4
  rvm:
5
+ - 2.3.0
6
+ - 2.2
4
7
  - 2.1
5
8
  - 2.0.0
6
9
  - 1.9.3
7
10
 
8
11
  before_install:
12
+ - sudo apt-get update
9
13
  - sudo apt-get install libgeoip-dev
14
+ - gem update bundler
15
+
16
+ gemfile:
17
+ - Gemfile
18
+ - gemfiles/fluentd_v0.10.gemfile
19
+ - gemfiles/fluentd_v0.12.gemfile
20
+
data/Appraisals ADDED
@@ -0,0 +1,8 @@
1
+ appraise "fluentd v0.10" do
2
+ gem "fluentd", "~> 0.10.46"
3
+ end
4
+
5
+ appraise "fluentd v0.12" do
6
+ gem "fluentd", "~> 0.12.0"
7
+ end
8
+
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "fluent-plugin-geoip"
7
- spec.version = "0.5.1"
7
+ spec.version = "0.6.0"
8
8
  spec.authors = ["Kentaro Yoshida"]
9
9
  spec.email = ["y.ken.studio@gmail.com"]
10
10
  spec.summary = %q{Fluentd Output plugin to add information about geographical location of IP addresses with Maxmind GeoIP databases.}
@@ -18,12 +18,11 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.add_development_dependency "bundler"
20
20
  spec.add_development_dependency "rake"
21
-
22
- if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
23
- spec.add_development_dependency "test-unit", '~> 3'
24
- end
21
+ spec.add_development_dependency "appraisal"
22
+ spec.add_development_dependency "test-unit", ">= 3.1.0"
25
23
 
26
24
  spec.add_runtime_dependency "fluentd"
27
25
  spec.add_runtime_dependency "fluent-mixin-rewrite-tag-name"
28
26
  spec.add_runtime_dependency "geoip-c"
29
27
  end
28
+
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "fluentd", "~> 0.10.46"
6
+
7
+ gemspec :path => "../"
8
+
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "fluentd", "~> 0.12.0"
6
+
7
+ gemspec :path => "../"
8
+
@@ -0,0 +1,32 @@
1
+ module Fluent
2
+ class GeoipFilter < Filter
3
+ Plugin.register_filter('geoip', self)
4
+
5
+ config_param :geoip_database, :string, :default => File.dirname(__FILE__) + '/../../../data/GeoLiteCity.dat'
6
+ config_param :geoip_lookup_key, :string, :default => 'host'
7
+ config_param :skip_adding_null_record, :bool, :default => false
8
+
9
+ config_set_default :include_tag_key, false
10
+
11
+ config_param :hostname_command, :string, :default => 'hostname'
12
+
13
+ config_param :flush_interval, :time, :default => 0
14
+ config_param :log_level, :string, :default => 'warn'
15
+
16
+
17
+ def initialize
18
+ require 'fluent/plugin/geoip'
19
+
20
+ super
21
+ end
22
+
23
+ def configure(conf)
24
+ super
25
+ @geoip = Fluent::GeoIP.new(self, conf)
26
+ end
27
+
28
+ def filter(tag, time, record)
29
+ @geoip.add_geoip_field(record)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,150 @@
1
+ require 'geoip'
2
+ require 'yajl'
3
+
4
+ module Fluent
5
+ class GeoIP
6
+ REGEXP_PLACEHOLDER_SINGLE = /^\$\{(?<geoip_key>-?[^\[]+)\[['"](?<record_key>-?[^'"]+)['"]\]\}$/
7
+ REGEXP_PLACEHOLDER_SCAN = /['"]?(\$\{[^\}]+?\})['"]?/
8
+
9
+ GEOIP_KEYS = %w(city latitude longitude country_code3 country_code country_name dma_code area_code region)
10
+
11
+ attr_reader :log
12
+
13
+ def initialize(plugin, conf)
14
+ @map = {}
15
+ plugin.geoip_lookup_key = plugin.geoip_lookup_key.split(/\s*,\s*/)
16
+ @geoip_lookup_key = plugin.geoip_lookup_key
17
+ @skip_adding_null_record = plugin.skip_adding_null_record
18
+ @log = plugin.log
19
+
20
+ # enable_key_* format (legacy format)
21
+ conf.keys.select{|k| k =~ /^enable_key_/}.each do |key|
22
+ geoip_key = key.sub('enable_key_','')
23
+ raise Fluent::ConfigError, "geoip: unsupported key #{geoip_key}" unless GEOIP_KEYS.include?(geoip_key)
24
+ @geoip_lookup_key.zip(conf[key].split(/\s*,\s*/)).each do |lookup_field,record_key|
25
+ if record_key.nil?
26
+ raise Fluent::ConfigError, "geoip: missing value found at '#{key} #{lookup_field}'"
27
+ end
28
+ @map.store(record_key, "${#{geoip_key}['#{lookup_field}']}")
29
+ end
30
+ end
31
+ if conf.keys.select{|k| k =~ /^enable_key_/}.size > 0
32
+ log.warn "geoip: 'enable_key_*' config format is obsoleted. use <record></record> directive for now."
33
+ log.warn "geoip: for further details referable to https://github.com/y-ken/fluent-plugin-geoip"
34
+ end
35
+
36
+ # <record></record> directive
37
+ conf.elements.select { |element| element.name == 'record' }.each { |element|
38
+ element.each_pair { |k, v|
39
+ element.has_key?(k) # to suppress unread configuration warning
40
+ v = v[1..v.size-2] if quoted_value?(v)
41
+ @map[k] = v
42
+ validate_json = Proc.new {
43
+ begin
44
+ dummy_text = Yajl::Encoder.encode('dummy_text')
45
+ Yajl::Parser.parse(v.gsub(REGEXP_PLACEHOLDER_SCAN, dummy_text))
46
+ rescue Yajl::ParseError => e
47
+ raise Fluent::ConfigError, "geoip: failed to parse '#{v}' as json."
48
+ end
49
+ }
50
+ validate_json.call if json?(v.tr('\'"\\', ''))
51
+ }
52
+ }
53
+ @placeholder_keys = @map.values.join.scan(REGEXP_PLACEHOLDER_SCAN).map{ |placeholder| placeholder[0] }.uniq
54
+ @placeholder_keys.each do |key|
55
+ geoip_key = key.match(REGEXP_PLACEHOLDER_SINGLE)[:geoip_key]
56
+ raise Fluent::ConfigError, "geoip: unsupported key #{geoip_key}" unless GEOIP_KEYS.include?(geoip_key)
57
+ end
58
+
59
+ if plugin.is_a?(Fluent::Output)
60
+ @placeholder_expander = PlaceholderExpander.new
61
+ unless have_tag_option?(plugin)
62
+ raise Fluent::ConfigError, "geoip: required at least one option of 'tag', 'remove_tag_prefix', 'remove_tag_suffix', 'add_tag_prefix', 'add_tag_suffix'."
63
+ end
64
+ end
65
+
66
+ @geoip = ::GeoIP::City.new(plugin.geoip_database, :memory, false)
67
+ end
68
+
69
+ def add_geoip_field(record)
70
+ placeholder = create_placeholder(geolocate(get_address(record)))
71
+ return record if @skip_adding_null_record && placeholder.values.first.nil?
72
+ @map.each do |record_key, value|
73
+ if value.match(REGEXP_PLACEHOLDER_SINGLE)
74
+ rewrited = placeholder[value]
75
+ elsif json?(value)
76
+ rewrited = value.gsub(REGEXP_PLACEHOLDER_SCAN) {|match|
77
+ match = match[1..match.size-2] if quoted_value?(match)
78
+ Yajl::Encoder.encode(placeholder[match])
79
+ }
80
+ rewrited = parse_json(rewrited)
81
+ else
82
+ rewrited = value.gsub(REGEXP_PLACEHOLDER_SCAN, placeholder)
83
+ end
84
+ record.store(record_key, rewrited)
85
+ end
86
+ return record
87
+ end
88
+
89
+ private
90
+
91
+ def have_tag_option?(plugin)
92
+ plugin.tag ||
93
+ plugin.remove_tag_prefix || plugin.remove_tag_suffix ||
94
+ plugin.add_tag_prefix || plugin.add_tag_suffix
95
+ end
96
+
97
+ def json?(text)
98
+ text.match(/^\[.+\]$/) || text.match(/^\{.+\}$/)
99
+ end
100
+
101
+ def quoted_value?(text)
102
+ # to improbe compatibility with fluentd v1-config
103
+ trim_quote = text[1..text.size-2]
104
+ text.match(/(^'.+'$|^".+"$)/)
105
+ end
106
+
107
+ def parse_json(message)
108
+ begin
109
+ return Yajl::Parser.parse(message)
110
+ rescue Yajl::ParseError => e
111
+ log.info "geoip: failed to parse '#{message}' as json.", :error_class => e.class, :error => e.message
112
+ return nil
113
+ end
114
+ end
115
+
116
+ def get_address(record)
117
+ address = {}
118
+ @geoip_lookup_key.each do |field|
119
+ address.store(field, record[field]); next if not record[field].nil?
120
+ key = field.split('.')
121
+ obj = record
122
+ key.each {|k|
123
+ break obj = nil if not obj.has_key?(k)
124
+ obj = obj[k]
125
+ }
126
+ address.store(field, obj)
127
+ end
128
+ return address
129
+ end
130
+
131
+ def geolocate(addresses)
132
+ geodata = {}
133
+ addresses.each do |field, ip|
134
+ geo = ip.nil? ? nil : @geoip.look_up(ip)
135
+ geodata.store(field, geo)
136
+ end
137
+ return geodata
138
+ end
139
+
140
+ def create_placeholder(geodata)
141
+ placeholder = {}
142
+ @placeholder_keys.each do |placeholder_key|
143
+ position = placeholder_key.match(REGEXP_PLACEHOLDER_SINGLE)
144
+ next if position.nil? or geodata[position[:record_key]].nil?
145
+ placeholder.store(placeholder_key, geodata[position[:record_key]][position[:geoip_key].to_sym])
146
+ end
147
+ return placeholder
148
+ end
149
+ end
150
+ end
@@ -3,11 +3,6 @@ require 'fluent/mixin/rewrite_tag_name'
3
3
  class Fluent::GeoipOutput < Fluent::BufferedOutput
4
4
  Fluent::Plugin.register_output('geoip', self)
5
5
 
6
- REGEXP_PLACEHOLDER_SINGLE = /^\$\{(?<geoip_key>-?[^\[]+)\[['"](?<record_key>-?[^'"]+)['"]\]\}$/
7
- REGEXP_PLACEHOLDER_SCAN = /['"]?(\$\{[^\}]+?\})['"]?/
8
-
9
- GEOIP_KEYS = %w(city latitude longitude country_code3 country_code country_name dma_code area_code region)
10
-
11
6
  config_param :geoip_database, :string, :default => File.dirname(__FILE__) + '/../../../data/GeoLiteCity.dat'
12
7
  config_param :geoip_lookup_key, :string, :default => 'host'
13
8
  config_param :tag, :string, :default => nil
@@ -28,64 +23,23 @@ class Fluent::GeoipOutput < Fluent::BufferedOutput
28
23
  define_method("log") { $log }
29
24
  end
30
25
 
26
+ # To support Fluentd v0.10.57 or earlier
27
+ unless method_defined?(:router)
28
+ define_method("router") { Fluent::Engine }
29
+ end
30
+
31
31
  def initialize
32
- require 'geoip'
33
- require 'yajl'
32
+ require 'fluent/plugin/geoip'
34
33
 
35
34
  super
36
35
  end
37
36
 
38
37
  def configure(conf)
39
38
  super
40
-
41
- @map = {}
42
- @geoip_lookup_key = @geoip_lookup_key.split(/\s*,\s*/)
43
-
44
- # enable_key_* format (legacy format)
45
- conf.keys.select{|k| k =~ /^enable_key_/}.each do |key|
46
- geoip_key = key.sub('enable_key_','')
47
- raise Fluent::ConfigError, "geoip: unsupported key #{geoip_key}" unless GEOIP_KEYS.include?(geoip_key)
48
- @geoip_lookup_key.zip(conf[key].split(/\s*,\s*/)).each do |lookup_field,record_key|
49
- if record_key.nil?
50
- raise Fluent::ConfigError, "geoip: missing value found at '#{key} #{lookup_field}'"
51
- end
52
- @map.store(record_key, "${#{geoip_key}['#{lookup_field}']}")
53
- end
54
- end
55
- if conf.keys.select{|k| k =~ /^enable_key_/}.size > 0
56
- log.warn "geoip: 'enable_key_*' config format is obsoleted. use <record></record> directive for now."
57
- log.warn "geoip: for further details referable to https://github.com/y-ken/fluent-plugin-geoip"
58
- end
59
-
60
- # <record></record> directive
61
- conf.elements.select { |element| element.name == 'record' }.each { |element|
62
- element.each_pair { |k, v|
63
- element.has_key?(k) # to suppress unread configuration warning
64
- v = v[1..v.size-2] if quoted_value?(v)
65
- @map[k] = v
66
- validate_json = Proc.new {
67
- begin
68
- dummy_text = Yajl::Encoder.encode('dummy_text')
69
- Yajl::Parser.parse(v.gsub(REGEXP_PLACEHOLDER_SCAN, dummy_text))
70
- rescue Yajl::ParseError => e
71
- raise Fluent::ConfigError, "geoip: failed to parse '#{v}' as json."
72
- end
73
- }
74
- validate_json.call if json?(v.tr('\'"\\', ''))
75
- }
76
- }
77
- @placeholder_keys = @map.values.join.scan(REGEXP_PLACEHOLDER_SCAN).map{ |placeholder| placeholder[0] }.uniq
78
- @placeholder_keys.each do |key|
79
- geoip_key = key.match(REGEXP_PLACEHOLDER_SINGLE)[:geoip_key]
80
- raise Fluent::ConfigError, "geoip: unsupported key #{geoip_key}" unless GEOIP_KEYS.include?(geoip_key)
81
- end
82
- @placeholder_expander = PlaceholderExpander.new
83
-
84
- if ( !@tag && !@remove_tag_prefix && !@remove_tag_suffix && !@add_tag_prefix && !@add_tag_suffix )
85
- raise Fluent::ConfigError, "geoip: required at least one option of 'tag', 'remove_tag_prefix', 'remove_tag_suffix', 'add_tag_prefix', 'add_tag_suffix'."
39
+ Fluent::GeoIP.class_eval do
40
+ include Fluent::Mixin::RewriteTagName
86
41
  end
87
-
88
- @geoip = GeoIP::City.new(@geoip_database, :memory, false)
42
+ @geoip = Fluent::GeoIP.new(self, conf)
89
43
  end
90
44
 
91
45
  def start
@@ -102,82 +56,7 @@ class Fluent::GeoipOutput < Fluent::BufferedOutput
102
56
 
103
57
  def write(chunk)
104
58
  chunk.msgpack_each do |tag, time, record|
105
- Fluent::Engine.emit(tag, time, add_geoip_field(record))
106
- end
107
- end
108
-
109
- private
110
-
111
- def json?(text)
112
- text.match(/^\[.+\]$/) || text.match(/^\{.+\}$/)
113
- end
114
-
115
- def quoted_value?(text)
116
- # to improbe compatibility with fluentd v1-config
117
- trim_quote = text[1..text.size-2]
118
- text.match(/(^'.+'$|^".+"$)/)
119
- end
120
-
121
- def add_geoip_field(record)
122
- placeholder = create_placeholder(geolocate(get_address(record)))
123
- return record if @skip_adding_null_record && placeholder.values.first.nil?
124
- @map.each do |record_key, value|
125
- if value.match(REGEXP_PLACEHOLDER_SINGLE)
126
- rewrited = placeholder[value]
127
- elsif json?(value)
128
- rewrited = value.gsub(REGEXP_PLACEHOLDER_SCAN) {|match|
129
- match = match[1..match.size-2] if quoted_value?(match)
130
- Yajl::Encoder.encode(placeholder[match])
131
- }
132
- rewrited = parse_json(rewrited)
133
- else
134
- rewrited = value.gsub(REGEXP_PLACEHOLDER_SCAN, placeholder)
135
- end
136
- record.store(record_key, rewrited)
137
- end
138
- return record
139
- end
140
-
141
- def parse_json(message)
142
- begin
143
- return Yajl::Parser.parse(message)
144
- rescue Yajl::ParseError => e
145
- log.info "geoip: failed to parse '#{message}' as json.", :error_class => e.class, :error => e.message
146
- return nil
147
- end
148
- end
149
-
150
- def get_address(record)
151
- address = {}
152
- @geoip_lookup_key.each do |field|
153
- address.store(field, record[field]); next if not record[field].nil?
154
- key = field.split('.')
155
- obj = record
156
- key.each {|k|
157
- break obj = nil if not obj.has_key?(k)
158
- obj = obj[k]
159
- }
160
- address.store(field, obj)
161
- end
162
- return address
163
- end
164
-
165
- def geolocate(addresses)
166
- geodata = {}
167
- addresses.each do |field, ip|
168
- geo = ip.nil? ? nil : @geoip.look_up(ip)
169
- geodata.store(field, geo)
170
- end
171
- return geodata
172
- end
173
-
174
- def create_placeholder(geodata)
175
- placeholder = {}
176
- @placeholder_keys.each do |placeholder_key|
177
- position = placeholder_key.match(REGEXP_PLACEHOLDER_SINGLE)
178
- next if position.nil? or geodata[position[:record_key]].nil?
179
- placeholder.store(placeholder_key, geodata[position[:record_key]][position[:geoip_key].to_sym])
59
+ router.emit(tag, time, @geoip.add_geoip_field(record))
180
60
  end
181
- return placeholder
182
61
  end
183
62
  end
data/test/helper.rb CHANGED
@@ -23,6 +23,9 @@ unless ENV.has_key?('VERBOSE')
23
23
  end
24
24
 
25
25
  require 'fluent/plugin/out_geoip'
26
+ if Fluent.const_defined?(:Filter)
27
+ require 'fluent/plugin/filter_geoip'
28
+ end
26
29
 
27
30
  class Test::Unit::TestCase
28
31
  end
@@ -0,0 +1,436 @@
1
+ require 'helper'
2
+
3
+ class GeoipFilterTest < Test::Unit::TestCase
4
+ def setup
5
+ omit_unless(Fluent.const_defined?(:Filter))
6
+ Fluent::Test.setup
7
+ @time = Fluent::Engine.now
8
+ end
9
+
10
+ CONFIG = %[
11
+ geoip_lookup_key host
12
+ enable_key_city geoip_city
13
+ remove_tag_prefix input.
14
+ tag geoip.${tag}
15
+ ]
16
+
17
+ def create_driver(conf=CONFIG, tag='test', use_v1=false)
18
+ Fluent::Test::FilterTestDriver.new(Fluent::GeoipFilter, tag).configure(conf, use_v1)
19
+ end
20
+
21
+ def filter(config, messages, use_v1=false)
22
+ d = create_driver(config, 'test', use_v1)
23
+ d.run {
24
+ messages.each {|message|
25
+ d.filter(message, @time)
26
+ }
27
+ }
28
+ filtered = d.filtered_as_array
29
+ filtered.map {|m| m[2] }
30
+ end
31
+
32
+ def test_configure
33
+ assert_nothing_raised {
34
+ create_driver('')
35
+ }
36
+ assert_raise(Fluent::ConfigError) {
37
+ create_driver('enable_key_cities')
38
+ }
39
+ d = create_driver %[
40
+ enable_key_city geoip_city
41
+ remove_tag_prefix input.
42
+ tag geoip.${tag}
43
+ ]
44
+ assert_equal 'geoip_city', d.instance.config['enable_key_city']
45
+
46
+ # multiple key config
47
+ d = create_driver %[
48
+ geoip_lookup_key from.ip, to.ip
49
+ enable_key_city from_city, to_city
50
+ remove_tag_prefix input.
51
+ tag geoip.${tag}
52
+ ]
53
+ assert_equal 'from_city, to_city', d.instance.config['enable_key_city']
54
+
55
+ # multiple key config (bad configure)
56
+ assert_raise(Fluent::ConfigError) {
57
+ d = create_driver %[
58
+ geoip_lookup_key from.ip, to.ip
59
+ enable_key_city from_city
60
+ enable_key_region from_region
61
+ remove_tag_prefix input.
62
+ tag geoip.${tag}
63
+ ]
64
+ }
65
+
66
+ # invalid json structure
67
+ assert_raise(Fluent::ConfigError) {
68
+ d = create_driver %[
69
+ geoip_lookup_key host
70
+ <record>
71
+ invalid_json {"foo" => 123}
72
+ </record>
73
+ remove_tag_prefix input.
74
+ tag geoip.${tag}
75
+ ]
76
+ }
77
+ assert_raise(Fluent::ConfigError) {
78
+ d = create_driver %[
79
+ geoip_lookup_key host
80
+ <record>
81
+ invalid_json {"foo" : string, "bar" : 123}
82
+ </record>
83
+ remove_tag_prefix input.
84
+ tag geoip.${tag}
85
+ ]
86
+ }
87
+ end
88
+
89
+ def test_filter
90
+ messages = [
91
+ {'host' => '66.102.3.80', 'message' => 'valid ip'},
92
+ {'message' => 'missing field'},
93
+ ]
94
+ expected = [
95
+ {'host' => '66.102.3.80', 'message' => 'valid ip', 'geoip_city' => 'Mountain View'},
96
+ {'message' => 'missing field', 'geoip_city' => nil},
97
+ ]
98
+ filtered = filter(CONFIG, messages)
99
+ assert_equal(expected, filtered)
100
+ end
101
+
102
+ def test_filter_with_dot_key
103
+ config = %[
104
+ geoip_lookup_key ip.origin, ip.dest
105
+ <record>
106
+ origin_country ${country_code['ip.origin']}
107
+ dest_country ${country_code['ip.dest']}
108
+ </record>
109
+ ]
110
+ messages = [
111
+ {'ip.origin' => '66.102.3.80', 'ip.dest' => '8.8.8.8'}
112
+ ]
113
+ expected = [
114
+ {'ip.origin' => '66.102.3.80', 'ip.dest' => '8.8.8.8',
115
+ 'origin_country' => 'US', 'dest_country' => 'US'}
116
+ ]
117
+ filtered = filter(config, messages)
118
+ assert_equal(expected, filtered)
119
+ end
120
+
121
+ def test_filter_nested_attr
122
+ config = %[
123
+ geoip_lookup_key host.ip
124
+ enable_key_city geoip_city
125
+ ]
126
+ messages = [
127
+ {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip'},
128
+ {'message' => 'missing field'}
129
+ ]
130
+ expected = [
131
+ {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip', 'geoip_city' => 'Mountain View'},
132
+ {'message' => 'missing field', 'geoip_city' => nil}
133
+ ]
134
+ filtered = filter(config, messages)
135
+ assert_equal(expected, filtered)
136
+ end
137
+
138
+ def test_filter_with_unknown_address
139
+ config = %[
140
+ geoip_lookup_key host
141
+ <record>
142
+ geoip_city ${city['host']}
143
+ geopoint [${longitude['host']}, ${latitude['host']}]
144
+ </record>
145
+ skip_adding_null_record false
146
+ ]
147
+ # 203.0.113.1 is a test address described in RFC5737
148
+ messages = [
149
+ {'host' => '203.0.113.1', 'message' => 'invalid ip'},
150
+ {'host' => '0', 'message' => 'invalid ip'}
151
+ ]
152
+ expected = [
153
+ {'host' => '203.0.113.1', 'message' => 'invalid ip', 'geoip_city' => nil, 'geopoint' => [nil, nil]},
154
+ {'host' => '0', 'message' => 'invalid ip', 'geoip_city' => nil, 'geopoint' => [nil, nil]}
155
+ ]
156
+ filtered = filter(config, messages)
157
+ assert_equal(expected, filtered)
158
+ end
159
+
160
+ def test_filter_with_skip_unknown_address
161
+ config = %[
162
+ geoip_lookup_key host
163
+ <record>
164
+ geoip_city ${city['host']}
165
+ geopoint [${longitude['host']}, ${latitude['host']}]
166
+ </record>
167
+ skip_adding_null_record true
168
+ ]
169
+ # 203.0.113.1 is a test address described in RFC5737
170
+ messages = [
171
+ {'host' => '203.0.113.1', 'message' => 'invalid ip'},
172
+ {'host' => '0', 'message' => 'invalid ip'},
173
+ {'host' => '8.8.8.8', 'message' => 'google public dns'}
174
+ ]
175
+ expected = [
176
+ {'host' => '203.0.113.1', 'message' => 'invalid ip'},
177
+ {'host' => '0', 'message' => 'invalid ip'},
178
+ {'host' => '8.8.8.8', 'message' => 'google public dns',
179
+ 'geoip_city' => 'Mountain View', 'geopoint' => [-122.08380126953125, 37.38600158691406]}
180
+ ]
181
+ filtered = filter(config, messages)
182
+ assert_equal(expected, filtered)
183
+ end
184
+
185
+ def test_filter_multiple_key
186
+ config = %[
187
+ geoip_lookup_key from.ip, to.ip
188
+ enable_key_city from_city, to_city
189
+ ]
190
+ messages = [
191
+ {'from' => {'ip' => '66.102.3.80'}, 'to' => {'ip' => '125.54.15.42'}},
192
+ {'message' => 'missing field'}
193
+ ]
194
+ expected = [
195
+ {'from' => {'ip' => '66.102.3.80'}, 'to' => {'ip' => '125.54.15.42'},
196
+ 'from_city' => 'Mountain View', 'to_city' => 'Tokorozawa'},
197
+ {'message' => 'missing field', 'from_city' => nil, 'to_city' => nil}
198
+ ]
199
+ filtered = filter(config, messages)
200
+ assert_equal(expected, filtered)
201
+ end
202
+
203
+ def test_filter_multiple_key_multiple_record
204
+ config = %[
205
+ geoip_lookup_key from.ip, to.ip
206
+ enable_key_city from_city, to_city
207
+ enable_key_country_name from_country, to_country
208
+ ]
209
+ messages = [
210
+ {'from' => {'ip' => '66.102.3.80'}, 'to' => {'ip' => '125.54.15.42'}},
211
+ {'from' => {'ip' => '66.102.3.80'}},
212
+ {'message' => 'missing field'}
213
+ ]
214
+ expected = [
215
+ {
216
+ 'from' => {'ip' => '66.102.3.80'},
217
+ 'to' => {'ip' => '125.54.15.42'},
218
+ 'from_city' => 'Mountain View',
219
+ 'from_country' => 'United States',
220
+ 'to_city' => 'Tokorozawa',
221
+ 'to_country' => 'Japan'
222
+ },
223
+ {
224
+ 'from' => {'ip' => '66.102.3.80'},
225
+ 'from_city' => 'Mountain View',
226
+ 'from_country' => 'United States',
227
+ 'to_city' => nil,
228
+ 'to_country' => nil
229
+ },
230
+ {
231
+ 'message' => 'missing field',
232
+ 'from_city' => nil,
233
+ 'from_country' => nil,
234
+ 'to_city' => nil,
235
+ 'to_country' => nil
236
+ }
237
+ ]
238
+ filtered = filter(config, messages)
239
+ assert_equal(expected, filtered)
240
+ end
241
+
242
+ def test_filter_record_directive
243
+ config = %[
244
+ geoip_lookup_key from.ip
245
+ <record>
246
+ from_city ${city['from.ip']}
247
+ from_country ${country_name['from.ip']}
248
+ latitude ${latitude['from.ip']}
249
+ longitude ${longitude['from.ip']}
250
+ float_concat ${latitude['from.ip']},${longitude['from.ip']}
251
+ float_array [${longitude['from.ip']}, ${latitude['from.ip']}]
252
+ float_nest { "lat" : ${latitude['from.ip']}, "lon" : ${longitude['from.ip']}}
253
+ string_concat ${city['from.ip']},${country_name['from.ip']}
254
+ string_array [${city['from.ip']}, ${country_name['from.ip']}]
255
+ string_nest { "city" : ${city['from.ip']}, "country_name" : ${country_name['from.ip']}}
256
+ unknown_city ${city['unknown_key']}
257
+ undefined ${city['undefined']}
258
+ broken_array1 [${longitude['from.ip']}, ${latitude['undefined']}]
259
+ broken_array2 [${longitude['undefined']}, ${latitude['undefined']}]
260
+ </record>
261
+ ]
262
+ messages = [
263
+ { 'from' => {'ip' => '66.102.3.80'} },
264
+ { 'message' => 'missing field' },
265
+ ]
266
+ expected = [
267
+ {
268
+ 'from' => {'ip' => '66.102.3.80'},
269
+ 'from_city' => 'Mountain View',
270
+ 'from_country' => 'United States',
271
+ 'latitude' => 37.4192008972168,
272
+ 'longitude' => -122.05740356445312,
273
+ 'float_concat' => '37.4192008972168,-122.05740356445312',
274
+ 'float_array' => [-122.05740356445312, 37.4192008972168],
275
+ 'float_nest' => { 'lat' => 37.4192008972168, 'lon' => -122.05740356445312 },
276
+ 'string_concat' => 'Mountain View,United States',
277
+ 'string_array' => ["Mountain View", "United States"],
278
+ 'string_nest' => {"city" => "Mountain View", "country_name" => "United States"},
279
+ 'unknown_city' => nil,
280
+ 'undefined' => nil,
281
+ 'broken_array1' => [-122.05740356445312, nil],
282
+ 'broken_array2' => [nil, nil]
283
+ },
284
+ {
285
+ 'message' => 'missing field',
286
+ 'from_city' => nil,
287
+ 'from_country' => nil,
288
+ 'latitude' => nil,
289
+ 'longitude' => nil,
290
+ 'float_concat' => ',',
291
+ 'float_array' => [nil, nil],
292
+ 'float_nest' => { 'lat' => nil, 'lon' => nil },
293
+ 'string_concat' => ',',
294
+ 'string_array' => [nil, nil],
295
+ 'string_nest' => { "city" => nil, "country_name" => nil },
296
+ 'unknown_city' => nil,
297
+ 'undefined' => nil,
298
+ 'broken_array1' => [nil, nil],
299
+ 'broken_array2' => [nil, nil]
300
+ },
301
+ ]
302
+ filtered = filter(config, messages)
303
+ # test-unit cannot calculate diff between large Array
304
+ assert_equal(expected[0], filtered[0])
305
+ assert_equal(expected[1], filtered[1])
306
+ end
307
+
308
+ def test_filter_record_directive_multiple_record
309
+ config = %[
310
+ geoip_lookup_key from.ip, to.ip
311
+ <record>
312
+ from_city ${city['from.ip']}
313
+ to_city ${city['to.ip']}
314
+ from_country ${country_name['from.ip']}
315
+ to_country ${country_name['to.ip']}
316
+ string_array [${country_name['from.ip']}, ${country_name['to.ip']}]
317
+ </record>
318
+ ]
319
+ messages = [
320
+ {'from' => {'ip' => '66.102.3.80'}, 'to' => {'ip' => '125.54.15.42'}},
321
+ {'message' => 'missing field'}
322
+ ]
323
+ expected = [
324
+ {
325
+ 'from' => { 'ip' => '66.102.3.80' },
326
+ 'to' => { 'ip' => '125.54.15.42' },
327
+ 'from_city' => 'Mountain View',
328
+ 'from_country' => 'United States',
329
+ 'to_city' => 'Tokorozawa',
330
+ 'to_country' => 'Japan',
331
+ 'string_array' => ['United States', 'Japan']
332
+ },
333
+ {
334
+ 'message' => 'missing field',
335
+ 'from_city' => nil,
336
+ 'from_country' => nil,
337
+ 'to_city' => nil,
338
+ 'to_country' => nil,
339
+ 'string_array' => [nil, nil]
340
+ }
341
+ ]
342
+ filtered = filter(config, messages)
343
+ assert_equal(expected, filtered)
344
+ end
345
+
346
+ CONFIG_QUOTED_RECORD = %[
347
+ geoip_lookup_key host
348
+ <record>
349
+ location_properties '{ "country_code" : "${country_code["host"]}", "lat": ${latitude["host"]}, "lon": ${longitude["host"]} }'
350
+ location_string ${latitude['host']},${longitude['host']}
351
+ location_string2 ${country_code["host"]}
352
+ location_array "[${longitude['host']},${latitude['host']}]"
353
+ location_array2 '[${longitude["host"]},${latitude["host"]}]'
354
+ peculiar_pattern '[GEOIP] message => {"lat":${latitude["host"]}, "lon":${longitude["host"]}}'
355
+ </record>
356
+ remove_tag_prefix input.
357
+ tag geoip.${tag}
358
+ ]
359
+
360
+ def test_filter_quoted_record
361
+ messages = [
362
+ {'host' => '66.102.3.80', 'message' => 'valid ip'}
363
+ ]
364
+ expected = [
365
+ {
366
+ 'host' => '66.102.3.80', 'message' => 'valid ip',
367
+ 'location_properties' => {
368
+ 'country_code' => 'US',
369
+ 'lat' => 37.4192008972168,
370
+ 'lon' => -122.05740356445312
371
+ },
372
+ 'location_string' => '37.4192008972168,-122.05740356445312',
373
+ 'location_string2' => 'US',
374
+ 'location_array' => [-122.05740356445312, 37.4192008972168],
375
+ 'location_array2' => [-122.05740356445312, 37.4192008972168],
376
+ 'peculiar_pattern' => '[GEOIP] message => {"lat":37.4192008972168, "lon":-122.05740356445312}'
377
+ }
378
+ ]
379
+ filtered = filter(CONFIG_QUOTED_RECORD, messages)
380
+ assert_equal(expected, filtered)
381
+ end
382
+
383
+ def test_filter_v1_config_compatibility
384
+ messages = [
385
+ {'host' => '66.102.3.80', 'message' => 'valid ip'}
386
+ ]
387
+ expected = [
388
+ {
389
+ 'host' => '66.102.3.80', 'message' => 'valid ip',
390
+ 'location_properties' => {
391
+ 'country_code' => 'US',
392
+ 'lat' => 37.4192008972168,
393
+ 'lon' => -122.05740356445312
394
+ },
395
+ 'location_string' => '37.4192008972168,-122.05740356445312',
396
+ 'location_string2' => 'US',
397
+ 'location_array' => [-122.05740356445312, 37.4192008972168],
398
+ 'location_array2' => [-122.05740356445312, 37.4192008972168],
399
+ 'peculiar_pattern' => '[GEOIP] message => {"lat":37.4192008972168, "lon":-122.05740356445312}'
400
+ }
401
+ ]
402
+ filtered = filter(CONFIG_QUOTED_RECORD, messages, true)
403
+ assert_equal(expected, filtered)
404
+ end
405
+
406
+ def test_filter_multiline_v1_config
407
+ config = %[
408
+ geoip_lookup_key host
409
+ <record>
410
+ location_properties {
411
+ "city": "${city['host']}",
412
+ "country_code": "${country_code['host']}",
413
+ "latitude": "${latitude['host']}",
414
+ "longitude": "${longitude['host']}"
415
+ }
416
+ </record>
417
+ ]
418
+ messages = [
419
+ { 'host' => '66.102.3.80', 'message' => 'valid ip' }
420
+ ]
421
+ expected = [
422
+ {
423
+ 'host' => '66.102.3.80', 'message' => 'valid ip',
424
+ "location_properties" => {
425
+ "city" => "Mountain View",
426
+ "country_code" => "US",
427
+ "latitude" => 37.4192008972168,
428
+ "longitude" => -122.05740356445312
429
+ }
430
+ }
431
+ ]
432
+ filtered = filter(config, messages, true)
433
+ assert_equal(expected, filtered)
434
+ end
435
+ end
436
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-geoip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-07-28 00:00:00.000000000 Z
12
+ date: 2016-01-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -43,6 +43,38 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: appraisal
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: test-unit
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 3.1.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 3.1.0
46
78
  - !ruby/object:Gem::Dependency
47
79
  name: fluentd
48
80
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +132,20 @@ extra_rdoc_files: []
100
132
  files:
101
133
  - .gitignore
102
134
  - .travis.yml
135
+ - Appraisals
103
136
  - Gemfile
104
137
  - LICENSE
105
138
  - README.md
106
139
  - Rakefile
107
140
  - data/GeoLiteCity.dat
108
141
  - fluent-plugin-geoip.gemspec
142
+ - gemfiles/fluentd_v0.10.gemfile
143
+ - gemfiles/fluentd_v0.12.gemfile
144
+ - lib/fluent/plugin/filter_geoip.rb
145
+ - lib/fluent/plugin/geoip.rb
109
146
  - lib/fluent/plugin/out_geoip.rb
110
147
  - test/helper.rb
148
+ - test/plugin/test_filter_geoip.rb
111
149
  - test/plugin/test_out_geoip.rb
112
150
  homepage: https://github.com/y-ken/fluent-plugin-geoip
113
151
  licenses:
@@ -137,4 +175,5 @@ summary: Fluentd Output plugin to add information about geographical location of
137
175
  addresses with Maxmind GeoIP databases.
138
176
  test_files:
139
177
  - test/helper.rb
178
+ - test/plugin/test_filter_geoip.rb
140
179
  - test/plugin/test_out_geoip.rb