fluent-plugin-geoip 1.0.0 → 1.3.2

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.
@@ -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