logstash-output-newrelic 1.0.2 → 1.0.3

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: 998bef6edbc8fb754b22ab4cca0f25b3e27182e3ac22b5992840ebeab1086764
4
- data.tar.gz: 9d2517ffcc0b573a5c2321dea3c4ca8ced58df4563346f93a1925983169c1a6d
3
+ metadata.gz: 6fb3ab8cf5881043a05b318e3a5158a15f9e39f71d35bf1b98a2b0526ec59df8
4
+ data.tar.gz: b78ec9ccaddeb88589815019ef3a6b8336b9b12aef7d51b03ccb07df21e7c679
5
5
  SHA512:
6
- metadata.gz: 5ebeed8b92b33be3ff45d7148ff402d0128fb0a29fa8028380a046b32819ea5c76ee2d388386ebc801a0840371ccc991a2253c7cf5806d8bc9a0cf050420f0d6
7
- data.tar.gz: '02922a85561259f830f3c142dbabac0bb59867e8bf1b87c2fc0810304c51f0d9333251768e32715014c0d1c60bc071ad0b5f0f142bf5dbab54677b0dc9fea477'
6
+ metadata.gz: d5d13fbb5791e8f7ecf98c6aaebae14596f94581246691cbc9b92a8129b97207405e7af396de356cf1a3bd8fbc882e77700be6353356f54c806a631b1052cd06
7
+ data.tar.gz: f57941893058a0fdd95c2ab7d3b307051275f428591314d5c2ae93afa71ad18cc29e7ec64bc17db17073670c031f1b0293525c675d00e8db52a1fddda6f85cce
data/README.md CHANGED
@@ -6,13 +6,8 @@ This is a plugin for [Logstash](https://github.com/elastic/logstash) that output
6
6
  Install the New Relic Logstash plugin using the following command:</br>
7
7
  `logstash-plugin install logstash-output-newrelic`
8
8
 
9
- (Optional) If you are interested in installing the gem directly, run the following command. If you want a specific version, specify it by appending the `-v <VERSION>` option.<br/>
10
- `gem install logstash-output-newrelic`
11
-
12
- ```
13
- Old version: 0.9.1 (unmaintained)
14
- Current: 1.0.0
15
- ```
9
+ ### Versions
10
+ Versions of this plugin less than 1.0.0 are unsupported.
16
11
 
17
12
  ## Configuration
18
13
 
@@ -50,7 +45,8 @@ output {
50
45
 
51
46
  ## Testing
52
47
 
53
- An easy way to test the plugin is to make sure Logstash is getting input from a log file you can write to. Something like this in your logstash.conf:
48
+ An easy way to test the plugin is to make sure Logstash is getting input from a log file you
49
+ can write to. Something like this in your logstash.conf:
54
50
  ```
55
51
  input {
56
52
  file {
@@ -62,32 +58,29 @@ input {
62
58
  * Append a test log message to your log file: `echo "test message" >> /path/to/your/log/file`
63
59
  * Search New Relic Logs for `"test message"`
64
60
 
65
- ## Notes
61
+ ## JSON message parsing
66
62
 
67
- This plugin will attempt to parse any 'message' attribute as JSON -- if it is JSON, its JSON attributes will be added to the event.
63
+ This plugin will attempt to parse any 'message' attribute as JSON -- if it is JSON, it will be parsed and
64
+ the JSON attributes will be added to the event.
68
65
 
69
- For example, the events:
66
+ For example, the event:
70
67
  ```
71
- [{
72
- "message": "some message",
73
- "timestamp": 1531414060739
74
- },
75
68
  {
76
- {"message":"some_message","timestamp":"12897439", "compound" :"{\"a\":111, \"b\":222}"},
77
- }]
69
+ "timestamp": 1562767499238,
70
+ "message": "{\"service-name\": \"login-service\", \"user\": {\"id\": 123, \"name\": \"alice\"}}"
71
+ }
72
+
78
73
  ```
79
74
 
80
- Will be output as:
75
+ Will be treated as:
81
76
  ```
82
- [{ "message": "{\"key\": \"value1\", \"compound\": {\"sub_key\": \"value2\"}}",
83
- "key": "value1",
84
- "compound": {
85
- "sub_key": "value2"
86
- },
87
- "other": "other value"
88
- }]
77
+ {
78
+ "timestamp": 1562767499238,
79
+ "message": "{\"service-name\": \"my-service\", \"user\": {\"id\": 123, \"name\": \"alice\"}}",
80
+ "service-name": "login-service",
81
+ "user": {
82
+ "id": 123,
83
+ "name": "alice"
84
+ }
85
+ }
89
86
  ```
90
-
91
- ## Development
92
-
93
- See [DEVELOPER.md](DEVELOPER.md)
@@ -38,13 +38,32 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
38
38
  @executor&.shutdown
39
39
  end
40
40
 
41
+ def time_to_milliseconds(time)
42
+ timestamp = time_to_logstash_timestamp(time)
43
+ if timestamp.nil?
44
+ return nil
45
+ end
46
+
47
+ (timestamp.to_f * 1000).to_i # to_f gives seconds with a fractional portion
48
+ end
49
+
50
+
51
+ def time_to_logstash_timestamp(time)
52
+ begin
53
+ LogStash::Timestamp.coerce(time)
54
+ rescue
55
+ nil
56
+ end
57
+ end
58
+
59
+
41
60
  def encode(event_hash)
42
61
  log_message_hash = {
43
- 'attributes' => {}
62
+ :attributes => {}
44
63
  }
45
64
 
46
65
  if event_hash.has_key?('message')
47
- log_message_hash['attributes'] = maybe_parse_json(event_hash['message'])
66
+ log_message_hash[:attributes] = maybe_parse_json(event_hash['message'])
48
67
  end
49
68
 
50
69
  event_hash.each do |key, value|
@@ -52,22 +71,17 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
52
71
  # intrinsic attributes go at the top level
53
72
  log_message_hash[key] = value
54
73
  elsif key == '@timestamp'
55
- # Do not add @timestamp
74
+ milliseconds = time_to_milliseconds(value)
75
+ if !milliseconds.nil?
76
+ log_message_hash[:timestamp] = milliseconds
77
+ end
56
78
  else
57
79
  # non-intrinsic attributes get put into 'attributes'
58
- log_message_hash['attributes'][key] = value
80
+ log_message_hash[:attributes][key] = value
59
81
  end
60
82
  end
61
-
62
- log_message_hash
63
- end
64
83
 
65
- def maybe_parse_message_json(event_hash)
66
- if event_hash.has_key?('message')
67
- message = event_hash['message']
68
- event_hash = event_hash.merge(maybe_parse_json(message))
69
- end
70
- event_hash
84
+ log_message_hash
71
85
  end
72
86
 
73
87
  def maybe_parse_json(message)
@@ -77,8 +91,9 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
77
91
  return parsed
78
92
  end
79
93
  rescue JSON::ParserError
94
+ # Not JSON
80
95
  end
81
- return {}
96
+ {}
82
97
  end
83
98
 
84
99
  def multi_receive(events)
@@ -87,15 +102,15 @@ class LogStash::Outputs::NewRelic < LogStash::Outputs::Base
87
102
  payload.push(encode(event.to_hash))
88
103
  end
89
104
  payload = {
90
- 'common' => {
91
- 'attributes' => {
92
- 'plugin' => {
93
- 'type' => 'logstash',
94
- 'version' => LogStash::Outputs::NewRelicVersion::VERSION,
95
- }
96
- }
97
- },
98
- 'logs' => payload
105
+ :common => {
106
+ :attributes => {
107
+ :plugin => {
108
+ :type => 'logstash',
109
+ :version => LogStash::Outputs::NewRelicVersion::VERSION,
110
+ }
111
+ }
112
+ },
113
+ :logs => payload
99
114
  }
100
115
  @semaphor.acquire()
101
116
  execute = @executor.java_method :submit, [java.lang.Runnable]
@@ -1,7 +1,7 @@
1
1
  module LogStash
2
2
  module Outputs
3
3
  module NewRelicVersion
4
- VERSION = "1.0.2"
4
+ VERSION = "1.0.3"
5
5
  end
6
6
  end
7
7
  end
@@ -24,6 +24,13 @@ describe LogStash::Outputs::NewRelic do
24
24
  }
25
25
  }
26
26
 
27
+ # An arbitrary time to use in these tests, with different representations
28
+ class FixedTime
29
+ MILLISECONDS = 1562888528123
30
+ ISO_8601_STRING_TIME = '2019-07-11T23:42:08.123Z'
31
+ LOGSTASH_TIMESTAMP = LogStash::Timestamp.coerce(ISO_8601_STRING_TIME)
32
+ end
33
+
27
34
  def gunzip(bytes)
28
35
  gz = Zlib::GzipReader.new(StringIO.new(bytes))
29
36
  gz.read
@@ -39,6 +46,16 @@ describe LogStash::Outputs::NewRelic do
39
46
  JSON.parse(gunzip(body))
40
47
  end
41
48
 
49
+ def now_in_milliseconds()
50
+ (Time.now.to_f * 1000).to_i # to_f gives seconds with a fractional portion
51
+ end
52
+
53
+ def within_five_seconds_of(time_in_millis, expected_in_millis)
54
+ five_seconds_in_millis = 5 * 1000
55
+ (time_in_millis - expected_in_millis).abs < five_seconds_in_millis
56
+ end
57
+
58
+
42
59
  before(:each) do
43
60
  @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(simple_config)
44
61
  @newrelic_output.register
@@ -61,7 +78,7 @@ describe LogStash::Outputs::NewRelic do
61
78
  it "all present" do
62
79
  stub_request(:any, base_uri).to_return(status: 200)
63
80
 
64
- event = LogStash::Event.new({ "message" => "Test message" })
81
+ event = LogStash::Event.new({:message => "Test message" })
65
82
  @newrelic_output.multi_receive([event])
66
83
 
67
84
  wait_for(a_request(:post, base_uri)
@@ -72,6 +89,7 @@ describe LogStash::Outputs::NewRelic do
72
89
  })).to have_been_made
73
90
  end
74
91
  end
92
+
75
93
  context "request body" do
76
94
 
77
95
  it "message contains plugin information" do
@@ -88,16 +106,47 @@ describe LogStash::Outputs::NewRelic do
88
106
  .to have_been_made
89
107
  end
90
108
 
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
109
 
95
- event = LogStash::Event.new({ :message => "Test message", :@timestamp => '123' })
96
- @newrelic_output.multi_receive([event])
110
+ context "@timestamp field" do
97
111
 
98
- wait_for(a_request(:post, base_uri)
99
- .with { |request| single_gzipped_message(request.body)['@timestamp'] == nil })
100
- .to have_been_made
112
+ def test_timestamp(timestamp, message_matcher)
113
+ stub_request(:any, base_uri).to_return(status: 200)
114
+
115
+ event = LogStash::Event.new({ :message => "Test message", :@timestamp => timestamp })
116
+ @newrelic_output.multi_receive([event])
117
+
118
+ wait_for(a_request(:post, base_uri)
119
+ .with { |request|
120
+ message = single_gzipped_message(request.body)
121
+ message_matcher.call(message)
122
+ }).to have_been_made
123
+ end
124
+
125
+ it "LogStash::Timestamp '@timestamp' field is added as 'timestamp' as milliseconds since epoch" do
126
+ def message_matcher(message)
127
+ message['timestamp'] == FixedTime::MILLISECONDS
128
+ end
129
+
130
+ test_timestamp(FixedTime::LOGSTASH_TIMESTAMP, method(:message_matcher))
131
+ end
132
+
133
+ it "String ISO 8601 '@timestamp' field is added as 'timestamp' as milliseconds since epoch" do
134
+ def message_matcher(message)
135
+ message['timestamp'] == FixedTime::MILLISECONDS
136
+ end
137
+
138
+ test_timestamp(FixedTime::ISO_8601_STRING_TIME, method(:message_matcher))
139
+ end
140
+
141
+ # Ideally we might not even set a timestamp if parsing fails. However, the time parsing library doesn't tell us if
142
+ # parsing fails -- it just returns the current time.
143
+ it "Unparseable '@timestamp' field is parsed as current time and put into 'timestamp'" do
144
+ def message_matcher(message)
145
+ within_five_seconds_of(message['timestamp'], now_in_milliseconds)
146
+ end
147
+
148
+ test_timestamp("Not an ISO 8601 string", method(:message_matcher))
149
+ end
101
150
  end
102
151
 
103
152
  it "all other fields passed through as is" do
@@ -109,7 +158,6 @@ describe LogStash::Outputs::NewRelic do
109
158
  wait_for(a_request(:post, base_uri)
110
159
  .with { |request|
111
160
  message = single_gzipped_message(request.body)
112
- puts message
113
161
  message['message'] == 'Test message' &&
114
162
  message['attributes']['other'] == 'Other value' })
115
163
  .to have_been_made
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.0.2
4
+ version: 1.0.3
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-07-09 00:00:00.000000000 Z
11
+ date: 2019-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement