logstash-filter-translate 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +1 -0
- data/lib/logstash/filters/translate.rb +195 -0
- data/logstash-filter-translate.gemspec +27 -0
- data/spec/filters/translate.rb +70 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZDBkYzAwNTgzMTNiNzVjZmJhNzY0YjFiYzUzYTc4YzU1Yjk4ZTI2OQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjdlYzhlZDU2NDdhNDY1ODM5OTMyNTRhNjk0MmI0MGU0M2ViMjBmOA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NGE5ZGEzMjkwMjRmZmJiMTllY2IyN2M3NWM3YTBkMjhjNmM1OWViZDM3MTZi
|
10
|
+
ODZmZDBiNmNlN2RmODA2NDJiNDM0NDJhZjY4MjczM2YwMzJlOTIwYTM3NDli
|
11
|
+
MmM2NzBhZjFiNmQ4MjVkNjdjZWQ4NDg1NjA3NDE0YzYzYmEzMDA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MDM1NjFkOWE3ZWVhYTBjM2M1OWY5MmFmY2Q1OWJiNDhlY2RmOTYxODU1N2Vj
|
14
|
+
NTg0MjQwZDg1NjY3MzI5NmE3ZDIwYzlkYWRhYjc3NDUxMmVjNDMxYzJiNTBj
|
15
|
+
YjdkNzQ4NzBmNjU1N2RiZDY2ODg0ZjFiMDAyNzEzMGM3YjVmYmM=
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/filters/base"
|
3
|
+
require "logstash/namespace"
|
4
|
+
|
5
|
+
# A general search and replace tool which uses a configured hash
|
6
|
+
# and/or a YAML file to determine replacement values.
|
7
|
+
#
|
8
|
+
# The dictionary entries can be specified in one of two ways: First,
|
9
|
+
# the "dictionary" configuration item may contain a hash representing
|
10
|
+
# the mapping. Second, an external YAML file (readable by logstash) may be specified
|
11
|
+
# in the "dictionary_path" configuration item. These two methods may not be used
|
12
|
+
# in conjunction; it will produce an error.
|
13
|
+
#
|
14
|
+
# Operationally, if the event field specified in the "field" configuration
|
15
|
+
# matches the EXACT contents of a dictionary entry key (or matches a regex if
|
16
|
+
# "regex" configuration item has been enabled), the field's value will be substituted
|
17
|
+
# with the matched key's value from the dictionary.
|
18
|
+
#
|
19
|
+
# By default, the translate filter will replace the contents of the
|
20
|
+
# maching event field (in-place). However, by using the "destination"
|
21
|
+
# configuration item, you may also specify a target event field to
|
22
|
+
# populate with the new translated value.
|
23
|
+
#
|
24
|
+
# Alternatively, for simple string search and replacements for just a few values
|
25
|
+
# you might consider using the gsub function of the mutate filter.
|
26
|
+
|
27
|
+
class LogStash::Filters::Translate < LogStash::Filters::Base
|
28
|
+
config_name "translate"
|
29
|
+
milestone 1
|
30
|
+
|
31
|
+
# The name of the logstash event field containing the value to be compared for a
|
32
|
+
# match by the translate filter (e.g. "message", "host", "response_code").
|
33
|
+
#
|
34
|
+
# If this field is an array, only the first value will be used.
|
35
|
+
config :field, :validate => :string, :required => true
|
36
|
+
|
37
|
+
# If the destination (or target) field already exists, this configuration item specifies
|
38
|
+
# whether the filter should skip translation (default) or overwrite the target field
|
39
|
+
# value with the new translation value.
|
40
|
+
config :override, :validate => :boolean, :default => false
|
41
|
+
|
42
|
+
# The dictionary to use for translation, when specified in the logstash filter
|
43
|
+
# configuration item (i.e. do not use the @dictionary_path YAML file)
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# filter {
|
47
|
+
# %PLUGIN% {
|
48
|
+
# dictionary => [ "100", "Continue",
|
49
|
+
# "101", "Switching Protocols",
|
50
|
+
# "merci", "thank you",
|
51
|
+
# "old version", "new version" ]
|
52
|
+
# }
|
53
|
+
# }
|
54
|
+
# NOTE: it is an error to specify both dictionary and dictionary_path
|
55
|
+
config :dictionary, :validate => :hash, :default => {}
|
56
|
+
|
57
|
+
# The full path of the external YAML dictionary file. The format of the table
|
58
|
+
# should be a standard YAML file. Make sure you specify any integer-based keys
|
59
|
+
# in quotes. The YAML file should look something like this:
|
60
|
+
#
|
61
|
+
# "100": Continue
|
62
|
+
# "101": Switching Protocols
|
63
|
+
# merci: gracias
|
64
|
+
# old version: new version
|
65
|
+
#
|
66
|
+
# NOTE: it is an error to specify both dictionary and dictionary_path
|
67
|
+
config :dictionary_path, :validate => :path
|
68
|
+
|
69
|
+
# When using a dictionary file, this setting will indicate how frequently
|
70
|
+
# (in seconds) logstash will check the YAML file for updates.
|
71
|
+
config :refresh_interval, :validate => :number, :default => 300
|
72
|
+
|
73
|
+
# The destination field you wish to populate with the translated code. The default
|
74
|
+
# is a field named "translation". Set this to the same value as source if you want
|
75
|
+
# to do a substitution, in this case filter will allways succeed. This will clobber
|
76
|
+
# the old value of the source field!
|
77
|
+
config :destination, :validate => :string, :default => "translation"
|
78
|
+
|
79
|
+
# When `exact => true`, the translate filter will populate the destination field
|
80
|
+
# with the exact contents of the dictionary value. When `exact => false`, the
|
81
|
+
# filter will populate the destination field with the result of any existing
|
82
|
+
# destination field's data, with the translated value substituted in-place.
|
83
|
+
#
|
84
|
+
# For example, consider this simple translation.yml, configured to check the `data` field:
|
85
|
+
# foo: bar
|
86
|
+
#
|
87
|
+
# If logstash receives an event with the `data` field set to "foo", and `exact => true`,
|
88
|
+
# the destination field will be populated with the string "bar".
|
89
|
+
|
90
|
+
# If `exact => false`, and logstash receives the same event, the destination field
|
91
|
+
# will be also set to "bar". However, if logstash receives an event with the `data` field
|
92
|
+
# set to "foofing", the destination field will be set to "barfing".
|
93
|
+
#
|
94
|
+
# Set both `exact => true` AND `regex => `true` if you would like to match using dictionary
|
95
|
+
# keys as regular expressions. A large dictionary could be expensive to match in this case.
|
96
|
+
config :exact, :validate => :boolean, :default => true
|
97
|
+
|
98
|
+
# If you'd like to treat dictionary keys as regular expressions, set `exact => true`.
|
99
|
+
# Note: this is activated only when `exact => true`.
|
100
|
+
config :regex, :validate => :boolean, :default => false
|
101
|
+
|
102
|
+
# In case no translation occurs in the event (no matches), this will add a default
|
103
|
+
# translation string, which will always populate "field", if the match failed.
|
104
|
+
#
|
105
|
+
# For example, if we have configured `fallback => "no match"`, using this dictionary:
|
106
|
+
#
|
107
|
+
# foo: bar
|
108
|
+
#
|
109
|
+
# Then, if logstash received an event with the field `foo` set to "bar", the destination
|
110
|
+
# field would be set to "bar". However, if logstash received an event with `foo` set to "nope",
|
111
|
+
# then the destination field would still be populated, but with the value of "no match".
|
112
|
+
config :fallback, :validate => :string
|
113
|
+
|
114
|
+
public
|
115
|
+
def register
|
116
|
+
if @dictionary_path
|
117
|
+
@next_refresh = Time.now + @refresh_interval
|
118
|
+
registering = true
|
119
|
+
load_yaml(registering)
|
120
|
+
end
|
121
|
+
|
122
|
+
@logger.debug? and @logger.debug("#{self.class.name}: Dictionary - ", :dictionary => @dictionary)
|
123
|
+
if @exact
|
124
|
+
@logger.debug? and @logger.debug("#{self.class.name}: Dictionary translation method - Exact")
|
125
|
+
else
|
126
|
+
@logger.debug? and @logger.debug("#{self.class.name}: Dictionary translation method - Fuzzy")
|
127
|
+
end
|
128
|
+
end # def register
|
129
|
+
|
130
|
+
public
|
131
|
+
def load_yaml(registering=false)
|
132
|
+
if !File.exists?(@dictionary_path)
|
133
|
+
@logger.warn("dictionary file read failure, continuing with old dictionary", :path => @dictionary_path)
|
134
|
+
return
|
135
|
+
end
|
136
|
+
|
137
|
+
begin
|
138
|
+
@dictionary.merge!(YAML.load_file(@dictionary_path))
|
139
|
+
rescue Exception => e
|
140
|
+
if registering
|
141
|
+
raise "#{self.class.name}: Bad Syntax in dictionary file #{@dictionary_path}"
|
142
|
+
else
|
143
|
+
@logger.warn("#{self.class.name}: Bad Syntax in dictionary file, continuing with old dictionary", :dictionary_path => @dictionary_path)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
public
|
149
|
+
def filter(event)
|
150
|
+
return unless filter?(event)
|
151
|
+
|
152
|
+
if @dictionary_path
|
153
|
+
if @next_refresh < Time.now
|
154
|
+
load_yaml
|
155
|
+
@next_refresh = Time.now + @refresh_interval
|
156
|
+
@logger.info("refreshing dictionary file")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
return unless event.include?(@field) # Skip translation in case event does not have @event field.
|
161
|
+
return if event.include?(@destination) and not @override # Skip translation in case @destination field already exists and @override is disabled.
|
162
|
+
|
163
|
+
begin
|
164
|
+
#If source field is array use first value and make sure source value is string
|
165
|
+
source = event[@field].is_a?(Array) ? event[@field].first.to_s : event[@field].to_s
|
166
|
+
matched = false
|
167
|
+
if @exact
|
168
|
+
if @regex
|
169
|
+
key = @dictionary.keys.detect{|k| source.match(Regexp.new(k))}
|
170
|
+
if key
|
171
|
+
event[@destination] = @dictionary[key]
|
172
|
+
matched = true
|
173
|
+
end
|
174
|
+
elsif @dictionary.include?(source)
|
175
|
+
event[@destination] = @dictionary[source]
|
176
|
+
matched = true
|
177
|
+
end
|
178
|
+
else
|
179
|
+
translation = source.gsub(Regexp.union(@dictionary.keys), @dictionary)
|
180
|
+
if source != translation
|
181
|
+
event[@destination] = translation.force_encoding(Encoding::UTF_8)
|
182
|
+
matched = true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
if not matched and @fallback
|
187
|
+
event[@destination] = @fallback
|
188
|
+
matched = true
|
189
|
+
end
|
190
|
+
filter_matched(event) if matched or @field == @destination
|
191
|
+
rescue Exception => e
|
192
|
+
@logger.error("Something went wrong when attempting to translate from dictionary", :exception => e, :field => @field, :event => event)
|
193
|
+
end
|
194
|
+
end # def filter
|
195
|
+
end # class LogStash::Filters::Translate
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
|
3
|
+
s.name = 'logstash-filter-translate'
|
4
|
+
s.version = '0.1.1'
|
5
|
+
s.licenses = ['Apache License (2.0)']
|
6
|
+
s.summary = "A general search and replace tool which uses a configured hash and/or a YAML file to determine replacement values."
|
7
|
+
s.description = "A general search and replace tool which uses a configured hash and/or a YAML file to determine replacement values."
|
8
|
+
s.authors = ["Elasticsearch"]
|
9
|
+
s.email = 'richard.pijnenburg@elasticsearch.com'
|
10
|
+
s.homepage = "http://logstash.net/"
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
|
13
|
+
# Files
|
14
|
+
s.files = `git ls-files`.split($\)
|
15
|
+
|
16
|
+
# Tests
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
# Special flag to let us know this is actually a logstash plugin
|
20
|
+
s.metadata = { "logstash_plugin" => "true", "group" => "filter" }
|
21
|
+
|
22
|
+
# Gem dependencies
|
23
|
+
s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
|
24
|
+
|
25
|
+
s.add_development_dependency 'gem_publisher', '~> 1.4.0'
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "test_utils"
|
2
|
+
require "logstash/filters/translate"
|
3
|
+
|
4
|
+
describe LogStash::Filters::Translate do
|
5
|
+
extend LogStash::RSpec
|
6
|
+
|
7
|
+
describe "exact translation" do
|
8
|
+
config <<-CONFIG
|
9
|
+
filter {
|
10
|
+
translate {
|
11
|
+
field => "status"
|
12
|
+
destination => "translation"
|
13
|
+
dictionary => [ "200", "OK",
|
14
|
+
"300", "Redirect",
|
15
|
+
"400", "Client Error",
|
16
|
+
"500", "Server Error" ]
|
17
|
+
exact => true
|
18
|
+
regex => false
|
19
|
+
}
|
20
|
+
}
|
21
|
+
CONFIG
|
22
|
+
|
23
|
+
sample("status" => 200) do
|
24
|
+
insist { subject["translation"] } == "OK"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "multi translation" do
|
29
|
+
config <<-CONFIG
|
30
|
+
filter {
|
31
|
+
translate {
|
32
|
+
field => "status"
|
33
|
+
destination => "translation"
|
34
|
+
dictionary => [ "200", "OK",
|
35
|
+
"300", "Redirect",
|
36
|
+
"400", "Client Error",
|
37
|
+
"500", "Server Error" ]
|
38
|
+
exact => false
|
39
|
+
regex => false
|
40
|
+
}
|
41
|
+
}
|
42
|
+
CONFIG
|
43
|
+
|
44
|
+
sample("status" => "200 & 500") do
|
45
|
+
insist { subject["translation"] } == "OK & Server Error"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "regex translation" do
|
50
|
+
config <<-CONFIG
|
51
|
+
filter {
|
52
|
+
translate {
|
53
|
+
field => "status"
|
54
|
+
destination => "translation"
|
55
|
+
dictionary => [ "^2[0-9][0-9]$", "OK",
|
56
|
+
"^3[0-9][0-9]$", "Redirect",
|
57
|
+
"^4[0-9][0-9]$", "Client Error",
|
58
|
+
"^5[0-9][0-9]$", "Server Error" ]
|
59
|
+
exact => true
|
60
|
+
regex => true
|
61
|
+
}
|
62
|
+
}
|
63
|
+
CONFIG
|
64
|
+
|
65
|
+
sample("status" => "200") do
|
66
|
+
insist { subject["translation"] } == "OK"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logstash-filter-translate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Elasticsearch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: logstash
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.0
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.0
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: gem_publisher
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.4.0
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.4.0
|
47
|
+
description: A general search and replace tool which uses a configured hash and/or
|
48
|
+
a YAML file to determine replacement values.
|
49
|
+
email: richard.pijnenburg@elasticsearch.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- lib/logstash/filters/translate.rb
|
56
|
+
- logstash-filter-translate.gemspec
|
57
|
+
- spec/filters/translate.rb
|
58
|
+
homepage: http://logstash.net/
|
59
|
+
licenses:
|
60
|
+
- Apache License (2.0)
|
61
|
+
metadata:
|
62
|
+
logstash_plugin: 'true'
|
63
|
+
group: filter
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.4.1
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: A general search and replace tool which uses a configured hash and/or a YAML
|
84
|
+
file to determine replacement values.
|
85
|
+
test_files:
|
86
|
+
- spec/filters/translate.rb
|