fluent-plugin-geoip 1.0.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,31 +1,38 @@
1
- version: "2"
1
+ version: "3"
2
2
  services:
3
+ test-ruby2.5:
4
+ build:
5
+ context: .
6
+ dockerfile: dockerfiles/Dockerfile-ruby2.5
7
+ env_file:
8
+ - env
9
+ command: tail -f /dev/null
3
10
  test-ruby2.4:
4
11
  build:
5
12
  context: .
6
13
  dockerfile: dockerfiles/Dockerfile-ruby2.4
7
14
  env_file:
8
15
  - env
9
- command: ruby -run -e httpd .
16
+ command: tail -f /dev/null
10
17
  test-ruby2.3:
11
18
  build:
12
19
  context: .
13
20
  dockerfile: dockerfiles/Dockerfile-ruby2.3
14
21
  env_file:
15
22
  - env
16
- command: ruby -run -e httpd .
23
+ command: tail -f /dev/null
17
24
  test-ruby2.2:
18
25
  build:
19
26
  context: .
20
27
  dockerfile: dockerfiles/Dockerfile-ruby2.2
21
28
  env_file:
22
29
  - env
23
- command: ruby -run -e httpd .
30
+ command: tail -f /dev/null
24
31
  test-ruby2.1:
25
32
  build:
26
33
  context: .
27
34
  dockerfile: dockerfiles/Dockerfile-ruby2.1
28
35
  env_file:
29
36
  - env
30
- command: ruby -run -e httpd .
37
+ command: tail -f /dev/null
31
38
 
@@ -2,7 +2,7 @@ FROM ruby:2.1-alpine
2
2
 
3
3
  ENV BUNDLE_GEMFILE=$BUNDLE_GEMFILE
4
4
 
5
- RUN apk --no-cache --update add build-base ruby-dev libc6-compat libmaxminddb-dev geoip-dev git
5
+ RUN apk --no-cache --update add build-base automake autoconf libtool ruby-dev libc6-compat geoip-dev git
6
6
 
7
7
  WORKDIR /app
8
8
  COPY . .
@@ -2,7 +2,7 @@ FROM ruby:2.2-alpine
2
2
 
3
3
  ENV BUNDLE_GEMFILE=$BUNDLE_GEMFILE
4
4
 
5
- RUN apk --no-cache --update add build-base ruby-dev libc6-compat libmaxminddb-dev geoip-dev git
5
+ RUN apk --no-cache --update add build-base automake autoconf libtool ruby-dev libc6-compat geoip-dev git
6
6
 
7
7
  WORKDIR /app
8
8
  COPY . .
@@ -2,7 +2,7 @@ FROM ruby:2.3-alpine
2
2
 
3
3
  ENV BUNDLE_GEMFILE=$BUNDLE_GEMFILE
4
4
 
5
- RUN apk --no-cache --update add build-base ruby-dev libc6-compat libmaxminddb-dev geoip-dev git
5
+ RUN apk --no-cache --update add build-base automake autoconf libtool ruby-dev libc6-compat geoip-dev git
6
6
 
7
7
  WORKDIR /app
8
8
  COPY . .
@@ -2,7 +2,7 @@ FROM ruby:2.4-alpine
2
2
 
3
3
  ENV BUNDLE_GEMFILE=$BUNDLE_GEMFILE
4
4
 
5
- RUN apk --no-cache --update add build-base ruby-dev libc6-compat libmaxminddb-dev geoip-dev git
5
+ RUN apk --no-cache --update add build-base automake autoconf libtool ruby-dev libc6-compat geoip-dev git
6
6
 
7
7
  WORKDIR /app
8
8
  COPY . .
@@ -0,0 +1,8 @@
1
+ FROM ruby:2.5-alpine
2
+
3
+ ENV BUNDLE_GEMFILE=$BUNDLE_GEMFILE
4
+
5
+ RUN apk --no-cache --update add build-base automake autoconf libtool ruby-dev libc6-compat geoip-dev git
6
+
7
+ WORKDIR /app
8
+ COPY . .
@@ -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 = "1.0.0"
7
+ spec.version = "1.3.2"
8
8
  spec.authors = ["Kentaro Yoshida"]
9
9
  spec.email = ["y.ken.studio@gmail.com"]
10
10
  spec.summary = %q{Fluentd Filter plugin to add information about geographical location of IP addresses with Maxmind GeoIP databases.}
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "rake"
21
21
  spec.add_development_dependency "appraisal"
22
22
  spec.add_development_dependency "test-unit", ">= 3.1.0"
23
+ spec.add_development_dependency "test-unit-rr"
23
24
  spec.add_development_dependency "geoip2_compat"
24
25
 
25
26
  spec.add_runtime_dependency "fluentd", [">= 0.14.8", "< 2"]
@@ -1,34 +1,219 @@
1
1
  require 'fluent/plugin/filter'
2
- require 'fluent/plugin/geoip'
2
+
3
+ require 'geoip'
4
+ require 'yajl'
5
+ unless {}.respond_to?(:dig)
6
+ begin
7
+ # backport_dig is faster than dig_rb so prefer backport_dig.
8
+ # And Fluentd v1.0.1 uses backport_dig
9
+ require 'backport_dig'
10
+ rescue LoadError
11
+ require 'dig_rb'
12
+ end
13
+ end
3
14
 
4
15
  module Fluent::Plugin
5
16
  class GeoipFilter < Fluent::Plugin::Filter
6
17
  Fluent::Plugin.register_filter('geoip', self)
7
18
 
8
- config_param :geoip_database, :string, default: File.dirname(__FILE__) + '/../../../data/GeoLiteCity.dat'
9
- config_param :geoip2_database, :string, default: File.dirname(__FILE__) + '/../../../data/GeoLite2-City.mmdb'
10
- config_param :geoip_lookup_key, :string, default: 'host'
11
- config_param :skip_adding_null_record, :bool, default: false
19
+ BACKEND_LIBRARIES = [:geoip, :geoip2_compat, :geoip2_c]
20
+
21
+ REGEXP_PLACEHOLDER_SINGLE = /^\$\{
22
+ (?<geoip_key>-?[^\[\]]+)
23
+ \[
24
+ (?:(?<dq>")|(?<sq>'))
25
+ (?<record_key>-?(?(<dq>)[^"{}]+|[^'{}]+))
26
+ (?(<dq>)"|')
27
+ \]
28
+ \}$/x
29
+ REGEXP_PLACEHOLDER_SCAN = /['"]?(\$\{[^\}]+?\})['"]?/
12
30
 
13
- config_set_default :include_tag_key, false
31
+ GEOIP_KEYS = %w(city latitude longitude country_code3 country_code country_name dma_code area_code region)
32
+ GEOIP2_COMPAT_KEYS = %w(city country_code country_name latitude longitude postal_code region region_name)
14
33
 
15
- config_param :hostname_command, :string, default: 'hostname'
34
+ helpers :compat_parameters, :inject, :record_accessor
16
35
 
17
- config_param :log_level, :string, default: 'warn'
36
+ config_param :geoip_database, :string, default: File.expand_path('../../../data/GeoLiteCity.dat', __dir__)
37
+ config_param :geoip2_database, :string, default: File.expand_path('../../../data/GeoLite2-City.mmdb', __dir__)
38
+ config_param :geoip_lookup_keys, :array, value_type: :string, default: ["host"]
39
+ config_param :geoip_lookup_key, :string, default: nil, deprecated: "Use geoip_lookup_keys instead"
40
+ config_param :skip_adding_null_record, :bool, default: false
18
41
 
19
- config_param :backend_library, :enum, list: Fluent::GeoIP::BACKEND_LIBRARIES, default: :geoip2_c
42
+ config_set_default :@log_level, "warn"
43
+
44
+ config_param :backend_library, :enum, list: BACKEND_LIBRARIES, default: :geoip2_c
20
45
 
21
46
  def configure(conf)
47
+ compat_parameters_convert(conf, :inject)
22
48
  super
23
- @geoip = Fluent::GeoIP.new(self, conf)
49
+
50
+ @map = {}
51
+ if @geoip_lookup_key
52
+ @geoip_lookup_keys = @geoip_lookup_key.split(/\s*,\s*/)
53
+ end
54
+
55
+ @geoip_lookup_keys.each do |key|
56
+ if key.include?(".") && !key.start_with?("$")
57
+ $log.warn("#{key} is not treated as nested attributes")
58
+ end
59
+ end
60
+ @geoip_lookup_accessors = @geoip_lookup_keys.map {|key| [key, record_accessor_create(key)] }.to_h
61
+
62
+ if conf.keys.any? {|k| k =~ /^enable_key_/ }
63
+ raise Fluent::ConfigError, "geoip: 'enable_key_*' config format is obsoleted. use <record></record> directive instead."
64
+ end
65
+
66
+ # <record></record> directive
67
+ conf.elements.select { |element| element.name == 'record' }.each { |element|
68
+ element.each_pair { |k, v|
69
+ element.has_key?(k) # to suppress unread configuration warning
70
+ v = v[1..v.size-2] if quoted_value?(v)
71
+ @map[k] = v
72
+ validate_json = Proc.new {
73
+ begin
74
+ dummy_text = Yajl::Encoder.encode('dummy_text')
75
+ Yajl::Parser.parse(v.gsub(REGEXP_PLACEHOLDER_SCAN, dummy_text))
76
+ rescue Yajl::ParseError => e
77
+ message = "geoip: failed to parse '#{v}' as json."
78
+ log.error message, error: e
79
+ raise Fluent::ConfigError, message
80
+ end
81
+ }
82
+ validate_json.call if json?(v.tr('\'"\\', ''))
83
+ }
84
+ }
85
+
86
+ @placeholder_keys = @map.values.join.scan(REGEXP_PLACEHOLDER_SCAN).map{|placeholder| placeholder[0] }.uniq
87
+ @placeholder_keys.each do |key|
88
+ m = key.match(REGEXP_PLACEHOLDER_SINGLE)
89
+ raise Fluent::ConfigError, "Invalid placeholder attributes: #{key}" unless m
90
+ geoip_key = m[:geoip_key]
91
+ case @backend_library
92
+ when :geoip
93
+ raise Fluent::ConfigError, "#{@backend_library}: unsupported key #{geoip_key}" unless GEOIP_KEYS.include?(geoip_key)
94
+ when :geoip2_compat
95
+ raise Fluent::ConfigError, "#{@backend_library}: unsupported key #{geoip_key}" unless GEOIP2_COMPAT_KEYS.include?(geoip_key)
96
+ when :geoip2_c
97
+ # Nothing to do.
98
+ # We cannot define supported key(s) before we fetch values from GeoIP2 database
99
+ # because geoip2_c can fetch any fields in GeoIP2 database.
100
+ end
101
+ end
102
+
103
+ @geoip = load_database
24
104
  end
25
105
 
26
106
  def filter(tag, time, record)
27
- filtered_record = @geoip.add_geoip_field(record)
107
+ filtered_record = add_geoip_field(record)
28
108
  if filtered_record
29
109
  record = filtered_record
30
110
  end
111
+ record = inject_values_to_record(tag, time, record)
31
112
  record
32
113
  end
114
+
115
+ def multi_workers_ready?
116
+ true
117
+ end
118
+
119
+ private
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) #|| value.match(REGEXP_PLACEHOLDER_BRACKET_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[record_key] = rewrited
137
+ end
138
+ record
139
+ end
140
+
141
+ def json?(text)
142
+ text.match(/^\[.+\]$/) || text.match(/^\{.+\}$/)
143
+ end
144
+
145
+ def quoted_value?(text)
146
+ # to improbe compatibility with fluentd v1-config
147
+ text.match(/(^'.+'$|^".+"$)/)
148
+ end
149
+
150
+ def parse_json(message)
151
+ begin
152
+ return Yajl::Parser.parse(message)
153
+ rescue Yajl::ParseError => e
154
+ log.info "geoip: failed to parse '#{message}' as json.", error_class: e.class, error: e.message
155
+ return nil
156
+ end
157
+ end
158
+
159
+ def get_address(record)
160
+ address = {}
161
+ @geoip_lookup_accessors.each do |field, accessor|
162
+ address[field] = accessor.call(record)
163
+ end
164
+ address
165
+ end
166
+
167
+ def geolocate(addresses)
168
+ geodata = {}
169
+ addresses.each do |field, ip|
170
+ geo = nil
171
+ if ip
172
+ if ip.empty?
173
+ log.warn "#{field} is empty string"
174
+ else
175
+ geo = if @geoip.respond_to?(:look_up)
176
+ @geoip.look_up(ip)
177
+ else
178
+ @geoip.lookup(ip)
179
+ end
180
+ end
181
+ end
182
+ geodata[field] = geo
183
+ end
184
+ geodata
185
+ end
186
+
187
+ def create_placeholder(geodata)
188
+ placeholder = {}
189
+ @placeholder_keys.each do |placeholder_key|
190
+ position = placeholder_key.match(REGEXP_PLACEHOLDER_SINGLE)
191
+ next if position.nil? or geodata[position[:record_key]].nil?
192
+ keys = [position[:record_key]] + position[:geoip_key].split('.').map(&:to_sym)
193
+ value = geodata.dig(*keys)
194
+ value = if [:latitude, :longitude].include?(keys.last)
195
+ value || 0.0
196
+ else
197
+ value
198
+ end
199
+ placeholder[placeholder_key] = value
200
+ end
201
+ placeholder
202
+ end
203
+
204
+ def load_database
205
+ case @backend_library
206
+ when :geoip
207
+ ::GeoIP::City.new(@geoip_database, :memory, false)
208
+ when :geoip2_compat
209
+ require 'geoip2_compat'
210
+ GeoIP2Compat.new(@geoip2_database)
211
+ when :geoip2_c
212
+ require 'geoip2'
213
+ GeoIP2::Database.new(@geoip2_database)
214
+ end
215
+ rescue LoadError
216
+ raise Fluent::ConfigError, "You must install #{@backend_library} gem."
217
+ end
33
218
  end
34
219
  end
@@ -1,5 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  require 'test/unit'
3
+ require 'test/unit/rr'
3
4
 
4
5
  $LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))
5
6
  $LOAD_PATH.unshift(__dir__)
@@ -9,8 +9,10 @@ class GeoipFilterTest < Test::Unit::TestCase
9
9
  end
10
10
 
11
11
  CONFIG = %[
12
- geoip_lookup_key host
13
- enable_key_city geoip_city
12
+ geoip_lookup_keys host
13
+ <record>
14
+ geoip_city ${city.names.en['host']}
15
+ </record>
14
16
  ]
15
17
 
16
18
  def create_driver(conf = CONFIG, syntax: :v1)
@@ -19,6 +21,7 @@ class GeoipFilterTest < Test::Unit::TestCase
19
21
 
20
22
  def filter(config, messages, syntax: :v1)
21
23
  d = create_driver(config, syntax: syntax)
24
+ yield d if block_given?
22
25
  d.run(default_tag: "input.access") {
23
26
  messages.each {|message|
24
27
  d.feed(@time, message)
@@ -27,6 +30,15 @@ class GeoipFilterTest < Test::Unit::TestCase
27
30
  d.filtered_records
28
31
  end
29
32
 
33
+ def setup_geoip_mock(d)
34
+ plugin = d.instance
35
+ db = Object.new
36
+ def db.lookup(ip)
37
+ {}
38
+ end
39
+ plugin.instance_variable_set(:@geoip, db)
40
+ end
41
+
30
42
  sub_test_case "configure" do
31
43
  test "empty" do
32
44
  assert_nothing_raised {
@@ -34,42 +46,27 @@ class GeoipFilterTest < Test::Unit::TestCase
34
46
  }
35
47
  end
36
48
 
37
- test "missing required parameters" do
49
+ test "obsoleted configuration" do
38
50
  assert_raise(Fluent::ConfigError) {
39
- create_driver('enable_key_cities')
51
+ create_driver('enable_key_city geoip_city')
40
52
  }
41
53
  end
42
54
 
43
- test "minimum" do
44
- d = create_driver %[
45
- enable_key_city geoip_city
46
- ]
47
- assert_equal 'geoip_city', d.instance.config['enable_key_city']
48
- end
49
-
50
- test "multiple key config" do
51
- d = create_driver %[
52
- geoip_lookup_key from.ip, to.ip
53
- enable_key_city from_city, to_city
55
+ test "deprecated configuration geoip_lookup_key" do
56
+ conf = %[
57
+ geoip_lookup_key host,ip
58
+ <record>
59
+ geoip_city ${city['host']}
60
+ </record>
54
61
  ]
55
- assert_equal 'from_city, to_city', d.instance.config['enable_key_city']
56
- end
57
-
58
- test "multiple key config (bad configure)" do
59
- assert_raise(Fluent::ConfigError) {
60
- create_driver %[
61
- geoip_lookup_key from.ip, to.ip
62
- enable_key_city from_city
63
- enable_key_region from_region
64
- ]
65
- }
62
+ d = create_driver(conf)
63
+ assert_equal(["host", "ip"], d.instance.geoip_lookup_keys)
66
64
  end
67
65
 
68
66
  test "invalid json structure w/ Ruby hash like" do
69
-
70
67
  assert_raise(Fluent::ConfigParseError) {
71
68
  create_driver %[
72
- geoip_lookup_key host
69
+ geoip_lookup_keys host
73
70
  <record>
74
71
  invalid_json {"foo" => 123}
75
72
  </record>
@@ -80,7 +77,7 @@ class GeoipFilterTest < Test::Unit::TestCase
80
77
  test "invalid json structure w/ unquoted string literal" do
81
78
  assert_raise(Fluent::ConfigParseError) {
82
79
  create_driver %[
83
- geoip_lookup_key host
80
+ geoip_lookup_keys host
84
81
  <record>
85
82
  invalid_json {"foo" : string, "bar" : 123}
86
83
  </record>
@@ -88,6 +85,49 @@ class GeoipFilterTest < Test::Unit::TestCase
88
85
  }
89
86
  end
90
87
 
88
+ test "dotted key is not treated as nested attributes" do
89
+ mock($log).warn("host.ip is not treated as nested attributes")
90
+ create_driver %[
91
+ geoip_lookup_keys host.ip
92
+ <record>
93
+ city ${city.names.en['host.ip']}
94
+ </record>
95
+ ]
96
+ end
97
+
98
+ test "nested attributes bracket style" do
99
+ mock($log).warn(anything).times(0)
100
+ create_driver %[
101
+ geoip_lookup_keys $["host"]["ip"]
102
+ <record>
103
+ geoip_city ${city.names.en['$["host"]["ip"]']}
104
+ </record>
105
+ ]
106
+ end
107
+
108
+ test "nested attributes dot style" do
109
+ mock($log).warn(anything).times(0)
110
+ create_driver %[
111
+ geoip_lookup_keys $.host.ip
112
+ <record>
113
+ geoip_city ${city['$.host.ip']}
114
+ </record>
115
+ ]
116
+ end
117
+
118
+ test "invalid placeholder attributes" do
119
+ assert_raise(Fluent::ConfigError) do
120
+ create_driver %[
121
+ geoip_lookup_keys host
122
+ backend_library geoip2_c
123
+
124
+ <record>
125
+ geoip.city_name ${city.names.en["host]}
126
+ </record>
127
+ ]
128
+ end
129
+ end
130
+
91
131
  data(geoip: "geoip",
92
132
  geoip2_compat: "geoip2_compat")
93
133
  test "unsupported key" do |backend|
@@ -129,7 +169,7 @@ class GeoipFilterTest < Test::Unit::TestCase
129
169
  def test_filter_with_dot_key
130
170
  config = %[
131
171
  backend_library geoip2_c
132
- geoip_lookup_key ip.origin, ip.dest
172
+ geoip_lookup_keys ip.origin, ip.dest
133
173
  <record>
134
174
  origin_country ${country.iso_code['ip.origin']}
135
175
  dest_country ${country.iso_code['ip.dest']}
@@ -149,7 +189,7 @@ class GeoipFilterTest < Test::Unit::TestCase
149
189
  def test_filter_with_unknown_address
150
190
  config = %[
151
191
  backend_library geoip2_c
152
- geoip_lookup_key host
192
+ geoip_lookup_keys host
153
193
  <record>
154
194
  geoip_city ${city.names.en['host']}
155
195
  geopoint [${location.longitude['host']}, ${location.latitude['host']}]
@@ -169,10 +209,30 @@ class GeoipFilterTest < Test::Unit::TestCase
169
209
  assert_equal(expected, filtered)
170
210
  end
171
211
 
212
+ def test_filter_with_empty_string
213
+ config = %[
214
+ backend_library geoip2_c
215
+ geoip_lookup_keys host
216
+ <record>
217
+ geoip_city '${city.names.en["host"]}'
218
+ geopoint '[${location.longitude["host"]}, ${location.latitude["host"]}]'
219
+ </record>
220
+ skip_adding_null_record false
221
+ ]
222
+ messages = [
223
+ {'host' => '', 'message' => 'empty string ip'},
224
+ ]
225
+ expected = [
226
+ {'host' => '', 'message' => 'empty string ip', 'geoip_city' => nil, 'geopoint' => [nil, nil]},
227
+ ]
228
+ filtered = filter(config, messages)
229
+ assert_equal(expected, filtered)
230
+ end
231
+
172
232
  def test_filter_with_skip_unknown_address
173
233
  config = %[
174
234
  backend_library geoip2_c
175
- geoip_lookup_key host
235
+ geoip_lookup_keys host
176
236
  <record>
177
237
  geoip_city ${city.names.en['host']}
178
238
  geopoint [${location.longitude['host']}, ${location.latitude['host']}]
@@ -198,21 +258,21 @@ class GeoipFilterTest < Test::Unit::TestCase
198
258
  def test_filter_record_directive
199
259
  config = %[
200
260
  backend_library geoip2_c
201
- geoip_lookup_key from.ip
261
+ geoip_lookup_keys $.from.ip
202
262
  <record>
203
- from_city ${city.names.en['from.ip']}
204
- from_country ${country.names.en['from.ip']}
205
- latitude ${location.latitude['from.ip']}
206
- longitude ${location.longitude['from.ip']}
207
- float_concat ${location.latitude['from.ip']},${location.longitude['from.ip']}
208
- float_array [${location.longitude['from.ip']}, ${location.latitude['from.ip']}]
209
- float_nest { "lat" : ${location.latitude['from.ip']}, "lon" : ${location.longitude['from.ip']}}
210
- string_concat ${city.names.en['from.ip']},${country.names.en['from.ip']}
211
- string_array [${city.names.en['from.ip']}, ${country.names.en['from.ip']}]
212
- string_nest { "city" : ${city.names.en['from.ip']}, "country_name" : ${country.names.en['from.ip']}}
263
+ from_city ${city.names.en['$.from.ip']}
264
+ from_country ${country.names.en['$.from.ip']}
265
+ latitude ${location.latitude['$.from.ip']}
266
+ longitude ${location.longitude['$.from.ip']}
267
+ float_concat ${location.latitude['$.from.ip']},${location.longitude['$.from.ip']}
268
+ float_array [${location.longitude['$.from.ip']}, ${location.latitude['$.from.ip']}]
269
+ float_nest { "lat" : ${location.latitude['$.from.ip']}, "lon" : ${location.longitude['$.from.ip']}}
270
+ string_concat ${city.names.en['$.from.ip']},${country.names.en['$.from.ip']}
271
+ string_array [${city.names.en['$.from.ip']}, ${country.names.en['$.from.ip']}]
272
+ string_nest { "city" : ${city.names.en['$.from.ip']}, "country_name" : ${country.names.en['$.from.ip']}}
213
273
  unknown_city ${city.names.en['unknown_key']}
214
274
  undefined ${city.names.en['undefined']}
215
- broken_array1 [${location.longitude['from.ip']}, ${location.latitude['undefined']}]
275
+ broken_array1 [${location.longitude['$.from.ip']}, ${location.latitude['undefined']}]
216
276
  broken_array2 [${location.longitude['undefined']}, ${location.latitude['undefined']}]
217
277
  </record>
218
278
  ]
@@ -265,13 +325,13 @@ class GeoipFilterTest < Test::Unit::TestCase
265
325
  def test_filter_record_directive_multiple_record
266
326
  config = %[
267
327
  backend_library geoip2_c
268
- geoip_lookup_key from.ip, to.ip
328
+ geoip_lookup_keys $.from.ip, $.to.ip
269
329
  <record>
270
- from_city ${city.names.en['from.ip']}
271
- to_city ${city.names.en['to.ip']}
272
- from_country ${country.names.en['from.ip']}
273
- to_country ${country.names.en['to.ip']}
274
- string_array [${country.names.en['from.ip']}, ${country.names.en['to.ip']}]
330
+ from_city ${city.names.en['$.from.ip']}
331
+ to_city ${city.names.en['$.to.ip']}
332
+ from_country ${country.names.en['$.from.ip']}
333
+ to_country ${country.names.en['$.to.ip']}
334
+ string_array [${country.names.en['$.from.ip']}, ${country.names.en['$.to.ip']}]
275
335
  </record>
276
336
  ]
277
337
  messages = [
@@ -304,7 +364,7 @@ class GeoipFilterTest < Test::Unit::TestCase
304
364
  def config_quoted_record
305
365
  %[
306
366
  backend_library geoip2_c
307
- geoip_lookup_key host
367
+ geoip_lookup_keys host
308
368
  <record>
309
369
  location_properties '{ "country_code" : "${country.iso_code["host"]}", "lat": ${location.latitude["host"]}, "lon": ${location.longitude["host"]} }'
310
370
  location_string ${location.latitude['host']},${location.longitude['host']}
@@ -365,7 +425,7 @@ class GeoipFilterTest < Test::Unit::TestCase
365
425
  def test_filter_multiline_v1_config
366
426
  config = %[
367
427
  backend_library geoip2_c
368
- geoip_lookup_key host
428
+ geoip_lookup_keys host
369
429
  <record>
370
430
  location_properties {
371
431
  "city": "${city.names.en['host']}",
@@ -392,13 +452,79 @@ class GeoipFilterTest < Test::Unit::TestCase
392
452
  filtered = filter(config, messages)
393
453
  assert_equal(expected, filtered)
394
454
  end
455
+
456
+ def test_filter_when_latitude_longitude_is_nil
457
+ config = %[
458
+ backend_library geoip2_c
459
+ geoip_lookup_keys host
460
+ <record>
461
+ latitude ${location.latitude['host']}
462
+ longitude ${location.longitude['host']}
463
+ </record>
464
+ ]
465
+ messages = [
466
+ { "host" => "180.94.85.84", "message" => "nil latitude and longitude" }
467
+ ]
468
+ expected = [
469
+ {
470
+ "host" => "180.94.85.84",
471
+ "message" => "nil latitude and longitude",
472
+ "latitude" => 0.0,
473
+ "longitude" => 0.0
474
+ }
475
+ ]
476
+ filtered = filter(config, messages) do |d|
477
+ setup_geoip_mock(d)
478
+ end
479
+ assert_equal(expected, filtered)
480
+ end
481
+
482
+ def test_filter_nested_attr_bracket_style_double_quote
483
+ config = %[
484
+ backend_library geoip2_c
485
+ geoip_lookup_keys $["host"]["ip"]
486
+ <record>
487
+ geoip_city ${city.names.en['$["host"]["ip"]']}
488
+ </record>
489
+ ]
490
+ messages = [
491
+ {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip'},
492
+ {'message' => 'missing field'}
493
+ ]
494
+ expected = [
495
+ {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip', 'geoip_city' => 'Mountain View'},
496
+ {'message' => 'missing field', 'geoip_city' => nil}
497
+ ]
498
+ filtered = filter(config, messages)
499
+ assert_equal(expected, filtered)
500
+ end
501
+
502
+ def test_filter_nested_attr_bracket_style_single_quote
503
+ config = %[
504
+ backend_library geoip2_c
505
+ geoip_lookup_keys $['host']['ip']
506
+ <record>
507
+ geoip_city ${city.names.en["$['host']['ip']"]}
508
+ </record>
509
+ ]
510
+ messages = [
511
+ {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip'},
512
+ {'message' => 'missing field'}
513
+ ]
514
+ expected = [
515
+ {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip', 'geoip_city' => 'Mountain View'},
516
+ {'message' => 'missing field', 'geoip_city' => nil}
517
+ ]
518
+ filtered = filter(config, messages)
519
+ assert_equal(expected, filtered)
520
+ end
395
521
  end
396
522
 
397
523
  sub_test_case "geoip2_compat" do
398
524
  def test_filter_with_dot_key
399
525
  config = %[
400
526
  backend_library geoip2_compat
401
- geoip_lookup_key ip.origin, ip.dest
527
+ geoip_lookup_keys ip.origin, ip.dest
402
528
  <record>
403
529
  origin_country ${country_code['ip.origin']}
404
530
  dest_country ${country_code['ip.dest']}
@@ -418,7 +544,7 @@ class GeoipFilterTest < Test::Unit::TestCase
418
544
  def test_filter_with_unknown_address
419
545
  config = %[
420
546
  backend_library geoip2_compat
421
- geoip_lookup_key host
547
+ geoip_lookup_keys host
422
548
  <record>
423
549
  geoip_city ${city['host']}
424
550
  geopoint [${longitude['host']}, ${latitude['host']}]
@@ -441,7 +567,7 @@ class GeoipFilterTest < Test::Unit::TestCase
441
567
  def test_filter_with_skip_unknown_address
442
568
  config = %[
443
569
  backend_library geoip2_compat
444
- geoip_lookup_key host
570
+ geoip_lookup_keys host
445
571
  <record>
446
572
  geoip_city ${city['host']}
447
573
  geopoint [${longitude['host']}, ${latitude['host']}]
@@ -467,21 +593,21 @@ class GeoipFilterTest < Test::Unit::TestCase
467
593
  def test_filter_record_directive
468
594
  config = %[
469
595
  backend_library geoip2_compat
470
- geoip_lookup_key from.ip
596
+ geoip_lookup_keys $.from.ip
471
597
  <record>
472
- from_city ${city['from.ip']}
473
- from_country ${country_name['from.ip']}
474
- latitude ${latitude['from.ip']}
475
- longitude ${longitude['from.ip']}
476
- float_concat ${latitude['from.ip']},${longitude['from.ip']}
477
- float_array [${longitude['from.ip']}, ${latitude['from.ip']}]
478
- float_nest { "lat" : ${latitude['from.ip']}, "lon" : ${longitude['from.ip']}}
479
- string_concat ${city['from.ip']},${country_name['from.ip']}
480
- string_array [${city['from.ip']}, ${country_name['from.ip']}]
481
- string_nest { "city" : ${city['from.ip']}, "country_name" : ${country_name['from.ip']}}
598
+ from_city ${city['$.from.ip']}
599
+ from_country ${country_name['$.from.ip']}
600
+ latitude ${latitude['$.from.ip']}
601
+ longitude ${longitude['$.from.ip']}
602
+ float_concat ${latitude['$.from.ip']},${longitude['$.from.ip']}
603
+ float_array [${longitude['$.from.ip']}, ${latitude['$.from.ip']}]
604
+ float_nest { "lat" : ${latitude['$.from.ip']}, "lon" : ${longitude['$.from.ip']}}
605
+ string_concat ${city['$.from.ip']},${country_name['$.from.ip']}
606
+ string_array [${city['$.from.ip']}, ${country_name['$.from.ip']}]
607
+ string_nest { "city" : ${city['$.from.ip']}, "country_name" : ${country_name['$.from.ip']}}
482
608
  unknown_city ${city['unknown_key']}
483
609
  undefined ${city['undefined']}
484
- broken_array1 [${longitude['from.ip']}, ${latitude['undefined']}]
610
+ broken_array1 [${longitude['$.from.ip']}, ${latitude['undefined']}]
485
611
  broken_array2 [${longitude['undefined']}, ${latitude['undefined']}]
486
612
  </record>
487
613
  ]
@@ -534,13 +660,13 @@ class GeoipFilterTest < Test::Unit::TestCase
534
660
  def test_filter_record_directive_multiple_record
535
661
  config = %[
536
662
  backend_library geoip2_compat
537
- geoip_lookup_key from.ip, to.ip
663
+ geoip_lookup_keys $.from.ip, $.to.ip
538
664
  <record>
539
- from_city ${city['from.ip']}
540
- to_city ${city['to.ip']}
541
- from_country ${country_name['from.ip']}
542
- to_country ${country_name['to.ip']}
543
- string_array [${country_name['from.ip']}, ${country_name['to.ip']}]
665
+ from_city ${city['$.from.ip']}
666
+ to_city ${city['$.to.ip']}
667
+ from_country ${country_name['$.from.ip']}
668
+ to_country ${country_name['$.to.ip']}
669
+ string_array [${country_name['$.from.ip']}, ${country_name['$.to.ip']}]
544
670
  </record>
545
671
  ]
546
672
  messages = [
@@ -573,7 +699,7 @@ class GeoipFilterTest < Test::Unit::TestCase
573
699
  def config_quoted_record
574
700
  %[
575
701
  backend_library geoip2_compat
576
- geoip_lookup_key host
702
+ geoip_lookup_keys host
577
703
  <record>
578
704
  location_properties '{ "country_code" : "${country_code["host"]}", "lat": ${latitude["host"]}, "lon": ${longitude["host"]} }'
579
705
  location_string ${latitude['host']},${longitude['host']}
@@ -634,7 +760,7 @@ class GeoipFilterTest < Test::Unit::TestCase
634
760
  def test_filter_multiline_v1_config
635
761
  config = %[
636
762
  backend_library geoip2_compat
637
- geoip_lookup_key host
763
+ geoip_lookup_keys host
638
764
  <record>
639
765
  location_properties {
640
766
  "city": "${city['host']}",
@@ -661,14 +787,42 @@ class GeoipFilterTest < Test::Unit::TestCase
661
787
  filtered = filter(config, messages)
662
788
  assert_equal(expected, filtered)
663
789
  end
790
+
791
+ def test_filter_when_latitude_longitude_is_nil
792
+ config = %[
793
+ backend_library geoip2_compat
794
+ geoip_lookup_keys host
795
+ <record>
796
+ latitude ${latitude['host']}
797
+ longitude ${longitude['host']}
798
+ </record>
799
+ ]
800
+ messages = [
801
+ { "host" => "180.94.85.84", "message" => "nil latitude and longitude" }
802
+ ]
803
+ expected = [
804
+ {
805
+ "host" => "180.94.85.84",
806
+ "message" => "nil latitude and longitude",
807
+ "latitude" => 0.0,
808
+ "longitude" => 0.0
809
+ }
810
+ ]
811
+ filtered = filter(config, messages) do |d|
812
+ setup_geoip_mock(d)
813
+ end
814
+ assert_equal(expected, filtered)
815
+ end
664
816
  end
665
817
 
666
818
  sub_test_case "geoip legacy" do
667
819
  def test_filter
668
820
  config = %[
669
821
  backend_library geoip
670
- geoip_lookup_key host
671
- enable_key_city geoip_city
822
+ geoip_lookup_keys host
823
+ <record>
824
+ geoip_city ${city['host']}
825
+ </record>
672
826
  ]
673
827
  messages = [
674
828
  {'host' => '66.102.3.80', 'message' => 'valid ip'},
@@ -685,7 +839,7 @@ class GeoipFilterTest < Test::Unit::TestCase
685
839
  def test_filter_with_dot_key
686
840
  config = %[
687
841
  backend_library geoip
688
- geoip_lookup_key ip.origin, ip.dest
842
+ geoip_lookup_keys ip.origin, ip.dest
689
843
  <record>
690
844
  origin_country ${country_code['ip.origin']}
691
845
  dest_country ${country_code['ip.dest']}
@@ -705,8 +859,10 @@ class GeoipFilterTest < Test::Unit::TestCase
705
859
  def test_filter_nested_attr
706
860
  config = %[
707
861
  backend_library geoip
708
- geoip_lookup_key host.ip
709
- enable_key_city geoip_city
862
+ geoip_lookup_keys $.host.ip
863
+ <record>
864
+ geoip_city ${city['$.host.ip']}
865
+ </record>
710
866
  ]
711
867
  messages = [
712
868
  {'host' => {'ip' => '66.102.3.80'}, 'message' => 'valid ip'},
@@ -723,7 +879,7 @@ class GeoipFilterTest < Test::Unit::TestCase
723
879
  def test_filter_with_unknown_address
724
880
  config = %[
725
881
  backend_library geoip
726
- geoip_lookup_key host
882
+ geoip_lookup_keys host
727
883
  <record>
728
884
  geoip_city ${city['host']}
729
885
  geopoint [${longitude['host']}, ${latitude['host']}]
@@ -746,7 +902,7 @@ class GeoipFilterTest < Test::Unit::TestCase
746
902
  def test_filter_with_skip_unknown_address
747
903
  config = %[
748
904
  backend_library geoip
749
- geoip_lookup_key host
905
+ geoip_lookup_keys host
750
906
  <record>
751
907
  geoip_city ${city['host']}
752
908
  geopoint [${longitude['host']}, ${latitude['host']}]
@@ -772,8 +928,11 @@ class GeoipFilterTest < Test::Unit::TestCase
772
928
  def test_filter_multiple_key
773
929
  config = %[
774
930
  backend_library geoip
775
- geoip_lookup_key from.ip, to.ip
776
- enable_key_city from_city, to_city
931
+ geoip_lookup_keys $.from.ip, $.to.ip
932
+ <record>
933
+ from_city ${city['$.from.ip']}
934
+ to_city ${city['$.to.ip']}
935
+ </record>
777
936
  ]
778
937
  messages = [
779
938
  {'from' => {'ip' => '66.102.3.80'}, 'to' => {'ip' => '125.54.15.42'}},
@@ -791,9 +950,13 @@ class GeoipFilterTest < Test::Unit::TestCase
791
950
  def test_filter_multiple_key_multiple_record
792
951
  config = %[
793
952
  backend_library geoip
794
- geoip_lookup_key from.ip, to.ip
795
- enable_key_city from_city, to_city
796
- enable_key_country_name from_country, to_country
953
+ geoip_lookup_keys $.from.ip, $.to.ip
954
+ <record>
955
+ from_city ${city['$.from.ip']}
956
+ from_country ${country_name['$.from.ip']}
957
+ to_city ${city['$.to.ip']}
958
+ to_country ${country_name['$.to.ip']}
959
+ </record>
797
960
  ]
798
961
  messages = [
799
962
  {'from' => {'ip' => '66.102.3.80'}, 'to' => {'ip' => '125.54.15.42'}},
@@ -831,21 +994,21 @@ class GeoipFilterTest < Test::Unit::TestCase
831
994
  def test_filter_record_directive
832
995
  config = %[
833
996
  backend_library geoip
834
- geoip_lookup_key from.ip
997
+ geoip_lookup_keys $.from.ip
835
998
  <record>
836
- from_city ${city['from.ip']}
837
- from_country ${country_name['from.ip']}
838
- latitude ${latitude['from.ip']}
839
- longitude ${longitude['from.ip']}
840
- float_concat ${latitude['from.ip']},${longitude['from.ip']}
841
- float_array [${longitude['from.ip']}, ${latitude['from.ip']}]
842
- float_nest { "lat" : ${latitude['from.ip']}, "lon" : ${longitude['from.ip']}}
843
- string_concat ${city['from.ip']},${country_name['from.ip']}
844
- string_array [${city['from.ip']}, ${country_name['from.ip']}]
845
- string_nest { "city" : ${city['from.ip']}, "country_name" : ${country_name['from.ip']}}
999
+ from_city ${city['$.from.ip']}
1000
+ from_country ${country_name['$.from.ip']}
1001
+ latitude ${latitude['$.from.ip']}
1002
+ longitude ${longitude['$.from.ip']}
1003
+ float_concat ${latitude['$.from.ip']},${longitude['$.from.ip']}
1004
+ float_array [${longitude['$.from.ip']}, ${latitude['$.from.ip']}]
1005
+ float_nest { "lat" : ${latitude['$.from.ip']}, "lon" : ${longitude['$.from.ip']}}
1006
+ string_concat ${city['$.from.ip']},${country_name['$.from.ip']}
1007
+ string_array [${city['$.from.ip']}, ${country_name['$.from.ip']}]
1008
+ string_nest { "city" : ${city['$.from.ip']}, "country_name" : ${country_name['$.from.ip']}}
846
1009
  unknown_city ${city['unknown_key']}
847
1010
  undefined ${city['undefined']}
848
- broken_array1 [${longitude['from.ip']}, ${latitude['undefined']}]
1011
+ broken_array1 [${longitude['$.from.ip']}, ${latitude['undefined']}]
849
1012
  broken_array2 [${longitude['undefined']}, ${latitude['undefined']}]
850
1013
  </record>
851
1014
  ]
@@ -898,13 +1061,13 @@ class GeoipFilterTest < Test::Unit::TestCase
898
1061
  def test_filter_record_directive_multiple_record
899
1062
  config = %[
900
1063
  backend_library geoip
901
- geoip_lookup_key from.ip, to.ip
1064
+ geoip_lookup_keys $.from.ip, $.to.ip
902
1065
  <record>
903
- from_city ${city['from.ip']}
904
- to_city ${city['to.ip']}
905
- from_country ${country_name['from.ip']}
906
- to_country ${country_name['to.ip']}
907
- string_array [${country_name['from.ip']}, ${country_name['to.ip']}]
1066
+ from_city ${city['$.from.ip']}
1067
+ to_city ${city['$.to.ip']}
1068
+ from_country ${country_name['$.from.ip']}
1069
+ to_country ${country_name['$.to.ip']}
1070
+ string_array [${country_name['$.from.ip']}, ${country_name['$.to.ip']}]
908
1071
  </record>
909
1072
  ]
910
1073
  messages = [
@@ -937,7 +1100,7 @@ class GeoipFilterTest < Test::Unit::TestCase
937
1100
  def config_quoted_record
938
1101
  %[
939
1102
  backend_library geoip
940
- geoip_lookup_key host
1103
+ geoip_lookup_keys host
941
1104
  <record>
942
1105
  location_properties '{ "country_code" : "${country_code["host"]}", "lat": ${latitude["host"]}, "lon": ${longitude["host"]} }'
943
1106
  location_string ${latitude['host']},${longitude['host']}
@@ -998,7 +1161,7 @@ class GeoipFilterTest < Test::Unit::TestCase
998
1161
  def test_filter_multiline_v1_config
999
1162
  config = %[
1000
1163
  backend_library geoip
1001
- geoip_lookup_key host
1164
+ geoip_lookup_keys host
1002
1165
  <record>
1003
1166
  location_properties {
1004
1167
  "city": "${city['host']}",
@@ -1025,6 +1188,32 @@ class GeoipFilterTest < Test::Unit::TestCase
1025
1188
  filtered = filter(config, messages)
1026
1189
  assert_equal(expected, filtered)
1027
1190
  end
1191
+
1192
+ def test_filter_when_latitude_longitude_is_nil
1193
+ config = %[
1194
+ backend_library geoip
1195
+ geoip_lookup_keys host
1196
+ <record>
1197
+ latitude ${latitude['host']}
1198
+ longitude ${longitude['host']}
1199
+ </record>
1200
+ ]
1201
+ messages = [
1202
+ { "host" => "180.94.85.84", "message" => "nil latitude and longitude" }
1203
+ ]
1204
+ expected = [
1205
+ {
1206
+ "host" => "180.94.85.84",
1207
+ "message" => "nil latitude and longitude",
1208
+ "latitude" => 0.0,
1209
+ "longitude" => 0.0
1210
+ }
1211
+ ]
1212
+ filtered = filter(config, messages) do |d|
1213
+ setup_geoip_mock(d)
1214
+ end
1215
+ assert_equal(expected, filtered)
1216
+ end
1028
1217
  end
1029
1218
  end
1030
1219