fluent-plugin-geoip 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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