lumberjack 1.2.10 → 1.3.1
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 +4 -4
- data/ARCHITECTURE.md +244 -0
- data/CHANGELOG.md +46 -0
- data/README.md +146 -57
- data/VERSION +1 -1
- data/lib/lumberjack/context.rb +5 -5
- data/lib/lumberjack/device/rolling_log_file.rb +1 -1
- data/lib/lumberjack/device/writer.rb +12 -8
- data/lib/lumberjack/formatter/date_time_formatter.rb +1 -1
- data/lib/lumberjack/formatter/multiply_formatter.rb +25 -0
- data/lib/lumberjack/formatter/redact_formatter.rb +23 -0
- data/lib/lumberjack/formatter/round_formatter.rb +21 -0
- data/lib/lumberjack/formatter/tagged_message.rb +39 -0
- data/lib/lumberjack/formatter.rb +28 -13
- data/lib/lumberjack/log_entry.rb +58 -16
- data/lib/lumberjack/logger.rb +131 -34
- data/lib/lumberjack/rack/context.rb +20 -1
- data/lib/lumberjack/rack/request_id.rb +6 -2
- data/lib/lumberjack/rack/unit_of_work.rb +5 -1
- data/lib/lumberjack/tag_formatter.rb +102 -27
- data/lib/lumberjack/tagged_logger_support.rb +25 -10
- data/lib/lumberjack/tags.rb +1 -7
- data/lib/lumberjack/template.rb +1 -1
- data/lib/lumberjack/utils.rb +133 -0
- data/lib/lumberjack.rb +10 -5
- data/lumberjack.gemspec +2 -2
- metadata +12 -6
data/lib/lumberjack/context.rb
CHANGED
@@ -5,7 +5,7 @@ module Lumberjack
|
|
5
5
|
class Context
|
6
6
|
attr_reader :tags
|
7
7
|
|
8
|
-
# @param [Context]
|
8
|
+
# @param parent_context [Context] A parent context to inherit tags from.
|
9
9
|
def initialize(parent_context = nil)
|
10
10
|
@tags = {}
|
11
11
|
@tags.merge!(parent_context.tags) if parent_context
|
@@ -13,7 +13,7 @@ module Lumberjack
|
|
13
13
|
|
14
14
|
# Set tags on the context.
|
15
15
|
#
|
16
|
-
# @param [Hash]
|
16
|
+
# @param tags [Hash] The tags to set.
|
17
17
|
# @return [void]
|
18
18
|
def tag(tags)
|
19
19
|
tags.each do |key, value|
|
@@ -23,7 +23,7 @@ module Lumberjack
|
|
23
23
|
|
24
24
|
# Get a context tag.
|
25
25
|
#
|
26
|
-
# @param [String, Symbol]
|
26
|
+
# @param key [String, Symbol] The tag key.
|
27
27
|
# @return [Object] The tag value.
|
28
28
|
def [](key)
|
29
29
|
@tags[key.to_s]
|
@@ -31,8 +31,8 @@ module Lumberjack
|
|
31
31
|
|
32
32
|
# Set a context tag.
|
33
33
|
#
|
34
|
-
# @param [String, Symbol]
|
35
|
-
# @param [Object]
|
34
|
+
# @param key [String, Symbol] The tag key.
|
35
|
+
# @param value [Object] The tag value.
|
36
36
|
# @return [void]
|
37
37
|
def []=(key, value)
|
38
38
|
@tags[key.to_s] = value
|
@@ -55,9 +55,8 @@ module Lumberjack
|
|
55
55
|
# :additional_lines and :time_format options will be passed through to the
|
56
56
|
# Template constuctor.
|
57
57
|
#
|
58
|
-
# The default template is "[:time :severity :progname(:pid)
|
59
|
-
# with additional lines formatted as "\n
|
60
|
-
# work id will only appear if it is present.
|
58
|
+
# The default template is "[:time :severity :progname(:pid)] :message"
|
59
|
+
# with additional lines formatted as "\n :message".
|
61
60
|
#
|
62
61
|
# The size of the internal buffer in bytes can be set by providing :buffer_size (defaults to 32K).
|
63
62
|
#
|
@@ -68,12 +67,12 @@ module Lumberjack
|
|
68
67
|
@stream = stream
|
69
68
|
@stream.sync = true if @stream.respond_to?(:sync=)
|
70
69
|
@buffer = Buffer.new
|
71
|
-
@buffer_size =
|
72
|
-
template =
|
70
|
+
@buffer_size = options[:buffer_size] || 0
|
71
|
+
template = options[:template] || DEFAULT_FIRST_LINE_TEMPLATE
|
73
72
|
if template.respond_to?(:call)
|
74
73
|
@template = template
|
75
74
|
else
|
76
|
-
additional_lines =
|
75
|
+
additional_lines = options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE
|
77
76
|
@template = Template.new(template, additional_lines: additional_lines, time_format: options[:time_format])
|
78
77
|
end
|
79
78
|
end
|
@@ -150,6 +149,13 @@ module Lumberjack
|
|
150
149
|
end
|
151
150
|
end
|
152
151
|
|
152
|
+
# Return the underlying stream. Provided for API compatibility with Logger devices.
|
153
|
+
#
|
154
|
+
# @return [IO] The underlying stream.
|
155
|
+
def dev
|
156
|
+
@stream
|
157
|
+
end
|
158
|
+
|
153
159
|
protected
|
154
160
|
|
155
161
|
# Set the underlying stream.
|
@@ -163,8 +169,6 @@ module Lumberjack
|
|
163
169
|
def write_to_stream(lines)
|
164
170
|
return if lines.empty?
|
165
171
|
lines = lines.first if lines.is_a?(Array) && lines.size == 1
|
166
|
-
|
167
|
-
out = nil
|
168
172
|
out = if lines.is_a?(Array)
|
169
173
|
"#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}"
|
170
174
|
else
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
class Formatter
|
5
|
+
# This formatter can be used to multiply a numeric value by a specified multiplier and
|
6
|
+
# optionally round to a specified number of decimal places.
|
7
|
+
class MultiplyFormatter
|
8
|
+
# @param multiplier [Numeric] The multiplier to apply to the value.
|
9
|
+
# @param decimals [Integer, nil] The number of decimal places to round the result to.
|
10
|
+
# If nil, no rounding is applied.
|
11
|
+
def initialize(multiplier, decimals = nil)
|
12
|
+
@multiplier = multiplier
|
13
|
+
@decimals = decimals
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(value)
|
17
|
+
return value unless value.is_a?(Numeric)
|
18
|
+
|
19
|
+
value *= @multiplier
|
20
|
+
value = value.round(@decimals) if @decimals
|
21
|
+
value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
class Formatter
|
5
|
+
# Log sensitive information in a redacted format showing the firat and last
|
6
|
+
# characters of the value, with the rest replaced by asterisks. The number of
|
7
|
+
# characters shown is dependent onthe length of the value; short values will
|
8
|
+
# not show any characters in order to avoid revealing too much information.
|
9
|
+
class RedactFormatter
|
10
|
+
def call(obj)
|
11
|
+
return obj unless obj.is_a?(String)
|
12
|
+
|
13
|
+
if obj.length > 8
|
14
|
+
"#{obj[0..1]}#{"*" * (obj.length - 4)}#{obj[-2..-1]}"
|
15
|
+
elsif obj.length > 5
|
16
|
+
"#{obj[0]}#{"*" * (obj.length - 2)}#{obj[-1]}"
|
17
|
+
else
|
18
|
+
"*****"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
class Formatter
|
5
|
+
# Round numeric values to a set number of decimal places. This is useful when logging
|
6
|
+
# floating point numbers to reduce noise and rounding errors in the logs.
|
7
|
+
class RoundFormatter
|
8
|
+
def initialize(precision = 3)
|
9
|
+
@precision = precision
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(obj)
|
13
|
+
if obj.is_a?(Numeric)
|
14
|
+
obj.round(@precision)
|
15
|
+
else
|
16
|
+
obj
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
class Formatter
|
5
|
+
# This class can be used as the return value from a formatter `call` method to
|
6
|
+
# extract additional tags from an object being logged. This can be useful when there
|
7
|
+
# using structured logging to include important metadata in the log message.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # Automatically add tags with error details when logging an exception.
|
11
|
+
# logger.add_formatter(Exception, ->(e) {
|
12
|
+
# Lumberjack::Formatter::TaggedMessage.new(e.message, {
|
13
|
+
# error: {
|
14
|
+
# message: e.message,
|
15
|
+
# class: e.class.name,
|
16
|
+
# trace: e.backtrace
|
17
|
+
# }
|
18
|
+
# })
|
19
|
+
# })
|
20
|
+
class TaggedMessage
|
21
|
+
attr_reader :message, :tags
|
22
|
+
|
23
|
+
# @param [Formatter] formatter The formatter to apply the transformation to.
|
24
|
+
# @param [Proc] transform The transformation function to apply to the formatted string.
|
25
|
+
def initialize(message, tags)
|
26
|
+
@message = message
|
27
|
+
@tags = tags || {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
inspect
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
{message: @message, tags: @tags}.inspect
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/lumberjack/formatter.rb
CHANGED
@@ -16,12 +16,16 @@ module Lumberjack
|
|
16
16
|
require_relative "formatter/exception_formatter"
|
17
17
|
require_relative "formatter/id_formatter"
|
18
18
|
require_relative "formatter/inspect_formatter"
|
19
|
+
require_relative "formatter/multiply_formatter"
|
19
20
|
require_relative "formatter/object_formatter"
|
20
21
|
require_relative "formatter/pretty_print_formatter"
|
22
|
+
require_relative "formatter/redact_formatter"
|
23
|
+
require_relative "formatter/round_formatter"
|
21
24
|
require_relative "formatter/string_formatter"
|
22
25
|
require_relative "formatter/strip_formatter"
|
23
26
|
require_relative "formatter/structured_formatter"
|
24
27
|
require_relative "formatter/truncate_formatter"
|
28
|
+
require_relative "formatter/tagged_message"
|
25
29
|
|
26
30
|
class << self
|
27
31
|
# Returns a new empty formatter with no mapping. For historical reasons, a formatter
|
@@ -66,12 +70,12 @@ module Lumberjack
|
|
66
70
|
# help avoid loading dependency issues. This applies only to classes; modules cannot be
|
67
71
|
# passed in as strings.
|
68
72
|
#
|
69
|
-
# @param [Class, Module, String, Array<Class, Module, String>]
|
70
|
-
# @param [Symbol, Class, String, #call]
|
73
|
+
# @param klass [Class, Module, String, Array<Class, Module, String>] The class or module to add a formatter for.
|
74
|
+
# @param formatter [Symbol, Class, String, #call] The formatter to use for the class.
|
71
75
|
# If a symbol is passed in, it will be used to load one of the predefined formatters.
|
72
76
|
# If a class is passed in, it will be initialized with the args passed in.
|
73
77
|
# Otherwise, the object will be used as the formatter and must respond to call method.
|
74
|
-
# @param [Array]
|
78
|
+
# @param args [Array] Arguments to pass to the formatter when it is initialized.
|
75
79
|
# @yield [obj] A block that will be used as the formatter for the class.
|
76
80
|
# @yieldparam [Object] obj The object to format.
|
77
81
|
# @yieldreturn [String] The formatted string.
|
@@ -129,7 +133,7 @@ module Lumberjack
|
|
129
133
|
# help avoid loading dependency issues. This applies only to classes; modules cannot be
|
130
134
|
# passed in as strings.
|
131
135
|
#
|
132
|
-
# @param [Class, Module, String, Array<Class, Module, String>]
|
136
|
+
# @param klass [Class, Module, String, Array<Class, Module, String>] The class or module to remove the formatters for.
|
133
137
|
# @return [self] Returns itself so that remove statements can be chained together.
|
134
138
|
def remove(klass)
|
135
139
|
Array(klass).each do |k|
|
@@ -152,9 +156,16 @@ module Lumberjack
|
|
152
156
|
self
|
153
157
|
end
|
154
158
|
|
159
|
+
# Return true if their are no registered formatters.
|
160
|
+
#
|
161
|
+
# @return [Boolean] true if there are no registered formatters, false otherwise.
|
162
|
+
def empty?
|
163
|
+
@class_formatters.empty? && @module_formatters.empty?
|
164
|
+
end
|
165
|
+
|
155
166
|
# Format a message object by applying all formatters attached to it.
|
156
167
|
#
|
157
|
-
# @param [Object]
|
168
|
+
# @param message [Object] The message object to format.
|
158
169
|
# @return [Object] The formatted object.
|
159
170
|
def format(message)
|
160
171
|
formatter = formatter_for(message.class)
|
@@ -168,18 +179,22 @@ module Lumberjack
|
|
168
179
|
# Compatibility with the Logger::Formatter signature. This method will just convert the message
|
169
180
|
# object to a string and ignores the other parameters.
|
170
181
|
#
|
171
|
-
# @param [Integer, String, Symbol]
|
172
|
-
# @param [Time]
|
173
|
-
# @param [String]
|
174
|
-
# @param [Object]
|
182
|
+
# @param severity [Integer, String, Symbol] The severity of the message.
|
183
|
+
# @param timestamp [Time] The time the message was logged.
|
184
|
+
# @param progname [String] The name of the program logging the message.
|
185
|
+
# @param msg [Object] The message object to format.
|
175
186
|
def call(severity, timestamp, progname, msg)
|
176
|
-
|
187
|
+
formatted_message = format(msg)
|
188
|
+
formatted_message = formatted_message.message if formatted_message.is_a?(TaggedMessage)
|
189
|
+
"#{formatted_message}#{Lumberjack::LINE_SEPARATOR}"
|
177
190
|
end
|
178
191
|
|
179
|
-
private
|
180
|
-
|
181
192
|
# Find the formatter for a class by looking it up using the class hierarchy.
|
182
|
-
|
193
|
+
#
|
194
|
+
# @api private
|
195
|
+
def formatter_for(klass)
|
196
|
+
return nil if empty?
|
197
|
+
|
183
198
|
check_modules = true
|
184
199
|
until klass.nil?
|
185
200
|
formatter = @class_formatters[klass.name]
|
data/lib/lumberjack/log_entry.rb
CHANGED
@@ -8,16 +8,17 @@ module Lumberjack
|
|
8
8
|
|
9
9
|
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
|
10
10
|
|
11
|
+
# @deprecated Will be removed in version 2.0.
|
11
12
|
UNIT_OF_WORK_ID = "unit_of_work_id"
|
12
13
|
|
13
14
|
# Create a new log entry.
|
14
15
|
#
|
15
|
-
# @param [Time]
|
16
|
-
# @param [Integer, String]
|
17
|
-
# @param [String]
|
18
|
-
# @param [String]
|
19
|
-
# @param [Integer]
|
20
|
-
# @param [Hash]
|
16
|
+
# @param time [Time] The time the log entry was created.
|
17
|
+
# @param severity [Integer, String] The severity of the log entry.
|
18
|
+
# @param message [String] The message to log.
|
19
|
+
# @param progname [String] The name of the program that created the log entry.
|
20
|
+
# @param pid [Integer] The process id of the program that created the log entry.
|
21
|
+
# @param tags [Hash<String, Object>] A hash of tags to associate with the log entry.
|
21
22
|
def initialize(time, severity, message, progname, pid, tags)
|
22
23
|
@time = time
|
23
24
|
@severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity))
|
@@ -25,9 +26,9 @@ module Lumberjack
|
|
25
26
|
@progname = progname
|
26
27
|
@pid = pid
|
27
28
|
# backward compatibility with 1.0 API where the last argument was the unit of work id
|
28
|
-
@tags = if tags.
|
29
|
-
tags
|
30
|
-
|
29
|
+
@tags = if tags.is_a?(Hash)
|
30
|
+
compact_tags(tags)
|
31
|
+
elsif !tags.nil?
|
31
32
|
{UNIT_OF_WORK_ID => tags}
|
32
33
|
end
|
33
34
|
end
|
@@ -44,17 +45,21 @@ module Lumberjack
|
|
44
45
|
to_s
|
45
46
|
end
|
46
47
|
|
47
|
-
#
|
48
|
+
# @deprecated - backward compatibility with 1.0 API. Will be removed in version 2.0.
|
48
49
|
def unit_of_work_id
|
49
|
-
|
50
|
+
Lumberjack::Utils.deprecated("Lumberjack::LogEntry#unit_of_work_id", "Lumberjack::LogEntry#unit_of_work_id will be removed in version 2.0") do
|
51
|
+
tags[UNIT_OF_WORK_ID] if tags
|
52
|
+
end
|
50
53
|
end
|
51
54
|
|
52
|
-
#
|
55
|
+
# @deprecated - backward compatibility with 1.0 API. Will be removed in version 2.0.
|
53
56
|
def unit_of_work_id=(value)
|
54
|
-
|
55
|
-
tags
|
56
|
-
|
57
|
-
|
57
|
+
Lumberjack::Utils.deprecated("Lumberjack::LogEntry#unit_of_work_id=", "Lumberjack::LogEntry#unit_of_work_id= will be removed in version 2.0") do
|
58
|
+
if tags
|
59
|
+
tags[UNIT_OF_WORK_ID] = value
|
60
|
+
else
|
61
|
+
@tags = {UNIT_OF_WORK_ID => value}
|
62
|
+
end
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
@@ -63,6 +68,11 @@ module Lumberjack
|
|
63
68
|
tags[name.to_s] if tags
|
64
69
|
end
|
65
70
|
|
71
|
+
# Return true if the log entry has no message and no tags.
|
72
|
+
def empty?
|
73
|
+
(message.nil? || message == "") && (tags.nil? || tags.empty?)
|
74
|
+
end
|
75
|
+
|
66
76
|
private
|
67
77
|
|
68
78
|
def tags_to_s
|
@@ -70,5 +80,37 @@ module Lumberjack
|
|
70
80
|
tags&.each { |name, value| tags_string << " #{name}:#{value.inspect}" }
|
71
81
|
tags_string
|
72
82
|
end
|
83
|
+
|
84
|
+
def compact_tags(tags)
|
85
|
+
delete_keys = nil
|
86
|
+
compacted_keys = nil
|
87
|
+
|
88
|
+
tags.each do |key, value|
|
89
|
+
if value.nil? || value == ""
|
90
|
+
delete_keys ||= []
|
91
|
+
delete_keys << key
|
92
|
+
elsif value.is_a?(Hash)
|
93
|
+
compacted_value = compact_tags(value)
|
94
|
+
if compacted_value.empty?
|
95
|
+
delete_keys ||= []
|
96
|
+
delete_keys << key
|
97
|
+
elsif !value.equal?(compacted_value)
|
98
|
+
compacted_keys ||= []
|
99
|
+
compacted_keys << [key, compacted_value]
|
100
|
+
end
|
101
|
+
elsif value.is_a?(Array) && value.empty?
|
102
|
+
delete_keys ||= []
|
103
|
+
delete_keys << key
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
return tags if delete_keys.nil? && compacted_keys.nil?
|
108
|
+
|
109
|
+
tags = tags.dup
|
110
|
+
delete_keys&.each { |key| tags.delete(key) }
|
111
|
+
compacted_keys&.each { |key, value| tags[key] = value }
|
112
|
+
|
113
|
+
tags
|
114
|
+
end
|
73
115
|
end
|
74
116
|
end
|