Skip to content

Commit f3b8fd4

Browse files
authored
Merge pull request #13 from logicmonitor/Add-metadata-limit-and-change-user-agent
Add user agent as lm-logs-logstash/{plugin-version}. Do not send event metadata by default. Add config include_metadata_keys.
2 parents a45206d + e6e83c1 commit f3b8fd4

File tree

6 files changed

+169
-38
lines changed

6 files changed

+169
-38
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
## 1.1.0
88
- Disable mfa for gem push
99
## 1.2.0
10-
- Add metadata by default to every log
10+
- Add metadata by default to every log
1111
## 1.2.1
1212
- Fix ensuring metadata exists before deleting the original while forwarding to lm-logs
1313
## 1.3.0
14-
- Add bearer token support for authentication with Logicmonitor
14+
- Add bearer token support for authentication with Logicmonitor
15+
## 1.3.1
16+
- Fix user agent populated in headers
17+
## 2.0.0
18+
- Dont send event metadata by default. Add config include_metadata_keys for including custom metadata

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![Gem Version](https://badge.fury.io/rb/logstash-output-lmlogs.svg)](https://badge.fury.io/rb/logstash-output-lmlogs)
22

3-
This plugin sends Logstash events to the [Logicmonitor Logs](https://www.logicmonitor.com)
3+
This plugin sends Logstash events to the [Logicmonitor Logs](https://www.logicmonitor.com)
44

55
# Getting started
66

@@ -28,27 +28,28 @@ You would need either `access_id` and `access_id` both or `bearer_token` for aut
2828

2929
| Option | Description| Default |
3030
| --- | --- | --- |
31-
| batch_size | Event batch size to send to LM Logs.| 100 |
31+
| batch_size | Event batch size to send to LM Logs.| 100 |
3232
| message_key | Key that will be used by the plugin as the system key | "message" |
3333
| lm_property | Key that will be used by LM to match resource based on property | "system.hostname" |
3434
| keep_timestamp | If false, LM Logs will use the ingestion timestamp as the event timestamp | true |
3535
| timestamp_is_key | If true, LM Logs will use a specified key as the event timestamp | false |
3636
| timestamp_key | If timestamp_is_key is set, LM Logs will use this key in the event as the timestamp | "logtimestamp" |
37-
| include_metadata | If false, the metadata fields will not be sent to LM Logs | true |
37+
| include_metadata | If true, all metadata fields will be sent to LM Logs | false |
38+
| include_metadata_keys | Array of json keys for which plugin looks for these keys and adds as event meatadata. A dot "." can be used to add nested subjson. If config `include_metadata` is set to true, all metadata will be sent regardless of this config. | [] |
3839

3940
See the [source code](lib/logstash/outputs/lmlogs.rb) for the full list of options
4041

4142
The syntax for `message_key` and `source_key` values are available in the [Logstash Event API Documentation](https://www.elastic.co/guide/en/logstash/current/event-api.html)
4243

43-
## Known issues
44+
## Known issues
4445
- Installation of the plugin fails on Logstash 6.2.1.
45-
46-
46+
47+
4748
## Contributing
48-
49+
4950
Bug reports and pull requests are welcome. This project is intended to
5051
be a safe, welcoming space for collaboration.
51-
52+
5253
## Development
53-
54+
5455
We use docker to build the plugin. You can build it by running `docker-compose run jruby gem build logstash-output-lmlogs.gemspec `

lib/logstash/outputs/lmlogs.rb

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'base64'
99
require 'openssl'
1010
require 'manticore'
11+
require_relative "version"
1112

1213
# An example output that does nothing.
1314
class LogStash::Outputs::LMLogs < LogStash::Outputs::Base
@@ -94,14 +95,17 @@ class InvalidHTTPConfigError < StandardError; end
9495
config :access_id, :validate => :string, :required => false, :default => nil
9596

9697
# Include/Exclude metadata from sending to LM Logs
97-
config :include_metadata, :validate => :boolean, :default => true
98+
config :include_metadata, :validate => :boolean, :default => false
9899

99100
# Password to use for HTTP auth
100101
config :access_key, :validate => :password, :required => false, :default => nil
101102

102103
# Use bearer token instead of access key/id for authentication.
103104
config :bearer_token, :validate => :password, :required => false, :default => nil
104105

106+
# json keys for which plugin looks for these keys and adds as event meatadata. A dot "." can be used to add nested subjson.
107+
config :include_metadata_keys, :validate => :array, :required => false, :default => []
108+
105109
@@MAX_PAYLOAD_SIZE = 8*1024*1024
106110

107111
# For developer debugging.
@@ -116,6 +120,14 @@ def register
116120
logger.info("Max Payload Size: ",
117121
:size => @@MAX_PAYLOAD_SIZE)
118122
configure_auth
123+
124+
@final_metadata_keys = Hash.new
125+
if @include_metadata_keys.any?
126+
include_metadata_keys.each do | nested_key |
127+
@final_metadata_keys[nested_key] = nested_key.to_s.split('.')
128+
end
129+
end
130+
119131
end # def register
120132

121133
def client_config
@@ -164,12 +176,12 @@ def configure_auth
164176
if @access_id == nil || @access_key.value == nil
165177
@logger.info "Access Id or access key null. Using bearer token for authentication."
166178
@use_bearer_instead_of_lmv1 = true
167-
end
168-
if @use_bearer_instead_of_lmv1 && @bearer_token.value == nil
179+
end
180+
if @use_bearer_instead_of_lmv1 && @bearer_token.value == nil
169181
@logger.error "Bearer token not specified. Either access_id and access_key both or bearer_token must be specified for authentication with Logicmonitor."
170182
raise LogStash::ConfigurationError, 'No valid authentication specified. Either access_id and access_key both or bearer_token must be specified for authentication with Logicmonitor.'
171183
end
172-
end
184+
end
173185
def generate_auth_string(body)
174186
if @use_bearer_instead_of_lmv1
175187
return "Bearer #{@bearer_token.value}"
@@ -196,7 +208,7 @@ def send_batch(events)
196208
:body => body,
197209
:headers => {
198210
"Content-Type" => "application/json",
199-
"User-Agent" => "LM Logs Logstash Plugin",
211+
"User-Agent" => "lm-logs-logstash/" + LmLogsLogstashPlugin::VERSION,
200212
"Authorization" => "#{auth_string}"
201213
}
202214
})
@@ -265,34 +277,48 @@ def multi_receive(events)
265277
events.each_slice(@batch_size) do |chunk|
266278
documents = []
267279
chunk.each do |event|
268-
event_json = JSON.parse(event.to_json)
269-
lmlogs_event = {}
270-
271-
if @include_metadata
272-
lmlogs_event = event_json
273-
lmlogs_event.delete("@timestamp") # remove redundant timestamp field
274-
if lmlogs_event.dig("event", "original") != nil
275-
lmlogs_event["event"].delete("original") # remove redundant log field
276-
end
277-
end
278280

279-
lmlogs_event["message"] = event.get(@message_key).to_s
280-
lmlogs_event["_lm.resourceId"] = {}
281-
lmlogs_event["_lm.resourceId"]["#{@lm_property}"] = event.get(@property_key.to_s)
281+
documents = isValidPayloadSize(documents, processEvent(event), @@MAX_PAYLOAD_SIZE)
282+
end
283+
send_batch(documents)
284+
end
285+
end
282286

283-
if @keep_timestamp
284-
lmlogs_event["timestamp"] = event.get("@timestamp")
285-
end
286287

287-
if @timestamp_is_key
288-
lmlogs_event["timestamp"] = event.get(@timestamp_key.to_s)
288+
def processEvent(event)
289+
event_json = JSON.parse(event.to_json)
290+
lmlogs_event = {}
291+
292+
if @include_metadata
293+
lmlogs_event = event_json
294+
lmlogs_event.delete("@timestamp") # remove redundant timestamp field
295+
if lmlogs_event.dig("event", "original") != nil
296+
lmlogs_event["event"].delete("original") # remove redundant log field
297+
end
298+
elsif @final_metadata_keys
299+
@final_metadata_keys.each do | key, value |
300+
nestedVal = event_json
301+
value.each { |x| nestedVal = nestedVal[x] }
302+
if nestedVal != nil
303+
lmlogs_event[key] = nestedVal
289304
end
305+
end
306+
end
290307

291-
documents = isValidPayloadSize(documents,lmlogs_event,@@MAX_PAYLOAD_SIZE)
308+
lmlogs_event["message"] = event.get(@message_key).to_s
309+
lmlogs_event["_lm.resourceId"] = {}
310+
lmlogs_event["_lm.resourceId"]["#{@lm_property}"] = event.get(@property_key.to_s)
292311

293-
end
294-
send_batch(documents)
312+
if @keep_timestamp
313+
lmlogs_event["timestamp"] = event.get("@timestamp")
314+
end
315+
316+
if @timestamp_is_key
317+
lmlogs_event["timestamp"] = event.get(@timestamp_key.to_s)
295318
end
319+
320+
return lmlogs_event
321+
296322
end
297323

298324
def log_failure(message, opts)

lib/logstash/outputs/version.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
module LmLogsLogstashPlugin
4+
VERSION = '2.0.0'
5+
end

logstash-output-lmlogs.gemspec

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
2+
lib = File.expand_path('../lib', __FILE__)
3+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4+
5+
require "logstash/outputs/version.rb"
6+
7+
8+
19
Gem::Specification.new do |s|
210
s.name = 'logstash-output-lmlogs'
3-
s.version = '1.3.0'
11+
s.version = LmLogsLogstashPlugin::VERSION
412
s.licenses = ['Apache-2.0']
513
s.summary = "Logstash output plugin for LM Logs"
614
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"
@@ -25,4 +33,5 @@ Gem::Specification.new do |s|
2533
s.add_runtime_dependency 'manticore', '>= 0.5.2', '< 1.0.0'
2634

2735
s.add_development_dependency 'logstash-devutils'
36+
s.add_development_dependency 'hashdiff', '>= 1.0.0'
2837
end

spec/outputs/metadata_spec.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# encoding: utf-8
2+
require "logstash/devutils/rspec/spec_helper"
3+
require "logstash/outputs/lmlogs"
4+
require "logstash/event"
5+
require "hashdiff"
6+
7+
8+
describe LogStash::Outputs::LMLogs do
9+
10+
11+
let(:logstash_event) {LogStash::Event.new("message" => "hello this is log 1",
12+
"host" => "host1",
13+
"nested1" => {"nested2" => {"nested3" => "value"},
14+
"nested2a" => {"nested3a" => {"nested4" => "valueA"}},
15+
"nested2b" => {"nested3b" => "value"}
16+
},
17+
"nested1_" => "value",
18+
"nested" => {"nested2" => {"nested3" => "value",
19+
"nested3b" => "value"},
20+
"nested_ignored" => "somevalue"
21+
}
22+
)}
23+
let(:sample_lm_logs_event){{"message" => "hello this is log 1", "_lm.resourceId" => {"test.property" => "host1"}, "timestamp" => "2021-03-22T04:28:55.907121106Z"}}
24+
let(:include_metadata_keys) {["host", "nested1.nested2.nested3", "nested1.nested2a", "nested.nested2" ]}
25+
26+
def create_output_plugin_with_conf(conf)
27+
return LogStash::Outputs::LMLogs.new(conf)
28+
end
29+
30+
def check_same_hash(h1,h2)
31+
32+
end
33+
34+
it "default behaviour" do
35+
puts "default behaviour"
36+
plugin = create_output_plugin_with_conf({
37+
"portal_name" => "localhost",
38+
"access_id" => "abcd",
39+
"access_key" => "abcd",
40+
"lm_property" => "system.hostname",
41+
"property_key" => "host"
42+
})
43+
constructed_event = plugin.processEvent(logstash_event)
44+
expected_event = {
45+
"message" => "hello this is log 1",
46+
"timestamp" => logstash_event.timestamp,
47+
"_lm.resourceId" => {"system.hostname" => "host1"},
48+
49+
}
50+
puts " actual : #{constructed_event} \n expected : #{expected_event}"
51+
52+
expect(Hashdiff.diff(constructed_event,expected_event)).to eq([])
53+
end
54+
55+
it "with include_metadata set to true" do
56+
puts "with include_metadata set to true"
57+
plugin = create_output_plugin_with_conf({
58+
"portal_name" => "localhost",
59+
"access_id" => "abcd",
60+
"access_key" => "abcd",
61+
"lm_property" => "system.hostname",
62+
"property_key" => "host",
63+
"include_metadata" => true
64+
})
65+
constructed_event = plugin.processEvent(logstash_event)
66+
expected_event = {
67+
"message" => "hello this is log 1",
68+
"timestamp" => logstash_event.timestamp,
69+
"@version" => "1",
70+
"_lm.resourceId" => {"system.hostname" => "host1"},
71+
"host" => "host1",
72+
"nested1" => {"nested2" => {"nested3" => "value"},
73+
"nested2a" => {"nested3a" => {"nested4" => "valueA"}},
74+
"nested2b" => {"nested3b" => "value"}
75+
},
76+
"nested1_" => "value",
77+
"nested" => {"nested2" => {"nested3" => "value",
78+
"nested3b" => "value"},
79+
"nested_ignored" => "somevalue"
80+
}
81+
}
82+
puts " actual : #{constructed_event} \n expected : #{expected_event}"
83+
puts " hash diff : #{Hashdiff.diff(constructed_event,expected_event)}"
84+
expect(Hashdiff.diff(constructed_event,expected_event)).to eq([])
85+
end
86+
end

0 commit comments

Comments
 (0)