logstash-output-newrelic 1.0.2 → 1.0.3

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: 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