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 +11 -0
- data/Appraisals +8 -0
- data/fluent-plugin-geoip.gemspec +4 -5
- data/gemfiles/fluentd_v0.10.gemfile +8 -0
- data/gemfiles/fluentd_v0.12.gemfile +8 -0
- data/lib/fluent/plugin/filter_geoip.rb +32 -0
- data/lib/fluent/plugin/geoip.rb +150 -0
- data/lib/fluent/plugin/out_geoip.rb +10 -131
- data/test/helper.rb +3 -0
- data/test/plugin/test_filter_geoip.rb +436 -0
- metadata +41 -2
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
data/fluent-plugin-geoip.gemspec
CHANGED
@@ -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.
|
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
|
-
|
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,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
|
-
|
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
|
-
|
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
@@ -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.
|
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:
|
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
|