logstash-output-newrelic 0.9.1 → 1.0.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.
@@ -0,0 +1,284 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/newrelic_internal"
4
+ require "logstash/outputs/newrelic_internal_version/version"
5
+ require "logstash/codecs/plain"
6
+ require "logstash/event"
7
+ require "webmock/rspec"
8
+ require "zlib"
9
+
10
+ describe LogStash::Outputs::NewRelicInternal do
11
+ let (:api_key) { "someAccountKey" }
12
+ let (:base_uri) { "https://testing-example-collector.com" }
13
+ let (:retry_seconds) { 0 }
14
+ # Don't sleep in tests, to keep tests fast. We have a test for the method that produces the sleep duration between retries.
15
+ let (:max_delay) { 0 }
16
+ let (:retries) { 3 }
17
+ let (:simple_config) {
18
+ {
19
+ "api_key" => api_key,
20
+ "base_uri" => base_uri,
21
+ "retries" => retries,
22
+ "retry_seconds" => retry_seconds,
23
+ "max_delay" => max_delay,
24
+ }
25
+ }
26
+
27
+ def gunzip(bytes)
28
+ gz = Zlib::GzipReader.new(StringIO.new(bytes))
29
+ gz.read
30
+ end
31
+
32
+ def single_gzipped_message(body)
33
+ message = JSON.parse(gunzip(body))[0]['logs']
34
+ expect(message.length).to equal(1)
35
+ message[0]
36
+ end
37
+
38
+ def multiple_gzipped_messages(body)
39
+ JSON.parse(gunzip(body))
40
+ end
41
+
42
+ before(:each) do
43
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic_internal").new(simple_config)
44
+ @newrelic_output.register
45
+ end
46
+
47
+ after(:each) do
48
+ @newrelic_output&.shutdown
49
+ end
50
+
51
+ context "validation of config" do
52
+ it "requires api_key" do
53
+ no_api_key_config = {
54
+ }
55
+
56
+ expect { LogStash::Plugin.lookup("output", "newrelic_internal").new(no_api_key_config) }.to raise_error LogStash::ConfigurationError
57
+ end
58
+ end
59
+
60
+ context "request headers" do
61
+ it "all present" do
62
+ stub_request(:any, base_uri).to_return(status: 200)
63
+
64
+ event = LogStash::Event.new({ "message" => "Test message" })
65
+ @newrelic_output.multi_receive([event])
66
+
67
+ wait_for(a_request(:post, base_uri)
68
+ .with(headers: {
69
+ "X-Insert-Key" => api_key,
70
+ "X-Event-Source" => "logs",
71
+ "Content-Encoding" => "gzip",
72
+ })).to have_been_made
73
+ end
74
+ end
75
+ context "request body" do
76
+
77
+ it "message contains plugin information" do
78
+ stub_request(:any, base_uri).to_return(status: 200)
79
+
80
+ event = LogStash::Event.new({ :message => "Test message", :@timestamp => '123' })
81
+ @newrelic_output.multi_receive([event])
82
+
83
+ wait_for(a_request(:post, base_uri)
84
+ .with { |request|
85
+ data = multiple_gzipped_messages(request.body)[0]
86
+ data['common']['attributes']['plugin']['type'] == 'logstash' &&
87
+ data['common']['attributes']['plugin']['version'] == LogStash::Outputs::NewRelicInternalVersion::VERSION })
88
+ .to have_been_made
89
+ end
90
+
91
+ # TODO: why is this field always removed?
92
+ it "'@timestamp' field is removed" do
93
+ stub_request(:any, base_uri).to_return(status: 200)
94
+
95
+ event = LogStash::Event.new({ :message => "Test message", :@timestamp => '123' })
96
+ @newrelic_output.multi_receive([event])
97
+
98
+ wait_for(a_request(:post, base_uri)
99
+ .with { |request| single_gzipped_message(request.body)['@timestamp'] == nil })
100
+ .to have_been_made
101
+ end
102
+
103
+ it "all other fields passed through as is" do
104
+ stub_request(:any, base_uri).to_return(status: 200)
105
+
106
+ event = LogStash::Event.new({ :message => "Test message", :other => "Other value" })
107
+ @newrelic_output.multi_receive([event])
108
+
109
+ wait_for(a_request(:post, base_uri)
110
+ .with { |request|
111
+ message = single_gzipped_message(request.body)
112
+ message['message'] == 'Test message' &&
113
+ message['other'] == 'Other value' })
114
+ .to have_been_made
115
+ end
116
+
117
+ it "JSON object 'message' field is parsed, removed, and its data merged as attributes" do
118
+ stub_request(:any, base_uri).to_return(status: 200)
119
+
120
+ message_json = '{ "in-json-1": "1", "in-json-2": "2", "sub-object": {"in-json-3": "3"} }'
121
+ event = LogStash::Event.new({ :message => message_json, :other => "Other value" })
122
+ @newrelic_output.multi_receive([event])
123
+
124
+ wait_for(a_request(:post, base_uri)
125
+ .with { |request|
126
+ message = single_gzipped_message(request.body)
127
+ message['in-json-1'] == '1' &&
128
+ message['in-json-2'] == '2' &&
129
+ message['sub-object'] == {"in-json-3" => "3"} &&
130
+ message['other'] == 'Other value' })
131
+ .to have_been_made
132
+ end
133
+
134
+ it "JSON array 'message' field is not parsed, left as is" do
135
+ stub_request(:any, base_uri).to_return(status: 200)
136
+
137
+ message_json_array = '[{ "in-json-1": "1", "in-json-2": "2", "sub-object": {"in-json-3": "3"} }]'
138
+ event = LogStash::Event.new({ :message => message_json_array, :other => "Other value" })
139
+ @newrelic_output.multi_receive([event])
140
+
141
+ wait_for(a_request(:post, base_uri)
142
+ .with { |request|
143
+ message = single_gzipped_message(request.body)
144
+ message['message'] == message_json_array &&
145
+ message['other'] == 'Other value' })
146
+ .to have_been_made
147
+ end
148
+
149
+ it "JSON string 'message' field is not parsed, left as is" do
150
+ stub_request(:any, base_uri).to_return(status: 200)
151
+
152
+ message_json_string = '"I can be parsed as JSON"'
153
+ event = LogStash::Event.new({ :message => message_json_string, :other => "Other value" })
154
+ @newrelic_output.multi_receive([event])
155
+
156
+ wait_for(a_request(:post, base_uri)
157
+ .with { |request|
158
+ message = single_gzipped_message(request.body)
159
+ message['message'] == message_json_string &&
160
+ message['other'] == 'Other value' })
161
+ .to have_been_made
162
+ end
163
+
164
+ it "other JSON fields are not parsed" do
165
+ stub_request(:any, base_uri).to_return(status: 200)
166
+
167
+ other_json = '{ "key": "value" }'
168
+ event = LogStash::Event.new({ :message => "Test message", :other => other_json })
169
+ @newrelic_output.multi_receive([event])
170
+
171
+ wait_for(a_request(:post, base_uri)
172
+ .with { |request|
173
+ message = single_gzipped_message(request.body)
174
+ message['message'] == 'Test message' &&
175
+ message['other'] == other_json })
176
+ .to have_been_made
177
+ end
178
+
179
+ it "handles messages without a 'message' field" do
180
+ stub_request(:any, base_uri).to_return(status: 200)
181
+
182
+ event = LogStash::Event.new({ :other => 'Other value' })
183
+ @newrelic_output.multi_receive([event])
184
+
185
+ wait_for(a_request(:post, base_uri)
186
+ .with { |request|
187
+ message = single_gzipped_message(request.body)
188
+ message['other'] == 'Other value' })
189
+ .to have_been_made
190
+ end
191
+
192
+ it "multiple events" do
193
+ stub_request(:any, base_uri).to_return(status: 200)
194
+
195
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
196
+ event2 = LogStash::Event.new({ "message" => "Test message 2" })
197
+ @newrelic_output.multi_receive([event1, event2])
198
+
199
+ wait_for(a_request(:post, base_uri)
200
+ .with { |request|
201
+ messages = multiple_gzipped_messages(request.body)[0]['logs']
202
+ messages.length == 2 &&
203
+ messages[0]['message'] == 'Test message 1' &&
204
+ messages[1]['message'] == 'Test message 2' })
205
+ .to have_been_made
206
+ end
207
+ end
208
+
209
+ context "retry" do
210
+ it "sleep periods double each time up to max time" do
211
+ specific_config = simple_config.clone
212
+ # Use non-trivial times -- they can be big, since this test doesn't do any sleeping, just
213
+ # tests the sleep duration
214
+ specific_config["max_delay"] = 60
215
+ specific_config["retry_seconds"] = 5
216
+
217
+ # Create a new plugin with this specific config that has longer retry sleep
218
+ # configuration than we normally want
219
+ @newrelic_output&.shutdown
220
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic_internal").new(specific_config)
221
+ @newrelic_output.register
222
+
223
+ expect(@newrelic_output.sleep_duration(0)).to equal(5)
224
+ expect(@newrelic_output.sleep_duration(1)).to equal(10)
225
+ expect(@newrelic_output.sleep_duration(2)).to equal(20)
226
+ expect(@newrelic_output.sleep_duration(3)).to equal(40)
227
+ expect(@newrelic_output.sleep_duration(4)).to equal(60)
228
+ expect(@newrelic_output.sleep_duration(5)).to equal(60) # Never gets bigger than this
229
+ end
230
+
231
+ it "first call fails, should retry" do
232
+ stub_request(:any, base_uri)
233
+ .to_return(status: 500)
234
+ .to_return(status: 200)
235
+
236
+ event = LogStash::Event.new({ "message" => "Test message" })
237
+ @newrelic_output.multi_receive([event])
238
+
239
+ wait_for(a_request(:post, base_uri)).to have_been_made.times(2)
240
+ end
241
+
242
+ it "first two calls fail, should retry" do
243
+ stub_request(:any, base_uri)
244
+ .to_return(status: 500)
245
+ .to_return(status: 500)
246
+ .to_return(status: 200)
247
+
248
+ event = LogStash::Event.new({ "message" => "Test message" })
249
+ @newrelic_output.multi_receive([event])
250
+
251
+ wait_for(a_request(:post, base_uri)).to have_been_made.times(3)
252
+ end
253
+
254
+ it "all calls fails, should stop retrying at some point" do
255
+ stub_request(:any, base_uri)
256
+ .to_return(status: 500)
257
+
258
+ event = LogStash::Event.new({ "message" => "Test message" })
259
+ @newrelic_output.multi_receive([event])
260
+
261
+ # This may not fail if the wait_for is called exactly when there have been 'retries' calls.
262
+ # However, with zero sleep time (max_delay=0), on a laptop the POST was done 2000+ times by the
263
+ # time this was executed
264
+ wait_for(a_request(:post, base_uri)).to have_been_made.times(retries)
265
+ end
266
+ end
267
+
268
+ context "error handling" do
269
+ it "continues through errors, future calls should still succeed" do
270
+ stub_request(:any, base_uri)
271
+ .to_raise(StandardError.new("from test"))
272
+ .to_return(status: 200)
273
+
274
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
275
+ event2 = LogStash::Event.new({ "message" => "Test message 2" })
276
+ @newrelic_output.multi_receive([event1])
277
+ @newrelic_output.multi_receive([event2])
278
+
279
+ wait_for(a_request(:post, base_uri)
280
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 2' })
281
+ .to have_been_made
282
+ end
283
+ end
284
+ end
metadata CHANGED
@@ -1,39 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-newrelic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - The Chocolate Factory
7
+ - New Relic Logging Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-22 00:00:00.000000000 Z
11
+ date: 2019-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - '>='
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.60'
19
- - - <=
20
- - !ruby/object:Gem::Version
21
- version: '2.99'
18
+ version: '2.0'
22
19
  name: logstash-core-plugin-api
23
20
  prerelease: false
24
21
  type: :runtime
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - '>='
28
- - !ruby/object:Gem::Version
29
- version: '1.60'
30
- - - <=
24
+ - - "~>"
31
25
  - !ruby/object:Gem::Version
32
- version: '2.99'
26
+ version: '2.0'
33
27
  - !ruby/object:Gem::Dependency
34
28
  requirement: !ruby/object:Gem::Requirement
35
29
  requirements:
36
- - - '>='
30
+ - - ">="
37
31
  - !ruby/object:Gem::Version
38
32
  version: '0'
39
33
  name: logstash-codec-plain
@@ -41,13 +35,13 @@ dependencies:
41
35
  type: :runtime
42
36
  version_requirements: !ruby/object:Gem::Requirement
43
37
  requirements:
44
- - - '>='
38
+ - - ">="
45
39
  - !ruby/object:Gem::Version
46
40
  version: '0'
47
41
  - !ruby/object:Gem::Dependency
48
42
  requirement: !ruby/object:Gem::Requirement
49
43
  requirements:
50
- - - '>='
44
+ - - ">="
51
45
  - !ruby/object:Gem::Version
52
46
  version: '0'
53
47
  name: logstash-devutils
@@ -55,35 +49,57 @@ dependencies:
55
49
  type: :development
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
51
  requirements:
58
- - - '>='
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ name: webmock
62
+ prerelease: false
63
+ type: :development
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ name: rspec-wait
76
+ prerelease: false
77
+ type: :development
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
59
81
  - !ruby/object:Gem::Version
60
82
  version: '0'
61
- description: Use logstash to ship log events to New Relic Insights. This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program.
62
- email: info@chocolatefirm.com
83
+ description: Gzips and decorates logstash events to be properly formatted as custom
84
+ events
85
+ email: logging-team@newrelic.com
63
86
  executables: []
64
87
  extensions: []
65
88
  extra_rdoc_files: []
66
89
  files:
67
- - .github/CONTRIBUTING.md
68
- - .github/ISSUE_TEMPLATE.md
69
- - .github/PULL_REQUEST_TEMPLATE.md
70
- - .gitignore
71
- - .travis.yml
72
90
  - CHANGELOG.md
91
+ - CONTRIBUTORS
92
+ - DEVELOPER.md
73
93
  - Gemfile
74
94
  - LICENSE
75
- - NOTICE.TXT
76
95
  - README.md
77
- - Rakefile
78
- - bin/logstash-newrelic
79
- - bin/logstash-newrelic-debug
80
- - bin/logstash-newrelic.conf.template
81
- - lib/logstash/outputs/newrelic.rb
96
+ - lib/logstash/outputs/newrelic_internal.rb
97
+ - lib/logstash/outputs/newrelic_internal_version/version.rb
82
98
  - logstash-output-newrelic.gemspec
83
- - spec/outputs/newrelic_spec.rb
84
- homepage: http://www.chocolatefirm.com
99
+ - spec/outputs/newrelic_internal_spec.rb
100
+ homepage: https://source.datanerd.us/logging/logstash-output-newrelic
85
101
  licenses:
86
- - MIT
102
+ - Apache-2.0
87
103
  metadata:
88
104
  logstash_plugin: 'true'
89
105
  logstash_group: output
@@ -93,19 +109,19 @@ require_paths:
93
109
  - lib
94
110
  required_ruby_version: !ruby/object:Gem::Requirement
95
111
  requirements:
96
- - - '>='
112
+ - - ">="
97
113
  - !ruby/object:Gem::Version
98
114
  version: '0'
99
115
  required_rubygems_version: !ruby/object:Gem::Requirement
100
116
  requirements:
101
- - - '>='
117
+ - - ">="
102
118
  - !ruby/object:Gem::Version
103
119
  version: '0'
104
120
  requirements: []
105
121
  rubyforge_project:
106
- rubygems_version: 2.4.5
122
+ rubygems_version: 2.7.6
107
123
  signing_key:
108
124
  specification_version: 4
109
- summary: New Relic Insights output plugin for Logstash
125
+ summary: Forwards logs as custom events to insights
110
126
  test_files:
111
- - spec/outputs/newrelic_spec.rb
127
+ - spec/outputs/newrelic_internal_spec.rb
@@ -1,65 +0,0 @@
1
- # Contributing to Logstash
2
-
3
- All contributions are welcome: ideas, patches, documentation, bug reports,
4
- complaints, etc!
5
-
6
- Programming is not a required skill, and there are many ways to help out!
7
- It is more important to us that you are able to contribute.
8
-
9
- That said, some basic guidelines, which you are free to ignore :)
10
-
11
- ## Want to learn?
12
-
13
- Want to lurk about and see what others are doing with Logstash?
14
-
15
- * The irc channel (#logstash on irc.freenode.org) is a good place for this
16
- * The [forum](https://discuss.elastic.co/c/logstash) is also
17
- great for learning from others.
18
-
19
- ## Got Questions?
20
-
21
- Have a problem you want Logstash to solve for you?
22
-
23
- * You can ask a question in the [forum](https://discuss.elastic.co/c/logstash)
24
- * Alternately, you are welcome to join the IRC channel #logstash on
25
- irc.freenode.org and ask for help there!
26
-
27
- ## Have an Idea or Feature Request?
28
-
29
- * File a ticket on [GitHub](https://github.com/elastic/logstash/issues). Please remember that GitHub is used only for issues and feature requests. If you have a general question, the [forum](https://discuss.elastic.co/c/logstash) or IRC would be the best place to ask.
30
-
31
- ## Something Not Working? Found a Bug?
32
-
33
- If you think you found a bug, it probably is a bug.
34
-
35
- * If it is a general Logstash or a pipeline issue, file it in [Logstash GitHub](https://github.com/elasticsearch/logstash/issues)
36
- * If it is specific to a plugin, please file it in the respective repository under [logstash-plugins](https://github.com/logstash-plugins)
37
- * or ask the [forum](https://discuss.elastic.co/c/logstash).
38
-
39
- # Contributing Documentation and Code Changes
40
-
41
- If you have a bugfix or new feature that you would like to contribute to
42
- logstash, and you think it will take more than a few minutes to produce the fix
43
- (ie; write code), it is worth discussing the change with the Logstash users and developers first! You can reach us via [GitHub](https://github.com/elastic/logstash/issues), the [forum](https://discuss.elastic.co/c/logstash), or via IRC (#logstash on freenode irc)
44
- Please note that Pull Requests without tests will not be merged. If you would like to contribute but do not have experience with writing tests, please ping us on IRC/forum or create a PR and ask our help.
45
-
46
- ## Contributing to plugins
47
-
48
- Check our [documentation](https://www.elastic.co/guide/en/logstash/current/contributing-to-logstash.html) on how to contribute to plugins or write your own! It is super easy!
49
-
50
- ## Contribution Steps
51
-
52
- 1. Test your changes! [Run](https://github.com/elastic/logstash#testing) the test suite
53
- 2. Please make sure you have signed our [Contributor License
54
- Agreement](https://www.elastic.co/contributor-agreement/). We are not
55
- asking you to assign copyright to us, but to give us the right to distribute
56
- your code without restriction. We ask this of all contributors in order to
57
- assure our users of the origin and continuing existence of the code. You
58
- only need to sign the CLA once.
59
- 3. Send a pull request! Push your changes to your fork of the repository and
60
- [submit a pull
61
- request](https://help.github.com/articles/using-pull-requests). In the pull
62
- request, describe what your changes do and mention any bugs/issues related
63
- to the pull request.
64
-
65
-
@@ -1,9 +0,0 @@
1
- Please post all product and debugging questions on our [forum](https://discuss.elastic.co/c/logstash). Your questions will reach our wider community members there, and if we confirm that there is a bug, then we can open a new issue here.
2
-
3
- For all general issues, please provide the following details for fast resolution:
4
-
5
- - Version:
6
- - Operating System:
7
- - Config File (if you have sensitive info, please remove it):
8
- - Sample Data:
9
- - Steps to Reproduce:
@@ -1 +0,0 @@
1
- Thanks for contributing to Logstash! If you haven't already signed our CLA, here's a handy link: https://www.elastic.co/contributor-agreement/
data/.gitignore DELETED
@@ -1,2 +0,0 @@
1
-
2
- .DS_Store
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - jruby-1.7.25
7
- script:
8
- - bundle exec rspec spec
9
- jdk:
10
- - oraclejdk8
11
- before_install: []
data/NOTICE.TXT DELETED
@@ -1,5 +0,0 @@
1
- Elasticsearch
2
- Copyright 2012-2015 Elasticsearch
3
-
4
- This product includes software developed by The Apache Software
5
- Foundation (http://www.apache.org/).
data/Rakefile DELETED
@@ -1,7 +0,0 @@
1
- @files=[]
2
-
3
- task :default do
4
- system("rake -T")
5
- end
6
-
7
- require "logstash/devutils/rake"
@@ -1,2 +0,0 @@
1
- #!/bin/sh
2
- nohup ./logstash -f logstash-newrelic.conf 2>&1 &
@@ -1,2 +0,0 @@
1
- #!/bin/sh
2
- nohup ./logstash -f logstash-newrelic.conf --debug 2>&1 &
@@ -1,10 +0,0 @@
1
- output {
2
- newrelic {
3
- account_id => your_id_here
4
- insert_key => your_key_here
5
- event_type => your_event_type_here
6
- batch => true
7
- batch_events => 10
8
- batch_timeout => 5
9
- }
10
- }