lumberjack 1.4.2 → 2.0.0
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 +524 -176
- data/CHANGELOG.md +89 -0
- data/README.md +604 -211
- data/UPGRADE_GUIDE.md +80 -0
- data/VERSION +1 -1
- data/lib/lumberjack/attribute_formatter.rb +451 -0
- data/lib/lumberjack/attributes_helper.rb +100 -0
- data/lib/lumberjack/context.rb +120 -23
- data/lib/lumberjack/context_logger.rb +620 -0
- data/lib/lumberjack/device/buffer.rb +209 -0
- data/lib/lumberjack/device/date_rolling_log_file.rb +10 -62
- data/lib/lumberjack/device/log_file.rb +76 -29
- data/lib/lumberjack/device/logger_wrapper.rb +137 -0
- data/lib/lumberjack/device/multi.rb +92 -30
- data/lib/lumberjack/device/null.rb +26 -8
- data/lib/lumberjack/device/size_rolling_log_file.rb +13 -54
- data/lib/lumberjack/device/test.rb +337 -0
- data/lib/lumberjack/device/writer.rb +184 -176
- data/lib/lumberjack/device.rb +134 -15
- data/lib/lumberjack/device_registry.rb +90 -0
- data/lib/lumberjack/entry_formatter.rb +357 -0
- data/lib/lumberjack/fiber_locals.rb +55 -0
- data/lib/lumberjack/forked_logger.rb +143 -0
- data/lib/lumberjack/formatter/date_time_formatter.rb +14 -3
- data/lib/lumberjack/formatter/exception_formatter.rb +12 -2
- data/lib/lumberjack/formatter/id_formatter.rb +13 -1
- data/lib/lumberjack/formatter/inspect_formatter.rb +14 -1
- data/lib/lumberjack/formatter/multiply_formatter.rb +10 -0
- data/lib/lumberjack/formatter/object_formatter.rb +13 -1
- data/lib/lumberjack/formatter/pretty_print_formatter.rb +15 -2
- data/lib/lumberjack/formatter/redact_formatter.rb +18 -3
- data/lib/lumberjack/formatter/round_formatter.rb +12 -0
- data/lib/lumberjack/formatter/string_formatter.rb +9 -1
- data/lib/lumberjack/formatter/strip_formatter.rb +13 -1
- data/lib/lumberjack/formatter/structured_formatter.rb +18 -2
- data/lib/lumberjack/formatter/tagged_message.rb +10 -32
- data/lib/lumberjack/formatter/tags_formatter.rb +32 -0
- data/lib/lumberjack/formatter/truncate_formatter.rb +8 -1
- data/lib/lumberjack/formatter.rb +271 -141
- data/lib/lumberjack/formatter_registry.rb +84 -0
- data/lib/lumberjack/io_compatibility.rb +133 -0
- data/lib/lumberjack/local_log_template.rb +209 -0
- data/lib/lumberjack/log_entry.rb +154 -79
- data/lib/lumberjack/log_entry_matcher/score.rb +276 -0
- data/lib/lumberjack/log_entry_matcher.rb +126 -0
- data/lib/lumberjack/logger.rb +328 -556
- data/lib/lumberjack/message_attributes.rb +38 -0
- data/lib/lumberjack/rack/context.rb +66 -15
- data/lib/lumberjack/rack.rb +0 -2
- data/lib/lumberjack/remap_attribute.rb +24 -0
- data/lib/lumberjack/severity.rb +52 -15
- data/lib/lumberjack/tag_context.rb +8 -71
- data/lib/lumberjack/tag_formatter.rb +22 -188
- data/lib/lumberjack/tags.rb +15 -21
- data/lib/lumberjack/template.rb +252 -62
- data/lib/lumberjack/template_registry.rb +60 -0
- data/lib/lumberjack/utils.rb +198 -48
- data/lib/lumberjack.rb +167 -59
- data/lumberjack.gemspec +4 -2
- metadata +41 -15
- data/lib/lumberjack/device/rolling_log_file.rb +0 -145
- data/lib/lumberjack/rack/request_id.rb +0 -31
- data/lib/lumberjack/rack/unit_of_work.rb +0 -21
- data/lib/lumberjack/tagged_logger_support.rb +0 -81
- data/lib/lumberjack/tagged_logging.rb +0 -29
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
# IOCompatibility provides methods that allow a logger to be used as an IO-like stream.
|
5
|
+
# This enables loggers to be used anywhere an IO object is expected, such as for
|
6
|
+
# redirecting standard output/error or integrating with libraries that expect stream objects.
|
7
|
+
#
|
8
|
+
# When used as a stream, all written values are logged with UNKNOWN severity and include
|
9
|
+
# timestamps and other standard log entry metadata. This is particularly useful for:
|
10
|
+
# - Capturing output from external libraries or subprocesses
|
11
|
+
# - Redirecting STDOUT/STDERR to logs
|
12
|
+
# - Providing a logging destination that conforms to IO interface expectations
|
13
|
+
#
|
14
|
+
# The module implements the essential IO methods like write, puts, print, printf, flush,
|
15
|
+
# and close to provide broad compatibility with Ruby's IO ecosystem.
|
16
|
+
#
|
17
|
+
# @example Basic stream usage
|
18
|
+
# logger = Lumberjack::Logger.new(STDOUT)
|
19
|
+
# logger.puts("Hello, world!") # Logs with UNKNOWN severity
|
20
|
+
# logger.write("Direct write") # Also logs with UNKNOWN severity
|
21
|
+
#
|
22
|
+
# @example Setting the log entry severity
|
23
|
+
# logger = Lumberjack::Logger.new(STDOUT)
|
24
|
+
# logger.default_severity = :info
|
25
|
+
# logger.puts("This is an info message") # Logs with INFO severity
|
26
|
+
#
|
27
|
+
# @example Using as STDOUT replacement
|
28
|
+
# logger = Lumberjack::Logger.new("/var/log/app.log")
|
29
|
+
# $stdout = logger # Redirect all puts/print calls to the logger
|
30
|
+
# puts "This goes to the log file"
|
31
|
+
module IOCompatibility
|
32
|
+
# Write a value to the log as a log entry. The value will be recorded with UNKNOWN severity,
|
33
|
+
# ensuring it always appears in the log regardless of the current log level.
|
34
|
+
#
|
35
|
+
# @param value [Object] The message to write. Will be converted to a string for logging.
|
36
|
+
# @return [Integer] Returns 1 if a log entry was written, or 0 if the value was nil or empty.
|
37
|
+
def write(value)
|
38
|
+
return 0 if value.nil? || value == ""
|
39
|
+
|
40
|
+
self << value
|
41
|
+
1
|
42
|
+
end
|
43
|
+
|
44
|
+
# Write multiple values to the log, each as a separate log entry with UNKNOWN severity.
|
45
|
+
# This method mimics the behavior of IO#puts by writing each argument on a separate line.
|
46
|
+
#
|
47
|
+
# @param args [Array<Object>] The messages to write. Each will be converted to a string.
|
48
|
+
# @return [nil]
|
49
|
+
def puts(*args)
|
50
|
+
args.each do |arg|
|
51
|
+
write(arg)
|
52
|
+
end
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# Concatentate strings into a single log entry. This mimics IO#print behavior
|
57
|
+
# by writing arguments without separators. If no arguments are given, writes the
|
58
|
+
# value of the global $_ variable.
|
59
|
+
#
|
60
|
+
# @param args [Array<Object>] The messages to write. If empty, uses $_ (last input record).
|
61
|
+
# @return [nil]
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# logger.print("Hello", " ", "World") # Single log entry: "Hello World"
|
65
|
+
def print(*args)
|
66
|
+
if args.empty?
|
67
|
+
write($_)
|
68
|
+
else
|
69
|
+
write(args.join(""))
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Write a formatted string to the log using sprintf-style formatting. The formatted
|
75
|
+
# result is logged as a single entry with UNKNOWN severity.
|
76
|
+
#
|
77
|
+
# @param format [String] The format string (printf-style format specifiers).
|
78
|
+
# @param args [Array<Object>] The values to substitute into the format string.
|
79
|
+
# @return [nil]
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# logger.printf("User %s logged in at %s", "alice", Time.now)
|
83
|
+
# # Logs: "User alice logged in at 2025-08-21 10:30:00 UTC"
|
84
|
+
def printf(format, *args)
|
85
|
+
write(format % args)
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Flush any buffered output. This method is provided for IO compatibility but
|
90
|
+
# is a no-op since log entries are typically written immediately to the underlying device.
|
91
|
+
# The actual flushing behavior depends on the logging device being used.
|
92
|
+
#
|
93
|
+
# @return [nil]
|
94
|
+
def flush
|
95
|
+
end
|
96
|
+
|
97
|
+
# Close the stream. This method is provided for IO compatibility but is a no-op.
|
98
|
+
# To actually close a logger, call close on the logger object itself, which will
|
99
|
+
# close the underlying logging device.
|
100
|
+
#
|
101
|
+
# @return [nil]
|
102
|
+
def close
|
103
|
+
end
|
104
|
+
|
105
|
+
# Check if the stream is closed. Always returns false since loggers using this
|
106
|
+
# module don't maintain a closed state through this interface.
|
107
|
+
#
|
108
|
+
# @return [Boolean] Always returns false.
|
109
|
+
def closed?
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check if the stream is connected to a terminal (TTY). Always returns false
|
114
|
+
# since loggers are not terminal devices, even when they write to STDOUT/STDERR.
|
115
|
+
# This method is required for complete IO compatibility.
|
116
|
+
#
|
117
|
+
# @return [Boolean] Always returns false.
|
118
|
+
# @api private
|
119
|
+
def tty?
|
120
|
+
false
|
121
|
+
end
|
122
|
+
|
123
|
+
# Set the encoding for the stream. This method is provided for IO compatibility
|
124
|
+
# but is a no-op since loggers handle encoding internally through their devices
|
125
|
+
# and formatters.
|
126
|
+
#
|
127
|
+
# @param _encoding [String, Encoding] The encoding to set (ignored).
|
128
|
+
# @return [nil]
|
129
|
+
# @api private
|
130
|
+
def set_encoding(_encoding)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
# This is a log template designed for local environments. It provides a simple,
|
5
|
+
# human-readable format that includes key information about log entries while
|
6
|
+
# omitting extraneous details. The template can be configured to include or
|
7
|
+
# exclude certain components such as the times, process ID, program name,
|
8
|
+
# and attributes.
|
9
|
+
#
|
10
|
+
# It is registered with the TemplateRegistry as :local.
|
11
|
+
#
|
12
|
+
# @see Template
|
13
|
+
class LocalLogTemplate
|
14
|
+
TemplateRegistry.add(:local, self)
|
15
|
+
|
16
|
+
# Create a new LocalLogTemplate instance.
|
17
|
+
#
|
18
|
+
# @param options [Hash] Options for configuring the template.
|
19
|
+
# @option options [Boolean, Array<String>, nil] :exclude_attributes If true, all attributes are excluded.
|
20
|
+
# If an array of strings is provided, those attributes (and their sub-attributes) are excluded.
|
21
|
+
# Defaults to nil (include all attributes).
|
22
|
+
# @option options [Boolean] :exclude_progname If true, the progname is excluded. Defaults to false.
|
23
|
+
# @option options [Boolean] :exclude_pid If true, the process ID is excluded. Defaults to true.
|
24
|
+
# @option options [Boolean] :exclude_time If true, the time is excluded. Defaults to true.
|
25
|
+
# @option options [Boolean] :colorize If true, colorize the output based on severity. Defaults to false.
|
26
|
+
# @option options [Symbol, Formatter, #call] :exception_formatter The formatter to use for exceptions in messages.
|
27
|
+
# Can be a symbol registered in the FormatterRegistry, a Formatter instance, or any object that responds to #call.
|
28
|
+
# Defaults to nil (use default exception formatting). If the logger does not have an exception formatter
|
29
|
+
# configured, then the device will use this to format exceptions.
|
30
|
+
# @option options [String, Symbol] :severity_format The optional format for severity labels (padded, char, emoji).
|
31
|
+
def initialize(options = {})
|
32
|
+
self.exclude_progname = options.fetch(:exclude_progname, false)
|
33
|
+
self.exclude_pid = options.fetch(:exclude_pid, true)
|
34
|
+
self.exclude_time = options.fetch(:exclude_time, true)
|
35
|
+
self.exclude_attributes = options.fetch(:exclude_attributes, nil)
|
36
|
+
self.colorize = options.fetch(:colorize, false)
|
37
|
+
self.severity_format = options.fetch(:severity_format, nil)
|
38
|
+
self.exception_formatter = options.fetch(:exception_formatter, :exception)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Format a log entry according to the template.
|
42
|
+
#
|
43
|
+
# @param entry [LogEntry] The log entry to format.
|
44
|
+
# @return [String] The formatted log entry.
|
45
|
+
def call(entry)
|
46
|
+
message = entry.message
|
47
|
+
if message.is_a?(Exception) && exception_formatter
|
48
|
+
message = exception_formatter.call(message)
|
49
|
+
end
|
50
|
+
|
51
|
+
formatted = +""
|
52
|
+
formatted << entry.time.strftime("%Y-%m-%d %H:%M:%S.%6N ") unless exclude_time?
|
53
|
+
formatted << "#{severity_label(entry)} #{message}"
|
54
|
+
formatted << "#{Lumberjack::LINE_SEPARATOR} progname: #{entry.progname}" if entry.progname.to_s != "" && !exclude_progname?
|
55
|
+
formatted << "#{Lumberjack::LINE_SEPARATOR} pid: #{entry.pid}" unless exclude_pid?
|
56
|
+
|
57
|
+
if entry.attributes && !entry.attributes.empty? && !exclude_attributes?
|
58
|
+
Lumberjack::Utils.flatten_attributes(entry.attributes).to_a.sort_by(&:first).each do |name, value|
|
59
|
+
next if @attribute_filter.any? do |filter_name|
|
60
|
+
if name.start_with?(filter_name)
|
61
|
+
next_char = name[filter_name.length]
|
62
|
+
next_char.nil? || next_char == "."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
formatted << "#{Lumberjack::LINE_SEPARATOR} #{name}: #{value}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
formatted = Template.colorize_entry(formatted, entry) if colorize?
|
71
|
+
formatted << Lumberjack::LINE_SEPARATOR
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return true if all attributes are excluded, false otherwise.
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
def exclude_attributes?
|
78
|
+
@exclude_attributes
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return the list of excluded attribute names.
|
82
|
+
#
|
83
|
+
# @return [Array<String>]
|
84
|
+
def excluded_attributes
|
85
|
+
@attribute_filter.dup
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set the attributes to exclude. If set to true, all attributes are excluded.
|
89
|
+
# If set to an array of strings, those attributes (and their sub-attributes)
|
90
|
+
# are excluded. If set to false or nil, no attributes are excluded.
|
91
|
+
#
|
92
|
+
# @param value [Boolean, Array<String>, nil]
|
93
|
+
# @return [void]
|
94
|
+
def exclude_attributes=(value)
|
95
|
+
@exclude_attributes = false
|
96
|
+
@attribute_filter = []
|
97
|
+
if value == true
|
98
|
+
@exclude_attributes = true
|
99
|
+
elsif value
|
100
|
+
@attribute_filter = Array(value).map(&:to_s)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Return true if the progname is excluded, false otherwise.
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
def exclude_progname?
|
108
|
+
@exclude_progname
|
109
|
+
end
|
110
|
+
|
111
|
+
# Set whether to exclude the progname.
|
112
|
+
#
|
113
|
+
# @param value [Boolean]
|
114
|
+
# @return [void]
|
115
|
+
def exclude_progname=(value)
|
116
|
+
@exclude_progname = !!value
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return true if the pid is excluded, false otherwise.
|
120
|
+
#
|
121
|
+
# @return [Boolean]
|
122
|
+
def exclude_pid?
|
123
|
+
@exclude_pid
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set whether to exclude the pid.
|
127
|
+
#
|
128
|
+
# @param value [Boolean]
|
129
|
+
# @return [void]
|
130
|
+
def exclude_pid=(value)
|
131
|
+
@exclude_pid = !!value
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return true if the time is excluded, false otherwise.
|
135
|
+
#
|
136
|
+
# @return [Boolean]
|
137
|
+
def exclude_time?
|
138
|
+
@exclude_time
|
139
|
+
end
|
140
|
+
|
141
|
+
# Set whether to exclude the time.
|
142
|
+
#
|
143
|
+
# @param value [Boolean]
|
144
|
+
# @return [void]
|
145
|
+
def exclude_time=(value)
|
146
|
+
@exclude_time = !!value
|
147
|
+
end
|
148
|
+
|
149
|
+
# Return true if colorization is enabled, false otherwise.
|
150
|
+
#
|
151
|
+
# @return [Boolean]
|
152
|
+
def colorize?
|
153
|
+
@colorize
|
154
|
+
end
|
155
|
+
|
156
|
+
# Set whether to enable colorization.
|
157
|
+
#
|
158
|
+
# @param value [Boolean]
|
159
|
+
# @return [void]
|
160
|
+
def colorize=(value)
|
161
|
+
@colorize = !!value
|
162
|
+
end
|
163
|
+
|
164
|
+
# Set the severity format.
|
165
|
+
#
|
166
|
+
# @param value [String, Symbol] The severity format (:padded, :char, :emoji, :level, nil).
|
167
|
+
# @return [void]
|
168
|
+
def severity_format=(value)
|
169
|
+
@severity_format = value.to_s
|
170
|
+
end
|
171
|
+
|
172
|
+
# Return the current severity format.
|
173
|
+
#
|
174
|
+
# @return [String]
|
175
|
+
attr_reader :severity_format
|
176
|
+
|
177
|
+
# Set the exception formatter. Can be a symbol registered in the FormatterRegistry,
|
178
|
+
# a Formatter instance, or any object that responds to #call.
|
179
|
+
#
|
180
|
+
# @param value [Symbol, Formatter, #call] The exception formatter to use.
|
181
|
+
# @return [void]
|
182
|
+
def exception_formatter=(value)
|
183
|
+
@exception_formatter = value.is_a?(Symbol) ? FormatterRegistry.formatter(value) : value
|
184
|
+
end
|
185
|
+
|
186
|
+
# Return the exception formatter.
|
187
|
+
#
|
188
|
+
# @return [#call, nil]
|
189
|
+
attr_reader :exception_formatter
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def severity_label(entry)
|
194
|
+
severity = entry.severity_data
|
195
|
+
case severity_format
|
196
|
+
when "padded"
|
197
|
+
severity.padded_label
|
198
|
+
when "char"
|
199
|
+
severity.char
|
200
|
+
when "emoji"
|
201
|
+
severity.emoji
|
202
|
+
when "level"
|
203
|
+
severity.level.to_s
|
204
|
+
else
|
205
|
+
severity.label
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
data/lib/lumberjack/log_entry.rb
CHANGED
@@ -1,139 +1,214 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Lumberjack
|
4
|
-
#
|
5
|
-
#
|
4
|
+
# A structured representation of a single log entry containing the message,
|
5
|
+
# metadata, and contextual information. LogEntry objects are immutable data
|
6
|
+
# structures that capture all relevant information about a logging event,
|
7
|
+
# including timing, severity, source identification, and custom attributes.
|
8
|
+
#
|
9
|
+
# This class serves as the fundamental data structure passed between loggers,
|
10
|
+
# formatters, and output devices throughout the Lumberjack logging pipeline.
|
11
|
+
# Each entry maintains consistent structure while supporting flexible attribute
|
12
|
+
# attachment for contextual logging scenarios.
|
6
13
|
class LogEntry
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
#
|
14
|
+
# @!attribute [rw] time
|
15
|
+
# @return [Time] The timestamp when the log entry was created
|
16
|
+
# @!attribute [rw] message
|
17
|
+
# @return [String] The primary log message content
|
18
|
+
# @!attribute [rw] severity
|
19
|
+
# @return [Integer] The numeric severity level of the log entry
|
20
|
+
# @!attribute [rw] progname
|
21
|
+
# @return [String] The name of the program or component that generated the entry
|
22
|
+
# @!attribute [rw] pid
|
23
|
+
# @return [Integer] The process ID of the logging process
|
24
|
+
# @!attribute [rw] attributes
|
25
|
+
# @return [Hash<String, Object>] Custom attributes associated with the log entry
|
26
|
+
attr_accessor :time, :message, :severity, :progname, :pid, :attributes
|
27
|
+
|
28
|
+
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N"
|
29
|
+
|
30
|
+
# Create a new log entry with the specified components. The entry captures
|
31
|
+
# all relevant information about a logging event in a structured format.
|
15
32
|
#
|
16
|
-
# @param time [Time] The
|
17
|
-
# @param severity [Integer, String] The severity
|
18
|
-
# @param message [String] The message
|
19
|
-
# @param progname [String] The name of the program
|
20
|
-
# @param pid [Integer] The process
|
21
|
-
# @param
|
22
|
-
def initialize(time, severity, message, progname, pid,
|
33
|
+
# @param time [Time] The timestamp when the log entry was created
|
34
|
+
# @param severity [Integer, String, Symbol] The severity level, accepts numeric levels or labels
|
35
|
+
# @param message [String] The primary log message content
|
36
|
+
# @param progname [String, nil] The name of the program or component generating the entry
|
37
|
+
# @param pid [Integer] The process ID of the logging process
|
38
|
+
# @param attributes [Hash<String, Object>, nil] Custom attributes to associate with the entry
|
39
|
+
def initialize(time, severity, message, progname, pid, attributes)
|
23
40
|
@time = time
|
24
41
|
@severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity))
|
25
42
|
@message = message
|
26
43
|
@progname = progname
|
27
44
|
@pid = pid
|
28
|
-
|
29
|
-
@tags = if tags.is_a?(Hash)
|
30
|
-
compact_tags(tags)
|
31
|
-
elsif !tags.nil?
|
32
|
-
{UNIT_OF_WORK_ID => tags}
|
33
|
-
end
|
45
|
+
@attributes = flatten_attributes(attributes) if attributes.is_a?(Hash)
|
34
46
|
end
|
35
47
|
|
48
|
+
# Get the human-readable severity label corresponding to the numeric severity level.
|
49
|
+
#
|
50
|
+
# @return [String] The severity label (DEBUG, INFO, WARN, ERROR, FATAL, or UNKNOWN)
|
36
51
|
def severity_label
|
37
|
-
|
52
|
+
severity_data.label
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the data corresponding to the severity. This include variations on the severity label.
|
56
|
+
def severity_data
|
57
|
+
Severity.data(severity)
|
38
58
|
end
|
39
59
|
|
60
|
+
# Generate a formatted string representation of the log entry suitable for
|
61
|
+
# human consumption. Includes timestamp, severity, program name, process ID,
|
62
|
+
# attributes, and the main message.
|
63
|
+
#
|
64
|
+
# @return [String] A formatted string representation of the complete log entry
|
40
65
|
def to_s
|
41
|
-
"[#{time.strftime(TIME_FORMAT)}
|
66
|
+
msg = +"[#{time.strftime(TIME_FORMAT)} #{severity_label} #{progname}(#{pid})] #{message}"
|
67
|
+
attributes&.each do |key, value|
|
68
|
+
msg << " [#{key}:#{value}]"
|
69
|
+
end
|
70
|
+
msg
|
42
71
|
end
|
43
72
|
|
73
|
+
# Return a string representation suitable for debugging and inspection.
|
74
|
+
#
|
75
|
+
# @return [String] The same as {#to_s}
|
44
76
|
def inspect
|
45
77
|
to_s
|
46
78
|
end
|
47
79
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
80
|
+
# Compare this log entry with another for equality. Two log entries are
|
81
|
+
# considered equal if all their components match exactly.
|
82
|
+
#
|
83
|
+
# @param other [Object] The object to compare against
|
84
|
+
# @return [Boolean] True if the entries are identical, false otherwise
|
85
|
+
def ==(other)
|
86
|
+
return true if equal?(other)
|
87
|
+
return false unless other.is_a?(LogEntry)
|
88
|
+
|
89
|
+
time == other.time &&
|
90
|
+
severity == other.severity &&
|
91
|
+
message == other.message &&
|
92
|
+
progname == other.progname &&
|
93
|
+
pid == other.pid &&
|
94
|
+
attributes == other.attributes
|
53
95
|
end
|
54
96
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
97
|
+
# Alias for tags to provide backward compatibility with version 1.x API. This method
|
98
|
+
# will eventually be removed.
|
99
|
+
#
|
100
|
+
# @return [Hash, nil] The attributes of the log entry.
|
101
|
+
# @deprecated Use {#attributes} instead.
|
102
|
+
def tags
|
103
|
+
Utils.deprecated("LogEntry#tags", "Lumberjack::LogEntry#tags is deprecated and will be removed in version 2.1; use attributes instead.") do
|
104
|
+
attributes
|
63
105
|
end
|
64
106
|
end
|
65
107
|
|
66
|
-
#
|
108
|
+
# Access an attribute value by name. Supports both simple and nested attribute
|
109
|
+
# access using dot notation for hierarchical data structures.
|
67
110
|
#
|
68
|
-
# @param name [String, Symbol] The
|
69
|
-
# @return [Object, nil] The
|
70
|
-
def
|
71
|
-
return nil if
|
111
|
+
# @param name [String, Symbol] The attribute name, supports dot notation for nested access
|
112
|
+
# @return [Object, nil] The attribute value or nil if the attribute does not exist
|
113
|
+
def [](name)
|
114
|
+
return nil if attributes.nil?
|
72
115
|
|
73
|
-
|
116
|
+
AttributesHelper.new(attributes)[name]
|
74
117
|
end
|
75
118
|
|
76
|
-
#
|
77
|
-
# will be
|
119
|
+
# Alias method for #[] to provide backward compatibility with version 1.x API. This
|
120
|
+
# method will eventually be removed.
|
78
121
|
#
|
79
|
-
# @return [Hash]
|
122
|
+
# @return [Hash]
|
123
|
+
# @deprecated Use {#[]} instead.
|
124
|
+
def tag(name)
|
125
|
+
Utils.deprecated("LogEntry#tag", "Lumberjack::LogEntry#tag is deprecated and will be removed in version 2.1; use [] instead.") do
|
126
|
+
self[name]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Expand flat attributes with dot notation into a nested hash structure.
|
131
|
+
# Attributes containing dots in their names are converted into hierarchical
|
132
|
+
# nested hashes for structured data representation.
|
80
133
|
#
|
81
|
-
# @
|
82
|
-
|
83
|
-
|
134
|
+
# @return [Hash] The attributes expanded into a nested structure
|
135
|
+
def nested_attributes
|
136
|
+
Utils.expand_attributes(attributes)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Alias for nested_attributes to provide API compatibility with version 1.x.
|
140
|
+
# This method will eventually be removed.
|
141
|
+
#
|
142
|
+
# @return [Hash]
|
143
|
+
# @deprecated Use {#nested_attributes} instead.
|
84
144
|
def nested_tags
|
85
|
-
Utils.
|
145
|
+
Utils.deprecated("LogEntry#nested_tags", "Lumberjack::LogEntry#nested_tags is deprecated and will be removed in version 2.1; use nested_attributes instead.") do
|
146
|
+
nested_attributes
|
147
|
+
end
|
86
148
|
end
|
87
149
|
|
88
|
-
#
|
150
|
+
# Determine if the log entry contains no meaningful content. An entry is
|
151
|
+
# considered empty if it has no message content and no attributes.
|
89
152
|
#
|
90
|
-
# @return [Boolean] True if the
|
153
|
+
# @return [Boolean] True if the entry is empty, false otherwise
|
91
154
|
def empty?
|
92
|
-
(message.nil? || message == "") && (
|
155
|
+
(message.nil? || message == "") && (attributes.nil? || attributes.empty?)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Convert the log entry into a hash suitable for JSON serialization. Attributes will be expanded
|
159
|
+
# into a nested structure (i.e. { "user.id" => 123 } becomes `{ "user" => { "id" => 123 } }).
|
160
|
+
# Severities will be converted to their string labels.
|
161
|
+
#
|
162
|
+
# @return [Hash] The JSON representation of the log entry
|
163
|
+
def as_json
|
164
|
+
{
|
165
|
+
"time" => time,
|
166
|
+
"severity" => severity_label,
|
167
|
+
"message" => message,
|
168
|
+
"progname" => progname,
|
169
|
+
"pid" => pid,
|
170
|
+
"attributes" => Utils.expand_attributes(attributes)
|
171
|
+
}
|
93
172
|
end
|
94
173
|
|
95
174
|
private
|
96
175
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
176
|
+
# Generate a string representation of all attributes for inclusion in the
|
177
|
+
# formatted output. Each attribute is formatted as key:value pairs.
|
178
|
+
#
|
179
|
+
# @return [String] A formatted string of all attributes
|
180
|
+
def attributes_to_s
|
181
|
+
attributes_string = +""
|
182
|
+
attributes&.each { |name, value| attributes_string << " #{name}:#{value.inspect}" }
|
183
|
+
attributes_string
|
101
184
|
end
|
102
185
|
|
103
|
-
|
104
|
-
|
186
|
+
# Flatten nested attributes and remove empty values.
|
187
|
+
#
|
188
|
+
# @param attributes [Hash] The attributes hash to compact
|
189
|
+
# @return [Hash] The flattened attributes with empty values removed
|
190
|
+
def flatten_attributes(attributes)
|
191
|
+
unless attributes.all? { |key, value| key.is_a?(String) && !value.is_a?(Hash) }
|
192
|
+
attributes = Utils.flatten_attributes(attributes)
|
193
|
+
end
|
105
194
|
|
106
195
|
delete_keys = nil
|
107
|
-
|
108
|
-
|
109
|
-
tags.each do |key, value|
|
196
|
+
attributes.each do |key, value|
|
110
197
|
if value.nil? || value == ""
|
111
198
|
delete_keys ||= []
|
112
199
|
delete_keys << key
|
113
|
-
elsif value.is_a?(Hash)
|
114
|
-
seen ||= Set.new
|
115
|
-
seen << tags.object_id
|
116
|
-
compacted_value = compact_tags(value, seen)
|
117
|
-
if compacted_value.empty?
|
118
|
-
delete_keys ||= []
|
119
|
-
delete_keys << key
|
120
|
-
elsif !value.equal?(compacted_value)
|
121
|
-
compacted_keys ||= []
|
122
|
-
compacted_keys << [key, compacted_value]
|
123
|
-
end
|
124
200
|
elsif value.is_a?(Array) && value.empty?
|
125
201
|
delete_keys ||= []
|
126
202
|
delete_keys << key
|
127
203
|
end
|
128
204
|
end
|
129
205
|
|
130
|
-
return
|
206
|
+
return attributes if delete_keys.nil?
|
131
207
|
|
132
|
-
|
133
|
-
delete_keys&.each { |key|
|
134
|
-
compacted_keys&.each { |key, value| tags[key] = value }
|
208
|
+
attributes = attributes.dup
|
209
|
+
delete_keys&.each { |key| attributes.delete(key) }
|
135
210
|
|
136
|
-
|
211
|
+
attributes
|
137
212
|
end
|
138
213
|
end
|
139
214
|
end
|