logstash-filter-elasticsearch 3.2.1 → 3.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e05102f70c7e104e992f6452fe120e3a7c36908eff0e0927b096b5ee420b040
4
- data.tar.gz: 0b7fa6d082030618ab5c1743bd9825432d538605d30a1da494e200747f7a510f
3
+ metadata.gz: 13f03ed46b5acfa84d22de961c7be68dfba316ccf9b4c3930405aa83454008ec
4
+ data.tar.gz: 32c91902583d770b2f98ec0a65d6b5f24006e11509978d90e19cb14c10828852
5
5
  SHA512:
6
- metadata.gz: '08698e711d7f451102be1962aab48998dcb9cb4eca1560b8a314d6ddacb113fac494d811bd5dcf40d48f7b091c87cb3e29b48f4db3b745090f1dd8e140b8f93f'
7
- data.tar.gz: 502324704c4752889da2ba85a7dcad9e8241b73766f3495b4f62754fef6d4c1c49cc3319eae9f62cc146b9b6bc95cf0a904ccea8f7c7547875e0754494fa2c5e
6
+ metadata.gz: 10989b0b2dc5369c11b82b7d88b6dd4e899ace3dcb2b010051daf6db1fb672ce9762a1faa87d153652b52c659864a163b22512428a1f8a8f5df5c7ae6141a7ff
7
+ data.tar.gz: 5c4a7e1cf126047839893544073942f0ca48d438d19519b7c7a7c63f299de108dde3c0fdf107b5cc2d9e38f51ec09907af1e77ce7e3004adff4b4bfe26bf2ad6
@@ -1,3 +1,9 @@
1
+ ## 3.3.0
2
+ - Enhancement : if elasticsearch response contains any shard failure, then `tag_on_failure` tags are added to Logstash event
3
+ - Enhancement : add support for nested fields
4
+ - Enhancement : add 'docinfo_fields' option
5
+ - Enhancement : add 'aggregation_fields' option
6
+
1
7
  ## 3.2.1
2
8
  - Update gemspec summary
3
9
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012–2016 Elasticsearch <http://www.elastic.co>
1
+ Copyright (c) 2012-2018 Elasticsearch <http://www.elastic.co>
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ the "end" event. Finally, using a combination of the "date" filter and the
55
55
  }
56
56
 
57
57
  ruby {
58
- code => "event['duration_hrs'] = (event['@timestamp'] - event['started']) / 3600 rescue nil"
58
+ code => "event.set('duration_hrs', (event.get('@timestamp') - event.get('started')) / 3600)"
59
59
  }
60
60
  }
61
61
 
@@ -67,6 +67,7 @@ the "end" event. Finally, using a combination of the "date" filter and the
67
67
  elasticsearch {
68
68
  hosts => ["es-server"]
69
69
  query_template => "template.json"
70
+ fields => { "@timestamp" => "started" }
70
71
  }
71
72
 
72
73
  date {
@@ -75,7 +76,7 @@ the "end" event. Finally, using a combination of the "date" filter and the
75
76
  }
76
77
 
77
78
  ruby {
78
- code => "event['duration_hrs'] = (event['@timestamp'] - event['started']) / 3600 rescue nil"
79
+ code => "event.set('duration_hrs', (event.get('@timestamp') - event.get('started')) / 3600)"
79
80
  }
80
81
  }
81
82
 
@@ -89,7 +90,7 @@ the "end" event. Finally, using a combination of the "date" filter and the
89
90
  "query": "type:start AND operation:%{[opid]}"
90
91
  }
91
92
  },
92
- "_source": ["@timestamp", "started"]
93
+ "_source": ["@timestamp"]
93
94
  }
94
95
 
95
96
  As illustrated above, through the use of 'opid', fields from the Logstash events can be referenced within the template.
@@ -105,7 +106,9 @@ This plugin supports the following configuration options plus the <<plugins-{typ
105
106
  [cols="<,<,<",options="header",]
106
107
  |=======================================================================
107
108
  |Setting |Input type|Required
109
+ | <<plugins-{type}s-{plugin}-aggregation_fields>> |<<hash,hash>>|No
108
110
  | <<plugins-{type}s-{plugin}-ca_file>> |a valid filesystem path|No
111
+ | <<plugins-{type}s-{plugin}-docinfo_fields>> |<<hash,hash>>|No
109
112
  | <<plugins-{type}s-{plugin}-enable_sort>> |<<boolean,boolean>>|No
110
113
  | <<plugins-{type}s-{plugin}-fields>> |<<array,array>>|No
111
114
  | <<plugins-{type}s-{plugin}-hosts>> |<<array,array>>|No
@@ -125,6 +128,24 @@ filter plugins.
125
128
 
126
129
  &nbsp;
127
130
 
131
+ [id="plugins-{type}s-{plugin}-aggregation_fields"]
132
+ ===== `aggregation_fields`
133
+
134
+ * Value type is <<hash,hash>>
135
+ * Default value is `{}`
136
+
137
+ Hash of aggregation names to copy from elasticsearch response into Logstash event fields
138
+
139
+ Example:
140
+ [source,ruby]
141
+ filter {
142
+ elasticsearch {
143
+ aggregation_fields => {
144
+ "my_agg_name" => "my_ls_field"
145
+ }
146
+ }
147
+ }
148
+
128
149
  [id="plugins-{type}s-{plugin}-ca_file"]
129
150
  ===== `ca_file`
130
151
 
@@ -133,6 +154,25 @@ filter plugins.
133
154
 
134
155
  SSL Certificate Authority file
135
156
 
157
+ [id="plugins-{type}s-{plugin}-docinfo_fields"]
158
+ ===== `docinfo_fields`
159
+
160
+ * Value type is <<hash,hash>>
161
+ * Default value is `{}`
162
+
163
+ Hash of docinfo fields to copy from old event (found via elasticsearch) into new event
164
+
165
+ Example:
166
+ [source,ruby]
167
+ filter {
168
+ elasticsearch {
169
+ docinfo_fields => {
170
+ "_id" => "document_id"
171
+ "_index" => "document_index"
172
+ }
173
+ }
174
+ }
175
+
136
176
  [id="plugins-{type}s-{plugin}-enable_sort"]
137
177
  ===== `enable_sort`
138
178
 
@@ -5,82 +5,6 @@ require_relative "elasticsearch/client"
5
5
  require "logstash/json"
6
6
  java_import "java.util.concurrent.ConcurrentHashMap"
7
7
 
8
- # .Compatibility Note
9
- # [NOTE]
10
- # ================================================================================
11
- # Starting with Elasticsearch 5.3, there's an {ref}modules-http.html[HTTP setting]
12
- # called `http.content_type.required`. If this option is set to `true`, and you
13
- # are using Logstash 2.4 through 5.2, you need to update the Elasticsearch filter
14
- # plugin to version 3.1.1 or higher.
15
- #
16
- # ================================================================================
17
- #
18
- # Search Elasticsearch for a previous log event and copy some fields from it
19
- # into the current event. Below are two complete examples of how this filter might
20
- # be used.
21
- #
22
- # The first example uses the legacy 'query' parameter where the user is limited to an Elasticsearch query_string.
23
- # Whenever logstash receives an "end" event, it uses this elasticsearch
24
- # filter to find the matching "start" event based on some operation identifier.
25
- # Then it copies the `@timestamp` field from the "start" event into a new field on
26
- # the "end" event. Finally, using a combination of the "date" filter and the
27
- # "ruby" filter, we calculate the time duration in hours between the two events.
28
- # [source,ruby]
29
- # --------------------------------------------------
30
- # if [type] == "end" {
31
- # elasticsearch {
32
- # hosts => ["es-server"]
33
- # query => "type:start AND operation:%{[opid]}"
34
- # fields => { "@timestamp" => "started" }
35
- # }
36
- #
37
- # date {
38
- # match => ["[started]", "ISO8601"]
39
- # target => "[started]"
40
- # }
41
- #
42
- # ruby {
43
- # code => "event.set('duration_hrs', (event.get('@timestamp') - event.get('started')) / 3600) rescue nil"
44
- # }
45
- # }
46
- #
47
- # The example below reproduces the above example but utilises the query_template. This query_template represents a full
48
- # Elasticsearch query DSL and supports the standard Logstash field substitution syntax. The example below issues
49
- # the same query as the first example but uses the template shown.
50
- #
51
- # if [type] == "end" {
52
- # elasticsearch {
53
- # hosts => ["es-server"]
54
- # query_template => "template.json"
55
- # }
56
- #
57
- # date {
58
- # match => ["started", "ISO8601"]
59
- # target => "started"
60
- # }
61
- #
62
- # ruby {
63
- # code => "event.set('duration_hrs', (event.get('@timestamp') - event.get('started')) / 3600) rescue nil"
64
- # }
65
- # }
66
- #
67
- #
68
- #
69
- # template.json:
70
- #
71
- # {
72
- # "query": {
73
- # "query_string": {
74
- # "query": "type:start AND operation:%{[opid]}"
75
- # }
76
- # },
77
- # "_source": ["@timestamp", "started"]
78
- # }
79
- #
80
- # As illustrated above, through the use of 'opid', fields from the Logstash events can be referenced within the template.
81
- # The template will be populated per event prior to being used to query Elasticsearch.
82
- #
83
- # --------------------------------------------------
84
8
 
85
9
  class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
86
10
  config_name "elasticsearch"
@@ -106,6 +30,12 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
106
30
  # Array of fields to copy from old event (found via elasticsearch) into new event
107
31
  config :fields, :validate => :array, :default => {}
108
32
 
33
+ # Hash of docinfo fields to copy from old event (found via elasticsearch) into new event
34
+ config :docinfo_fields, :validate => :hash, :default => {}
35
+
36
+ # Hash of aggregation names to copy from elasticsearch response into Logstash event fields
37
+ config :aggregation_fields, :validate => :hash, :default => {}
38
+
109
39
  # Basic Auth - username
110
40
  config :user, :validate => :string
111
41
 
@@ -145,7 +75,6 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
145
75
 
146
76
  def filter(event)
147
77
  begin
148
-
149
78
  params = {:index => event.sprintf(@index) }
150
79
 
151
80
  if @query_dsl
@@ -161,15 +90,33 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
161
90
  @logger.debug("Querying elasticsearch for lookup", :params => params)
162
91
 
163
92
  results = get_client.search(params)
164
- @fields.each do |old_key, new_key|
165
- if !results['hits']['hits'].empty?
166
- set = []
167
- results["hits"]["hits"].to_a.each do |doc|
168
- set << doc["_source"][old_key]
93
+ raise "Elasticsearch query error: #{results["_shards"]["failures"]}" if results["_shards"].include? "failures"
94
+
95
+ resultsHits = results["hits"]["hits"]
96
+ if !resultsHits.nil? && !resultsHits.empty?
97
+ @fields.each do |old_key, new_key|
98
+ old_key_path = extract_path(old_key)
99
+ set = resultsHits.map do |doc|
100
+ extract_value(doc["_source"], old_key_path)
101
+ end
102
+ event.set(new_key, set.count > 1 ? set : set.first)
103
+ end
104
+ @docinfo_fields.each do |old_key, new_key|
105
+ old_key_path = extract_path(old_key)
106
+ set = resultsHits.map do |doc|
107
+ extract_value(doc, old_key_path)
169
108
  end
170
109
  event.set(new_key, set.count > 1 ? set : set.first)
171
110
  end
172
111
  end
112
+
113
+ resultsAggs = results["aggregations"]
114
+ if !resultsAggs.nil? && !resultsAggs.empty?
115
+ @aggregation_fields.each do |agg_name, ls_field|
116
+ event.set(ls_field, resultsAggs[agg_name])
117
+ end
118
+ end
119
+
173
120
  rescue => e
174
121
  @logger.warn("Failed to query elasticsearch for previous event", :index => @index, :query => query, :event => event, :error => e)
175
122
  @tag_on_failure.each{|tag| event.tag(tag)}
@@ -194,4 +141,22 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
194
141
  def get_client
195
142
  @clients_pool.computeIfAbsent(Thread.current, lambda { |x| new_client })
196
143
  end
144
+
145
+ # get an array of path elements from a path reference
146
+ def extract_path(path_reference)
147
+ return [path_reference] unless path_reference.start_with?('[') && path_reference.end_with?(']')
148
+
149
+ path_reference[1...-1].split('][')
150
+ end
151
+
152
+ # given a Hash and an array of path fragments, returns the value at the path
153
+ # @param source [Hash{String=>Object}]
154
+ # @param path [Array{String}]
155
+ # @return [Object]
156
+ def extract_value(source, path)
157
+ path.reduce(source) do |memo, old_key_fragment|
158
+ break unless memo.include?(old_key_fragment)
159
+ memo[old_key_fragment]
160
+ end
161
+ end
197
162
  end #class LogStash::Filters::Elasticsearch
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-elasticsearch'
4
- s.version = '3.2.1'
4
+ s.version = '3.3.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Copies fields from previous log events in Elasticsearch to current events "
7
7
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -20,7 +20,9 @@ describe LogStash::Filters::Elasticsearch do
20
20
  {
21
21
  "hosts" => ["localhost:9200"],
22
22
  "query" => "response: 404",
23
- "fields" => [ ["response", "code"] ],
23
+ "fields" => { "response" => "code" },
24
+ "docinfo_fields" => { "_index" => "es_index" },
25
+ "aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
24
26
  }
25
27
  end
26
28
  let(:plugin) { described_class.new(config) }
@@ -60,6 +62,8 @@ describe LogStash::Filters::Elasticsearch do
60
62
  it "should enhance the current event with new data" do
61
63
  plugin.filter(event)
62
64
  expect(event.get("code")).to eq(404)
65
+ expect(event.get("es_index")).to eq("logstash-2014.08.26")
66
+ expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
63
67
  end
64
68
 
65
69
  it "should receive all necessary params to perform the search" do
@@ -74,7 +78,7 @@ describe LogStash::Filters::Elasticsearch do
74
78
  "index" => "foo*",
75
79
  "hosts" => ["localhost:9200"],
76
80
  "query" => "response: 404",
77
- "fields" => [ ["response", "code"] ],
81
+ "fields" => { "response" => "code" }
78
82
  }
79
83
  end
80
84
 
@@ -90,7 +94,7 @@ describe LogStash::Filters::Elasticsearch do
90
94
  {
91
95
  "hosts" => ["localhost:9200"],
92
96
  "query" => "response: 404",
93
- "fields" => [ ["response", "code"] ],
97
+ "fields" => { "response" => "code" },
94
98
  "result_size" => 10
95
99
  }
96
100
  end
@@ -125,7 +129,7 @@ describe LogStash::Filters::Elasticsearch do
125
129
  {
126
130
  "hosts" => ["localhost:9200"],
127
131
  "query_template" => File.join(File.dirname(__FILE__), "fixtures", "query_template.json"),
128
- "fields" => [ ["response", "code"] ],
132
+ "fields" => { "response" => "code" },
129
133
  "result_size" => 1
130
134
  }
131
135
  end
@@ -154,7 +158,7 @@ describe LogStash::Filters::Elasticsearch do
154
158
  "index" => "foo_%{subst_field}*",
155
159
  "hosts" => ["localhost:9200"],
156
160
  "query" => "response: 404",
157
- "fields" => [["response", "code"]]
161
+ "fields" => { "response" => "code" }
158
162
  }
159
163
  end
160
164
 
@@ -162,6 +166,39 @@ describe LogStash::Filters::Elasticsearch do
162
166
  expect(client).to receive(:search).with({:q => "response: 404", :size => 1, :index => "foo_subst_value*", :sort => "@timestamp:desc"})
163
167
  plugin.filter(event)
164
168
  end
169
+ end
170
+
171
+ context "if query result errored but no exception is thrown" do
172
+ let(:response) do
173
+ LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
174
+ end
175
+
176
+ before(:each) do
177
+ allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
178
+ allow(client).to receive(:search).and_return(response)
179
+ plugin.register
180
+ end
181
+
182
+ it "tag the event as something happened, but still deliver it" do
183
+ expect(plugin.logger).to receive(:warn)
184
+ plugin.filter(event)
185
+ expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
186
+ end
187
+ end
188
+
189
+ context "if query is on nested field" do
190
+ let(:config) do
191
+ {
192
+ "hosts" => ["localhost:9200"],
193
+ "query" => "response: 404",
194
+ "fields" => [ ["[geoip][ip]", "ip_address"] ]
195
+ }
196
+ end
197
+
198
+ it "should enhance the current event with new data" do
199
+ plugin.filter(event)
200
+ expect(event.get("ip_address")).to eq("66.249.73.185")
201
+ end
165
202
 
166
203
  end
167
204
 
@@ -0,0 +1,25 @@
1
+ {
2
+ "took": 16,
3
+ "timed_out": false,
4
+ "_shards": {
5
+ "total": 155,
6
+ "successful": 100,
7
+ "failed": 55,
8
+ "failures": [
9
+ {
10
+ "shard": 0,
11
+ "index": "logstash-2014.08.26",
12
+ "node": "YI1MT0H-Q469pFgAVTXI2g",
13
+ "reason": {
14
+ "type": "search_parse_exception",
15
+ "reason": "No mapping found for [@timestamp] in order to sort on"
16
+ }
17
+ }
18
+ ]
19
+ },
20
+ "hits": {
21
+ "total": 0,
22
+ "max_score": null,
23
+ "hits": []
24
+ }
25
+ }
@@ -58,5 +58,10 @@
58
58
  "timestamp": "26/Aug/2014:21:22:13 +0000"
59
59
  }
60
60
  }]
61
+ },
62
+ "aggregations": {
63
+ "bytes_avg": {
64
+ "value": 294
65
+ }
61
66
  }
62
67
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-07 00:00:00.000000000 Z
11
+ date: 2018-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -84,6 +84,7 @@ files:
84
84
  - logstash-filter-elasticsearch.gemspec
85
85
  - spec/filters/elasticsearch_spec.rb
86
86
  - spec/filters/fixtures/query_template.json
87
+ - spec/filters/fixtures/request_error.json
87
88
  - spec/filters/fixtures/request_x_1.json
88
89
  - spec/filters/fixtures/request_x_10.json
89
90
  - spec/filters/integration/elasticsearch_spec.rb
@@ -109,13 +110,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  version: '0'
110
111
  requirements: []
111
112
  rubyforge_project:
112
- rubygems_version: 2.6.11
113
+ rubygems_version: 2.6.13
113
114
  signing_key:
114
115
  specification_version: 4
115
116
  summary: Copies fields from previous log events in Elasticsearch to current events
116
117
  test_files:
117
118
  - spec/filters/elasticsearch_spec.rb
118
119
  - spec/filters/fixtures/query_template.json
120
+ - spec/filters/fixtures/request_error.json
119
121
  - spec/filters/fixtures/request_x_1.json
120
122
  - spec/filters/fixtures/request_x_10.json
121
123
  - spec/filters/integration/elasticsearch_spec.rb