lumberjack_ecs_device 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGE_LOG.md +3 -0
- data/MIT_LICENSE +20 -0
- data/README.md +38 -0
- data/VERSION +1 -0
- data/lib/lumberjack_ecs_device.rb +175 -0
- data/lumberjack_ecs_device.gemspec +28 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: edb8e82b2c9f2ddd629c9efddf43f63ac4f5b984b754a9466c5d714e22727f0b
|
4
|
+
data.tar.gz: 52bd32275d8e55f1e03cef9aa661e19fcf4de2fe5296c277dcf161cb523a1258
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f0a44ee035ae0053a1d18927051f57d470dbb12b913a18c9e363897ad1148cd74f3d7dd62929377438e9d0657596338ef11b0f32d0951959c8b72e8c7346a29
|
7
|
+
data.tar.gz: 3ce536acc0ba277dc0d193bc5fa1dd8fb0bf779cc53f9235b6f749d9842a11388d8b5eb03f8c53b7125d1d5f4400f6ea3fa5d3919e606800d64eda6b144129a7
|
data/CHANGE_LOG.md
ADDED
data/MIT_LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2021 Brian Durand
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Lumberjack ECS Device
|
2
|
+
|
3
|
+
![Continuous Integration](https://github.com/bdurand/lumberjack_ecs_device/workflows/Continuous%20Integration/badge.svg)
|
4
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/97e98dc4d3d2565a3208/maintainability)](https://codeclimate.com/github/bdurand/lumberjack_ecs_device/maintainability)
|
5
|
+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
6
|
+
|
7
|
+
This gem provides a logging device that produces JSON output that matches the standard fields defined for the [Elastic Common Schema](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html). This allows logs to be sent seamlessly to Kibana or other servers that expect this format.
|
8
|
+
|
9
|
+
* The time will be sent as "@timestamp" with a precision in microseconds.
|
10
|
+
|
11
|
+
* The severity will be sent as "log.level" with a string label (DEBUG, INFO, WARN, ERROR, FATAL).
|
12
|
+
|
13
|
+
* The progname will be sent as "process.name"
|
14
|
+
|
15
|
+
* The pid will be sent as "process.pid".
|
16
|
+
|
17
|
+
* The message will be sent as "message". In addition, if the message is an exception, the error message, class, and backtrace will be sent as "error.message", "error.type", and "error.stack_trace".
|
18
|
+
|
19
|
+
* If the "error" tag contains an exception, it will be sent as "error.message", "error.type", and "error.stack_trace".
|
20
|
+
|
21
|
+
* A duration can be sent as a number of seconds in the "duration" tag or as a number of milliseconds in the "duration_ms" tag or as a number of microsectons in the "duration_micros" tag or as a number of nanoseconds in the "duration_ns" tag. The value will be sent as "event.duration" and converted to nanoseconds.
|
22
|
+
|
23
|
+
* All other log tags are sent as is. If a tag name includes a dot, it will be sent as a nested JSON structure.
|
24
|
+
|
25
|
+
This device extends from [`Lumberjack::JsonDevice`](). It is not tied to ECS or Kibana in any way other than that it is opinionated about how to map and format some log tags. It can be used with other services or pipelines without issue.
|
26
|
+
|
27
|
+
## Example
|
28
|
+
|
29
|
+
You could log an HTTP request to some of the ECS standard fields like this:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
logger.tag("http.request.method" => request.method, "url.full" => request.url) do
|
33
|
+
logger.info("#{request.method} #{request.path} finished in #{elapsed_time} seconds",
|
34
|
+
duration: elapsed_time,
|
35
|
+
"http.response.status_code" => response.status
|
36
|
+
)
|
37
|
+
end
|
38
|
+
```
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "lumberjack_json_device"
|
4
|
+
|
5
|
+
module Lumberjack
|
6
|
+
# This Lumberjack device logs output to another device as JSON formatted text that maps fields
|
7
|
+
# to the standard ECS JSON format.
|
8
|
+
#
|
9
|
+
# See https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html
|
10
|
+
class EcsDevice < JsonDevice
|
11
|
+
module ExceptionHash
|
12
|
+
protected
|
13
|
+
|
14
|
+
def exception_hash(exception, device)
|
15
|
+
hash = {"type" => exception.class.name}
|
16
|
+
hash["message"] = exception.message unless exception.message.nil?
|
17
|
+
trace = exception.backtrace
|
18
|
+
if trace && device && device.respond_to?(:backtrace_cleaner) && device.backtrace_cleaner
|
19
|
+
trace = device.backtrace_cleaner.call(trace)
|
20
|
+
end
|
21
|
+
hash["stack_trace"] = trace if trace
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Formatter to format a messge as an error if it is an exception.
|
27
|
+
class MessageExceptionFormatter
|
28
|
+
include ExceptionHash
|
29
|
+
|
30
|
+
def initialize(device = nil)
|
31
|
+
@device = device
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(object)
|
35
|
+
if object.is_a?(Exception)
|
36
|
+
{
|
37
|
+
"message" => object.inspect,
|
38
|
+
"error" => exception_hash(object, @device)
|
39
|
+
}
|
40
|
+
elsif object.is_a?(Hash)
|
41
|
+
{"message" => object}
|
42
|
+
elsif object.nil?
|
43
|
+
{"message" => nil}
|
44
|
+
else
|
45
|
+
message = object.to_s
|
46
|
+
max_message_length = @device.max_message_length
|
47
|
+
if max_message_length && message.length > max_message_length
|
48
|
+
message = message[0, max_message_length]
|
49
|
+
end
|
50
|
+
{"message" => message}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Formatter to remove empty tag values and expand the error tag if it is an exception.
|
56
|
+
class EcsTagsFormatter
|
57
|
+
include ExceptionHash
|
58
|
+
|
59
|
+
def initialize(device = nil)
|
60
|
+
@device = device
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(tags)
|
64
|
+
copy = {}
|
65
|
+
tags.each do |name, value|
|
66
|
+
value = remove_empty_values(value)
|
67
|
+
next if value.nil?
|
68
|
+
|
69
|
+
if name == "error" && value.is_a?(Exception)
|
70
|
+
copy[name] = exception_hash(value, @device)
|
71
|
+
elsif name.include?(".")
|
72
|
+
names = name.split(".")
|
73
|
+
next_value_in_hash(copy, names, value)
|
74
|
+
else
|
75
|
+
copy[name] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
copy
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def remove_empty_values(value)
|
84
|
+
if value.is_a?(String)
|
85
|
+
value unless value.empty?
|
86
|
+
elsif value.is_a?(Hash)
|
87
|
+
new_hash = {}
|
88
|
+
value.each do |key, val|
|
89
|
+
val = remove_empty_values(val)
|
90
|
+
new_hash[key] = val unless val.nil?
|
91
|
+
end
|
92
|
+
new_hash unless new_hash.empty?
|
93
|
+
elsif value.is_a?(Array)
|
94
|
+
new_array = value.collect { |val| remove_empty_values(val) }
|
95
|
+
new_array unless new_array.empty?
|
96
|
+
else
|
97
|
+
value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def next_value_in_hash(hash, keys, value)
|
102
|
+
key = keys.first
|
103
|
+
if keys.size == 1
|
104
|
+
hash[key] = value
|
105
|
+
else
|
106
|
+
current = hash[key]
|
107
|
+
unless current.is_a?(Hash)
|
108
|
+
current = {}
|
109
|
+
hash[key] = current
|
110
|
+
end
|
111
|
+
next_value_in_hash(current, keys[1, keys.size], value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class DurationFormatter
|
117
|
+
def initialize(multiplier)
|
118
|
+
@multiplier = multiplier
|
119
|
+
end
|
120
|
+
|
121
|
+
def call(value)
|
122
|
+
if value.is_a?(Numeric)
|
123
|
+
value = (value.to_f * @multiplier).round
|
124
|
+
end
|
125
|
+
{"event" => {"duration" => value}}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
ECS_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%6NZ"
|
130
|
+
|
131
|
+
# You can specify a backtrace cleaner that will be called with exception backtraces before they
|
132
|
+
# are added to the payload. You can use this to remove superfluous lines, compress line length, etc.
|
133
|
+
# One use for it is to keep stack traces clean and prevent them from overflowing the limit on
|
134
|
+
# the payload size for an individual log entry.
|
135
|
+
attr_accessor :backtrace_cleaner
|
136
|
+
|
137
|
+
# You can specify a limit on the message size. Messages over this size will be split into multiple
|
138
|
+
# log entries to prevent overflowing the limit on message size which makes the log entries unparseable.
|
139
|
+
attr_accessor :max_message_length
|
140
|
+
|
141
|
+
def initialize(stream_or_device, backtrace_cleaner: nil, max_message_length: nil, datetime_format: ECS_TIMESTAMP_FORMAT)
|
142
|
+
super(stream_or_device, mapping: ecs_mapping, datetime_format: datetime_format)
|
143
|
+
self.backtrace_cleaner = backtrace_cleaner
|
144
|
+
self.max_message_length = max_message_length
|
145
|
+
@utc_timestamps = !!datetime_format.match(/[^%]Z/)
|
146
|
+
end
|
147
|
+
|
148
|
+
def entry_as_json(entry)
|
149
|
+
original_time = entry.time
|
150
|
+
begin
|
151
|
+
entry.time = entry.time.utc if @utc_timestamps && !entry.time.utc?
|
152
|
+
super
|
153
|
+
ensure
|
154
|
+
entry.time = original_time
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def ecs_mapping
|
161
|
+
{
|
162
|
+
time: "@timestamp",
|
163
|
+
severity: ["log", "level"],
|
164
|
+
progname: ["process", "name"],
|
165
|
+
pid: ["process", "pid"],
|
166
|
+
message: MessageExceptionFormatter.new(self),
|
167
|
+
duration: DurationFormatter.new(1_000_000_000),
|
168
|
+
duration_ms: DurationFormatter.new(1_000_000),
|
169
|
+
duration_micros: DurationFormatter.new(1_000),
|
170
|
+
duration_ns: ["event", "duration"],
|
171
|
+
tags: EcsTagsFormatter.new(self)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "lumberjack_ecs_device"
|
3
|
+
spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = "A logging device for formatting logs in Elastic Container Schema (ECS) format for integration with Kibana."
|
8
|
+
spec.homepage = "https://github.com/bdurand/lumberjack_ecs_device"
|
9
|
+
spec.license = "MIT"
|
10
|
+
|
11
|
+
# Specify which files should be added to the gem when it is released.
|
12
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
+
ignore_files = %w[
|
14
|
+
.
|
15
|
+
Gemfile
|
16
|
+
Gemfile.lock
|
17
|
+
Rakefile
|
18
|
+
gemfiles/
|
19
|
+
spec/
|
20
|
+
]
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_dependency "lumberjack_json_device", ">=1.0"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lumberjack_ecs_device
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Durand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lumberjack_json_device
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- bbdurand@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- CHANGE_LOG.md
|
35
|
+
- MIT_LICENSE
|
36
|
+
- README.md
|
37
|
+
- VERSION
|
38
|
+
- lib/lumberjack_ecs_device.rb
|
39
|
+
- lumberjack_ecs_device.gemspec
|
40
|
+
homepage: https://github.com/bdurand/lumberjack_ecs_device
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubygems_version: 3.0.3
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: A logging device for formatting logs in Elastic Container Schema (ECS) format
|
63
|
+
for integration with Kibana.
|
64
|
+
test_files: []
|