lumberjack_ecs_device 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ # 1.0.0
2
+
3
+ * Initial release
@@ -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.
@@ -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: []