logstash-output-newrelic 1.1.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45a98fd90dd92f095b34d4593459e6b5587ab779c331597c291ea3b94ad4291e
4
- data.tar.gz: f7f612da2d0040da95ca329c25e6dae097762246f5fa5061ad01082d22df0733
3
+ metadata.gz: a4569ef6cca71b35b48be2b7ad4f42c5648368a082ec71af161c4024d841df53
4
+ data.tar.gz: d8e2e7397ddc36ad08710f4dbe67b4cfb6f795ca6fa379502991bc0323673169
5
5
  SHA512:
6
- metadata.gz: 816c2f5fa309328ffdf78f58894885cf791c3f8fb91867620e8494e1761e2ca4b8d9744b4c6a385c13482a7654ecfc677fb0792dc4174141e0b92beb6bc757ec
7
- data.tar.gz: b2b41b31f4af73a28bdad58416c10f0a25e54a56bc295987d96c825268bce4bcfcdc01e80ca0027dfaa71af95edb9c88d8fe254ad5be6aa6b025b692742d50b1
6
+ metadata.gz: accb5c5d6ee856dc360e0522ac052cffee7e65ab8808e0bc19a588ad247ae7bb0c1132d96e105afc520560ebcdb85033af290a3af1b96cdb838623535f2510bc
7
+ data.tar.gz: cec14c1c4e073bba5533cc77a60b9abe4b85d5cde2a6cad8a563140273ac965a02d02e26e778bbab6285027ebd9123f58e7f94b7f5b5fca6d4e240d27432c48e
data/DEVELOPER.md CHANGED
@@ -14,14 +14,6 @@
14
14
  * Run tests: `jruby -S bundle exec rspec`
15
15
  * Build the gem: `jruby -S gem build logstash-output-newrelic.gemspec`
16
16
 
17
- ## Pushing changes to the public repo
18
- After updating the New Relic repo with changes, changes will need to be pushed to the public GitHub repo at: https://github.com/newrelic/logstash-output-plugin
19
-
20
- * `git remote add public git@github.com:newrelic/logstash-output-plugin.git`
21
- * `git push public master:name-of-branch-to-create`
22
- * Create a PR from that branch in https://github.com/newrelic/logstash-output-plugin
23
- * Get the PR reviewed, merged, and delete the branch!
24
-
25
17
  # Testing it with a local Logstash install
26
18
 
27
19
  Note: you may need to run the following commands outside of your checkout, since these should not
@@ -31,10 +23,4 @@ be run with the JRuby version that you've configured your checkout to use (by us
31
23
  * Add new version: `logstash-plugin install logstash-output-newrelic-<version>.gem`
32
24
  * Restart logstash: For Homebrew: `brew services restart logstash`
33
25
  * Cause a change that you've configured Logstash to pick up (for instance, append to a file you're having it monitor)
34
- * Look in `https://staging-one.newrelic.com/launcher/logger.log-launcher` for your log message
35
-
36
- # Push changes to RubyGems
37
- After updating the source code and gem version in `version.rb`, push the changes to RubyGems. There is an older version of the gem that we do not want to overwrite — `version 0.9.1`. Please be sure you are publishing changes to the correct gem i.e. `version 1.0.0` or higher. Note, you must be a gem owner to publish changes on [RubyGems.org](https://rubygems.org/profiles/NR-LOGGING). Once you've created the account, you will need to run `gem signin` to login to RubyGems via the command line.
38
-
39
- * Build the gem: `gem build logstash-output-newrelic.gemspec`
40
- * Publish the gem: `gem push logstash-output-newrelic-<VERSION>.gem` with the updated version (ex: `gem push logstash-output-newrelic-1.0.0.gem`)
26
+ * Look in `https://one.newrelic.com/launcher/logger.log-launcher` for your log message
data/README.md CHANGED
@@ -11,9 +11,11 @@ Versions of this plugin less than 1.0.0 are unsupported.
11
11
 
12
12
  ## Configuration
13
13
 
14
- Add the following block to your logstash.conf (with your specific API Insert key), then restart Logstash.
14
+ Add one of the following blocks to your logstash.conf (with your specific key), then restart Logstash.
15
15
  There are other optional configuration properties, see below.
16
16
 
17
+ ### Using API Insert Key
18
+
17
19
  Get your API Insert Key:
18
20
  `https://insights.newrelic.com/accounts/<ACCOUNT_ID>/manage/api_keys`
19
21
 
@@ -26,12 +28,28 @@ output {
26
28
  }
27
29
  ```
28
30
 
31
+ ### Using License Key
32
+
33
+ Get your License Key:
34
+ `https://rpm.newrelic.com/accounts/<ACCOUNT_ID>`
35
+
36
+ Example:
37
+ ```rb
38
+ output {
39
+ newrelic {
40
+ license_key => "<LICENSE_KEY>"
41
+ }
42
+ }
43
+ ```
29
44
 
30
45
  ### Required plugin configuration
31
46
 
47
+ Exactly one of the following:
48
+
32
49
  | Property | Description |
33
50
  |---|---|
34
51
  | api_key | your New Relic API Insert key |
52
+ | license_key | your New Relic License key |
35
53
 
36
54
  ### Optional plugin configuration
37
55
 
@@ -39,11 +57,11 @@ output {
39
57
  |---|---|---|
40
58
  | concurrent_requests | The number of threads to make requests from | 1 |
41
59
  | base_uri | New Relic ingestion endpoint | https://log-api.newrelic.com/log/v1 |
42
-
60
+ | max_retries | Maximum number attempts to retry to send a message. If set to 0, no re-attempts will be made. | 3 |
43
61
 
44
62
  ### EU plugin configuration
45
63
 
46
- When using this plugin in the EU override the base_uri with `http://log-api.eu.newrelic.com/log/v1`
64
+ When using this plugin in the EU override the base_uri with `https://log-api.eu.newrelic.com/log/v1`
47
65
 
48
66
  ## Testing
49
67
 
@@ -59,30 +77,3 @@ input {
59
77
  * Restart Logstash
60
78
  * Append a test log message to your log file: `echo "test message" >> /path/to/your/log/file`
61
79
  * Search New Relic Logs for `"test message"`
62
-
63
- ## JSON message parsing
64
-
65
- This plugin will attempt to parse any 'message' attribute as JSON -- if it is JSON, it will be parsed and
66
- the JSON attributes will be added to the event.
67
-
68
- For example, the event:
69
- ```
70
- {
71
- "timestamp": 1562767499238,
72
- "message": "{\"service-name\": \"login-service\", \"user\": {\"id\": 123, \"name\": \"alice\"}}"
73
- }
74
-
75
- ```
76
-
77
- Will be treated as:
78
- ```
79
- {
80
- "timestamp": 1562767499238,
81
- "message": "{\"service-name\": \"my-service\", \"user\": {\"id\": 123, \"name\": \"alice\"}}",
82
- "service-name": "login-service",
83
- "user": {
84
- "id": 123,
85
- "name": "alice"
86
- }
87
- }
88
- ```
@@ -0,0 +1,24 @@
1
+ require 'bigdecimal'
2
+
3
+ class BigDecimal
4
+ # Floating-point numbers that go through the 'json' Logstash filter get automatically converted into BigDecimals.
5
+ # Example of such a filter:
6
+ #
7
+ # filter {
8
+ # json {
9
+ # source => "message"
10
+ # }
11
+ # }
12
+ #
13
+ # The problem is that { "value" => BigDecimal('0.12345') } gets serialized into { "value": "0.12345e0"}. We do
14
+ # want to keep floating point numbers serialized as floating point numbers, even at the expense of loosing a little
15
+ # bit of precision during the conversion. So, in the above example, the correct serialization would be:
16
+ # { "value": 0.12345}
17
+ def to_json(options = nil) #:nodoc:
18
+ if finite?
19
+ self.to_f.to_s
20
+ else
21
+ 'null'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ class Error
2
+ class BadResponseCodeError < StandardError
3
+ attr_reader :url, :response_code
4
+
5
+ def initialize(response_code, url)
6
+ @response_code = response_code
7
+ @url = url
8
+ end
9
+
10
+ def message
11
+ "Got response code '#{@response_code}' contacting NewRelic at URL '#{@url}'"
12
+ end
13
+ end
14
+ end
@@ -6,17 +6,23 @@ require 'uri'
6
6
  require 'zlib'
7
7
  require 'json'
8
8
  require 'java'
9
+ require 'set'
10
+ require_relative './config/bigdecimal_patch'
11
+ require_relative './exception/error'
9
12
 
10
13
  class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
11
14
  java_import java.util.concurrent.Executors;
12
15
  java_import java.util.concurrent.Semaphore;
13
16
 
17
+ NON_RETRYABLE_CODES = Set[401, 403]
18
+
14
19
  config_name "newrelic"
15
20
 
16
21
  config :api_key, :validate => :password, :required => false
17
22
  config :license_key, :validate => :password, :required => false
18
23
  config :concurrent_requests, :validate => :number, :default => 1
19
24
  config :base_uri, :validate => :string, :default => "https://log-api.newrelic.com/log/v1"
25
+ config :max_retries, :validate => :number, :default => 3
20
26
 
21
27
  public
22
28
 
@@ -26,12 +32,12 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
26
32
  raise LogStash::ConfigurationError, "Must provide a license key or api key", caller
27
33
  end
28
34
  auth = {
29
- @api_key.nil? ? 'X-License-Key': 'X-Insert-Key' =>
30
- @api_key.nil? ? @license_key.value : @api_key.value
35
+ @api_key.nil? ? 'X-License-Key' : 'X-Insert-Key' =>
36
+ @api_key.nil? ? @license_key.value : @api_key.value
31
37
  }
32
38
  @header = {
33
- 'X-Event-Source' => 'logs',
34
- 'Content-Encoding' => 'gzip'
39
+ 'X-Event-Source' => 'logs',
40
+ 'Content-Encoding' => 'gzip'
35
41
  }.merge(auth).freeze
36
42
  @executor = java.util.concurrent.Executors.newFixedThreadPool(@concurrent_requests)
37
43
  @semaphor = java.util.concurrent.Semaphore.new(@concurrent_requests)
@@ -58,11 +64,10 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
58
64
  end
59
65
  end
60
66
 
61
-
62
67
  def encode(event_hash)
63
68
  log_message_hash = {
64
- # non-intrinsic attributes get put into 'attributes'
65
- :attributes => event_hash
69
+ # non-intrinsic attributes get put into 'attributes'
70
+ :attributes => event_hash
66
71
  }
67
72
 
68
73
  # intrinsic attributes go at the top level
@@ -88,15 +93,15 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
88
93
  payload.push(encode(event.to_hash))
89
94
  end
90
95
  payload = {
91
- :common => {
92
- :attributes => {
93
- :plugin => {
94
- :type => 'logstash',
95
- :version => LogStash::Outputs::NewRelicVersion::VERSION,
96
- }
97
- }
98
- },
99
- :logs => payload
96
+ :common => {
97
+ :attributes => {
98
+ :plugin => {
99
+ :type => 'logstash',
100
+ :version => LogStash::Outputs::NewRelicVersion::VERSION,
101
+ }
102
+ }
103
+ },
104
+ :logs => payload
100
105
  }
101
106
  @semaphor.acquire()
102
107
  execute = @executor.java_method :submit, [java.lang.Runnable]
@@ -115,17 +120,50 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
115
120
 
116
121
  def handle_response(response)
117
122
  if !(200 <= response.code.to_i && response.code.to_i < 300)
118
- @logger.error("Request returned " + response.code + " " + response.body)
123
+ raise Error::BadResponseCodeError.new(response.code.to_i, @base_uri)
119
124
  end
120
125
  end
121
126
 
122
127
  def nr_send(payload)
123
- http = Net::HTTP.new(@end_point.host, 443)
124
- request = Net::HTTP::Post.new(@end_point.request_uri)
125
- http.use_ssl = true
126
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
127
- @header.each {|k, v| request[k] = v}
128
- request.body = payload
129
- handle_response(http.request(request))
128
+ retries = 0
129
+ begin
130
+ http = Net::HTTP.new(@end_point.host, 443)
131
+ request = Net::HTTP::Post.new(@end_point.request_uri)
132
+ http.use_ssl = true
133
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
134
+ @header.each { |k, v| request[k] = v }
135
+ request.body = payload
136
+ handle_response(http.request(request))
137
+ rescue Error::BadResponseCodeError => e
138
+ @logger.error(e.message)
139
+ if (should_retry(retries) && is_retryable_code(e))
140
+ retries += 1
141
+ sleep(1)
142
+ retry
143
+ end
144
+ rescue => e
145
+ # Stuff that should never happen
146
+ # For all other errors print out full issues
147
+ @logger.error(
148
+ "An unknown error occurred sending a bulk request to NewRelic.",
149
+ :error_message => e.message,
150
+ :error_class => e.class.name,
151
+ :backtrace => e.backtrace
152
+ )
153
+ if (should_retry(retries))
154
+ retries += 1
155
+ sleep(1)
156
+ retry
157
+ end
158
+ end
159
+ end
160
+
161
+ def should_retry(retries)
162
+ retries < @max_retries
163
+ end
164
+
165
+ def is_retryable_code(response_error)
166
+ error_code = response_error.response_code
167
+ !NON_RETRYABLE_CODES.include?(error_code)
130
168
  end
131
169
  end # class LogStash::Outputs::NewRelic
@@ -1,7 +1,7 @@
1
1
  module LogStash
2
2
  module Outputs
3
3
  module NewRelicVersion
4
- VERSION = "1.1.1"
4
+ VERSION = "1.2.1"
5
5
  end
6
6
  end
7
7
  end
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.name = 'logstash-output-newrelic'
8
8
  s.version = LogStash::Outputs::NewRelicVersion::VERSION
9
9
  s.licenses = ['Apache-2.0']
10
- s.summary = "Sends Lostash events to New Relic"
10
+ s.summary = "Sends Logstash events to New Relic"
11
11
  s.homepage = 'https://github.com/newrelic/logstash-output-plugin'
12
12
  s.authors = ['New Relic Logging Team']
13
13
  s.email = 'logging-team@newrelic.com'
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.add_runtime_dependency "logstash-codec-plain"
27
27
  s.add_development_dependency "logstash-devutils"
28
28
  s.add_development_dependency "webmock"
29
+ s.add_development_dependency "rspec"
29
30
  s.add_development_dependency "rspec-wait"
30
-
31
+ s.add_development_dependency "rspec_junit_formatter"
31
32
  end
@@ -21,7 +21,6 @@ describe LogStash::Outputs::NewRelic do
21
21
  }
22
22
  }
23
23
 
24
-
25
24
  before(:each) do
26
25
  @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(simple_config)
27
26
  @newrelic_output.register
@@ -32,6 +31,7 @@ describe LogStash::Outputs::NewRelic do
32
31
  @newrelic_output.shutdown
33
32
  end
34
33
  end
34
+
35
35
  context "license key tests" do
36
36
  it "sets license key when given in the header" do
37
37
  stub_request(:any, base_uri).to_return(status: 200)
@@ -279,5 +279,104 @@ describe LogStash::Outputs::NewRelic do
279
279
  .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 2' })
280
280
  .to have_been_made
281
281
  end
282
+
283
+ it "retry when receive retryable http error code" do
284
+ stub_request(:any, base_uri)
285
+ .to_return(status: 500)
286
+ .to_return(status: 200)
287
+
288
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
289
+ @newrelic_output.multi_receive([event1])
290
+
291
+ wait_for(a_request(:post, base_uri)
292
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 1' })
293
+ .to have_been_made.times(2)
294
+ end
295
+
296
+ it "not retry when receive a non retryable http error code" do
297
+ stub_request(:any, base_uri)
298
+ .to_return(status: 401)
299
+
300
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
301
+ @newrelic_output.multi_receive([event1])
302
+ # Due the async behavior we need to wait to be sure that the method was not called more than 1 time
303
+ sleep(2)
304
+ wait_for(a_request(:post, base_uri)
305
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 1' })
306
+ .to have_been_made.times(1)
307
+ end
308
+
309
+ it "not retries when retry is disabled" do
310
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(
311
+ { "base_uri" => base_uri, "license_key" => api_key, "max_retries" => '0' }
312
+ )
313
+ @newrelic_output.register
314
+ stub_request(:any, base_uri)
315
+ .to_return(status: 500)
316
+
317
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
318
+ @newrelic_output.multi_receive([event1])
319
+ # Due the async behavior we need to wait to be sure that the method was not called more than 1 time
320
+ sleep(2)
321
+ wait_for(a_request(:post, base_uri)
322
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 1' })
323
+ .to have_been_made.times(1)
324
+ end
325
+
326
+ it "retry when receive a not expected exception" do
327
+ stub_request(:any, base_uri)
328
+ .to_raise(StandardError.new("from test"))
329
+ .to_return(status: 200)
330
+
331
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
332
+ @newrelic_output.multi_receive([event1])
333
+ wait_for(a_request(:post, base_uri)
334
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 1' })
335
+ .to have_been_made.times(2)
336
+ end
337
+ end
338
+
339
+ context "JSON serialization" do
340
+ it "serializes floating point numbers as floating point numbers" do
341
+ stub_request(:any, base_uri).to_return(status: 200)
342
+
343
+ event = LogStash::Event.new({ "floatingpoint" => 0.12345 })
344
+ @newrelic_output.multi_receive([event])
345
+
346
+ wait_for(a_request(:post, base_uri)
347
+ .with { |request|
348
+ message = single_gzipped_message(request.body)
349
+ message['attributes']['floatingpoint'] == 0.12345
350
+ }
351
+ ).to have_been_made
352
+ end
353
+
354
+ it "serializes BigDecimals as floating point numbers" do
355
+ stub_request(:any, base_uri).to_return(status: 200)
356
+
357
+ event = LogStash::Event.new({ "bigdecimal" => BigDecimal('0.12345') })
358
+ @newrelic_output.multi_receive([event])
359
+
360
+ wait_for(a_request(:post, base_uri)
361
+ .with { |request|
362
+ message = single_gzipped_message(request.body)
363
+ message['attributes']['bigdecimal'] == 0.12345
364
+ }
365
+ ).to have_been_made
366
+ end
367
+
368
+ it "serializes NaN as null" do
369
+ stub_request(:any, base_uri).to_return(status: 200)
370
+
371
+ event = LogStash::Event.new({ "nan" => BigDecimal('NaN') })
372
+ @newrelic_output.multi_receive([event])
373
+
374
+ wait_for(a_request(:post, base_uri)
375
+ .with { |request|
376
+ message = single_gzipped_message(request.body)
377
+ message['attributes']['nan'] == nil
378
+ }
379
+ ).to have_been_made
380
+ end
282
381
  end
283
382
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-newrelic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - New Relic Logging Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-04 00:00:00.000000000 Z
11
+ date: 2021-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -17,8 +17,8 @@ dependencies:
17
17
  - !ruby/object:Gem::Version
18
18
  version: '2.0'
19
19
  name: logstash-core-plugin-api
20
- prerelease: false
21
20
  type: :runtime
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
@@ -31,8 +31,8 @@ dependencies:
31
31
  - !ruby/object:Gem::Version
32
32
  version: '0'
33
33
  name: logstash-codec-plain
34
- prerelease: false
35
34
  type: :runtime
35
+ prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
@@ -45,8 +45,8 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  name: logstash-devutils
48
- prerelease: false
49
48
  type: :development
49
+ prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
@@ -59,8 +59,22 @@ dependencies:
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
61
  name: webmock
62
+ type: :development
62
63
  prerelease: false
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
63
76
  type: :development
77
+ prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - ">="
@@ -73,8 +87,22 @@ dependencies:
73
87
  - !ruby/object:Gem::Version
74
88
  version: '0'
75
89
  name: rspec-wait
90
+ type: :development
76
91
  prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ name: rspec_junit_formatter
77
104
  type: :development
105
+ prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
108
  - - ">="
@@ -92,6 +120,8 @@ files:
92
120
  - Gemfile
93
121
  - LICENSE
94
122
  - README.md
123
+ - lib/logstash/outputs/config/bigdecimal_patch.rb
124
+ - lib/logstash/outputs/exception/error.rb
95
125
  - lib/logstash/outputs/newrelic.rb
96
126
  - lib/logstash/outputs/newrelic_version/version.rb
97
127
  - logstash-output-newrelic.gemspec
@@ -117,10 +147,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
147
  - !ruby/object:Gem::Version
118
148
  version: '0'
119
149
  requirements: []
120
- rubyforge_project:
121
- rubygems_version: 2.7.6
150
+ rubygems_version: 3.0.6
122
151
  signing_key:
123
152
  specification_version: 4
124
- summary: Sends Lostash events to New Relic
153
+ summary: Sends Logstash events to New Relic
125
154
  test_files:
126
155
  - spec/outputs/newrelic_spec.rb