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,620 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "fiber_locals"
|
4
|
+
require_relative "io_compatibility"
|
5
|
+
require_relative "severity"
|
6
|
+
|
7
|
+
module Lumberjack
|
8
|
+
# ContextLogger provides a logging interface with support for contextual attributes,
|
9
|
+
# level management, and program name scoping. This module is included by Logger
|
10
|
+
# and ForkedLogger to provide a common API for structured logging.
|
11
|
+
#
|
12
|
+
# Key features include:
|
13
|
+
# - Context-aware attribute management with tag/untag methods
|
14
|
+
# - Scoped logging levels and program names
|
15
|
+
# - Compatibility with Ruby's standard Logger API
|
16
|
+
# - Support for forking isolated logger contexts
|
17
|
+
#
|
18
|
+
# @see Lumberjack::Logger
|
19
|
+
# @see Lumberjack::ForkedLogger
|
20
|
+
# @see Lumberjack::Context
|
21
|
+
module ContextLogger
|
22
|
+
# Constant used for setting trace log level.
|
23
|
+
TRACE = Severity::TRACE
|
24
|
+
|
25
|
+
LEADING_OR_TRAILING_WHITESPACE = /(?:\A\s)|(?:\s\z)/
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def included(base)
|
29
|
+
base.include(FiberLocals) unless base.include?(FiberLocals)
|
30
|
+
base.include(IOCompatibility) unless base.include?(IOCompatibility)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the level of severity of entries that are logged. Entries with a lower
|
35
|
+
# severity level will be ignored.
|
36
|
+
#
|
37
|
+
# @return [Integer] The severity level.
|
38
|
+
def level
|
39
|
+
current_context&.level || default_context&.level
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method :sev_threshold, :level
|
43
|
+
|
44
|
+
# Set the log level using either an integer level like Logger::INFO or a label like
|
45
|
+
# :info or "info"
|
46
|
+
#
|
47
|
+
# @param value [Integer, Symbol, String] The severity level.
|
48
|
+
# @return [void]
|
49
|
+
def level=(value)
|
50
|
+
value = Severity.coerce(value) unless value.nil?
|
51
|
+
|
52
|
+
ctx = current_context
|
53
|
+
ctx.level = value if ctx
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :sev_threshold=, :level=
|
57
|
+
|
58
|
+
# Adjust the log level during the block execution for the current Fiber only.
|
59
|
+
#
|
60
|
+
# @param severity [Integer, Symbol, String] The severity level.
|
61
|
+
# @return [Object] The result of the block.
|
62
|
+
def with_level(severity, &block)
|
63
|
+
context do |ctx|
|
64
|
+
ctx.level = severity
|
65
|
+
block.call(ctx)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set the logger progname for the current context. This is the name of the program that is logging.
|
70
|
+
#
|
71
|
+
# @param value [String, nil]
|
72
|
+
# @return [void]
|
73
|
+
def progname=(value)
|
74
|
+
value = value&.to_s&.freeze
|
75
|
+
|
76
|
+
ctx = current_context
|
77
|
+
ctx.progname = value if ctx
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get the current progname.
|
81
|
+
#
|
82
|
+
# @return [String, nil]
|
83
|
+
def progname
|
84
|
+
current_context&.progname || default_context&.progname
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set the logger progname for the duration of the block.
|
88
|
+
#
|
89
|
+
# @yield [Object] The block to execute with the program name set.
|
90
|
+
# @param value [String] The program name to use.
|
91
|
+
# @return [Object] The result of the block.
|
92
|
+
def with_progname(value, &block)
|
93
|
+
context do |ctx|
|
94
|
+
ctx.progname = value
|
95
|
+
block.call(ctx)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the default severity used when writing log messages directly to a stream.
|
100
|
+
#
|
101
|
+
# @return [Integer] The default severity level.
|
102
|
+
def default_severity
|
103
|
+
current_context&.default_severity || default_context&.default_severity || Logger::UNKNOWN
|
104
|
+
end
|
105
|
+
|
106
|
+
# Set the default severity used when writing log messages directly to a stream
|
107
|
+
# for the current context.
|
108
|
+
#
|
109
|
+
# @param value [Integer, Symbol, String] The default severity level.
|
110
|
+
# @return [void]
|
111
|
+
def default_severity=(value)
|
112
|
+
ctx = current_context
|
113
|
+
ctx.default_severity = value if ctx
|
114
|
+
end
|
115
|
+
|
116
|
+
# ::Logger compatible method to add a log entry.
|
117
|
+
#
|
118
|
+
# @param severity [Integer, Symbol, String] The severity of the message.
|
119
|
+
# @param message_or_progname_or_attributes [Object] The message to log, progname, or attributes.
|
120
|
+
# @param progname_or_attributes [String, Hash] The name of the program or attributes.
|
121
|
+
# @return [true]
|
122
|
+
def add(severity, message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
123
|
+
# This convoluted logic is to have API compatibility with ::Logger#add.
|
124
|
+
severity ||= Logger::UNKNOWN
|
125
|
+
if message_or_progname_or_attributes.nil? && !progname_or_attributes.is_a?(Hash)
|
126
|
+
message_or_progname_or_attributes = progname_or_attributes
|
127
|
+
progname_or_attributes = nil
|
128
|
+
end
|
129
|
+
call_add_entry(severity, message_or_progname_or_attributes, progname_or_attributes, &block)
|
130
|
+
end
|
131
|
+
|
132
|
+
alias_method :log, :add
|
133
|
+
|
134
|
+
# Log a +FATAL+ message. The message can be passed in either the +message+ argument or in a block.
|
135
|
+
#
|
136
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
137
|
+
# if the message is passed in a block.
|
138
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
139
|
+
# if the message is passed in a block.
|
140
|
+
# @return [true]
|
141
|
+
def fatal(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
142
|
+
call_add_entry(Logger::FATAL, message_or_progname_or_attributes, progname_or_attributes, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return +true+ if +FATAL+ messages are being logged.
|
146
|
+
#
|
147
|
+
# @return [Boolean]
|
148
|
+
def fatal?
|
149
|
+
level <= Logger::FATAL
|
150
|
+
end
|
151
|
+
|
152
|
+
# Set the log level to fatal.
|
153
|
+
#
|
154
|
+
# @return [void]
|
155
|
+
def fatal!
|
156
|
+
self.level = Logger::FATAL
|
157
|
+
end
|
158
|
+
|
159
|
+
# Log an +ERROR+ message. The message can be passed in either the +message+ argument or in a block.
|
160
|
+
#
|
161
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
162
|
+
# if the message is passed in a block.
|
163
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
164
|
+
# if the message is passed in a block.
|
165
|
+
# @return [true]
|
166
|
+
def error(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
167
|
+
call_add_entry(Logger::ERROR, message_or_progname_or_attributes, progname_or_attributes, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Return +true+ if +ERROR+ messages are being logged.
|
171
|
+
#
|
172
|
+
# @return [Boolean]
|
173
|
+
def error?
|
174
|
+
level <= Logger::ERROR
|
175
|
+
end
|
176
|
+
|
177
|
+
# Set the log level to error.
|
178
|
+
#
|
179
|
+
# @return [void]
|
180
|
+
def error!
|
181
|
+
self.level = Logger::ERROR
|
182
|
+
end
|
183
|
+
|
184
|
+
# Log a +WARN+ message. The message can be passed in either the +message+ argument or in a block.
|
185
|
+
#
|
186
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
187
|
+
# if the message is passed in a block.
|
188
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
189
|
+
# if the message is passed in a block.
|
190
|
+
# @return [true]
|
191
|
+
def warn(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
192
|
+
call_add_entry(Logger::WARN, message_or_progname_or_attributes, progname_or_attributes, &block)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Return +true+ if +WARN+ messages are being logged.
|
196
|
+
#
|
197
|
+
# @return [Boolean]
|
198
|
+
def warn?
|
199
|
+
level <= Logger::WARN
|
200
|
+
end
|
201
|
+
|
202
|
+
# Set the log level to warn.
|
203
|
+
#
|
204
|
+
# @return [void]
|
205
|
+
def warn!
|
206
|
+
self.level = Logger::WARN
|
207
|
+
end
|
208
|
+
|
209
|
+
# Log an +INFO+ message. The message can be passed in either the +message+ argument or in a block.
|
210
|
+
#
|
211
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
212
|
+
# if the message is passed in a block.
|
213
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
214
|
+
# if the message is passed in a block.
|
215
|
+
# @return [true]
|
216
|
+
def info(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
217
|
+
call_add_entry(Logger::INFO, message_or_progname_or_attributes, progname_or_attributes, &block)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Return +true+ if +INFO+ messages are being logged.
|
221
|
+
#
|
222
|
+
# @return [Boolean]
|
223
|
+
def info?
|
224
|
+
level <= Logger::INFO
|
225
|
+
end
|
226
|
+
|
227
|
+
# Set the log level to info.
|
228
|
+
#
|
229
|
+
# @return [void]
|
230
|
+
def info!
|
231
|
+
self.level = Logger::INFO
|
232
|
+
end
|
233
|
+
|
234
|
+
# Log a +DEBUG+ message. The message can be passed in either the +message+ argument or in a block.
|
235
|
+
#
|
236
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
237
|
+
# if the message is passed in a block.
|
238
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
239
|
+
# if the message is passed in a block.
|
240
|
+
# @return [true]
|
241
|
+
def debug(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
242
|
+
call_add_entry(Logger::DEBUG, message_or_progname_or_attributes, progname_or_attributes, &block)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Return +true+ if +DEBUG+ messages are being logged.
|
246
|
+
#
|
247
|
+
# @return [Boolean]
|
248
|
+
def debug?
|
249
|
+
level <= Logger::DEBUG
|
250
|
+
end
|
251
|
+
|
252
|
+
# Set the log level to debug.
|
253
|
+
#
|
254
|
+
# @return [void]
|
255
|
+
def debug!
|
256
|
+
self.level = Logger::DEBUG
|
257
|
+
end
|
258
|
+
|
259
|
+
# Log a +TRACE+ message. The message can be passed in either the +message+ argument or in a block.
|
260
|
+
# Trace logs are a level lower than debug and are generally used to log code execution paths for
|
261
|
+
# low level debugging.
|
262
|
+
#
|
263
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
264
|
+
# if the message is passed in a block.
|
265
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
266
|
+
# if the message is passed in a block.
|
267
|
+
# @return [true]
|
268
|
+
def trace(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
269
|
+
call_add_entry(TRACE, message_or_progname_or_attributes, progname_or_attributes, &block)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Return +true+ if +TRACE+ messages are being logged.
|
273
|
+
#
|
274
|
+
# @return [Boolean]
|
275
|
+
def trace?
|
276
|
+
level <= TRACE
|
277
|
+
end
|
278
|
+
|
279
|
+
# Set the log level to trace.
|
280
|
+
#
|
281
|
+
# @return [void]
|
282
|
+
def trace!
|
283
|
+
self.level = TRACE
|
284
|
+
end
|
285
|
+
|
286
|
+
# Log a message when the severity is not known. Unknown messages will always appear in the log.
|
287
|
+
# The message can be passed in either the +message+ argument or in a block.
|
288
|
+
#
|
289
|
+
# @param message_or_progname_or_attributes [Object] The message to log or progname
|
290
|
+
# if the message is passed in a block.
|
291
|
+
# @param progname_or_attributes [String, Hash] The name of the program that is logging the message or attributes
|
292
|
+
# if the message is passed in a block.
|
293
|
+
# @return [void]
|
294
|
+
def unknown(message_or_progname_or_attributes = nil, progname_or_attributes = nil, &block)
|
295
|
+
call_add_entry(Logger::UNKNOWN, message_or_progname_or_attributes, progname_or_attributes, &block)
|
296
|
+
end
|
297
|
+
|
298
|
+
# Add a message when the severity is not known.
|
299
|
+
#
|
300
|
+
# @param msg [Object] The message to log.
|
301
|
+
# @return [void]
|
302
|
+
def <<(msg)
|
303
|
+
add_entry(default_severity, msg)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Tag the logger with a set of attributes. If a block is given, the attributes will only be set
|
307
|
+
# for the duration of the block. Otherwise the attributes will be applied on the current
|
308
|
+
# logger context for the duration of the current context. If there is no current context,
|
309
|
+
# then a new logger object will be returned with those attributes set on it.
|
310
|
+
#
|
311
|
+
# @param attributes [Hash] The attributes to set.
|
312
|
+
# @return [Object, Lumberjack::ContextLogger] If a block is given then the result of the block is returned.
|
313
|
+
# Otherwise it returns the logger itself so you can chain methods.
|
314
|
+
#
|
315
|
+
# @example
|
316
|
+
# # Only applies the attributes inside the block
|
317
|
+
# logger.tag(foo: "bar") do
|
318
|
+
# logger.info("message")
|
319
|
+
# end
|
320
|
+
#
|
321
|
+
# @example
|
322
|
+
# # Only applies the attributes inside the context block
|
323
|
+
# logger.context do
|
324
|
+
# logger.tag(foo: "bar")
|
325
|
+
# logger.info("message")
|
326
|
+
# end
|
327
|
+
def tag(attributes, &block)
|
328
|
+
if block
|
329
|
+
context do |ctx|
|
330
|
+
ctx.assign_attributes(attributes)
|
331
|
+
block.call(ctx)
|
332
|
+
end
|
333
|
+
else
|
334
|
+
local_context&.assign_attributes(attributes)
|
335
|
+
self
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Tags the logger with a set of persistent attributes. These attributes will be included on every log
|
340
|
+
# entry and are not tied to a context block. If the logger does not have a default context, then
|
341
|
+
# these will be ignored.
|
342
|
+
#
|
343
|
+
# @param attributes [Hash] The attributes to set persistently on the logger.
|
344
|
+
# @return [nil]
|
345
|
+
# @example
|
346
|
+
# logger.tag!(version: "1.2.3", environment: "production")
|
347
|
+
# logger.info("Server started") # Will include version and environment attributes
|
348
|
+
def tag!(attributes)
|
349
|
+
default_context&.assign_attributes(attributes)
|
350
|
+
nil
|
351
|
+
end
|
352
|
+
|
353
|
+
# Tags the outermost context with a set of attributes. If there is no outermost context, then
|
354
|
+
# nothing will happen. This method can be used to bubble attributes up to the top level context.
|
355
|
+
# It can be used in situations where you want to ensure a set of attributes are set for the rest
|
356
|
+
# of the request or operation defined by the outmermost context.
|
357
|
+
#
|
358
|
+
# @param attributes [Hash] The attributes to set on the outermost context.
|
359
|
+
# @return [nil]
|
360
|
+
#
|
361
|
+
# @example
|
362
|
+
# logger.tag(request_id: "12345") do
|
363
|
+
# logger.tag(action: "login") do
|
364
|
+
# # Add the user_id attribute to the outermost context along with request_id so that
|
365
|
+
# # it doesn't fall out of scope after this tag block ends.
|
366
|
+
# logger.tag_all_contexts(user_id: "67890")
|
367
|
+
# end
|
368
|
+
# end
|
369
|
+
def tag_all_contexts(attributes)
|
370
|
+
parent_context = local_context
|
371
|
+
while parent_context
|
372
|
+
parent_context.assign_attributes(attributes)
|
373
|
+
parent_context = parent_context.parent
|
374
|
+
end
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
|
378
|
+
# Append a value to an attribute. This method can be used to add "tags" to a logger by appending
|
379
|
+
# values to the same attribute. The tag values will be appended to any value that is already
|
380
|
+
# in the attribute. If a block is passed, then a new context will be opened as well. If no
|
381
|
+
# block is passed, then the values will be appended to the attribute in the current context.
|
382
|
+
# If there is no current context, then nothing will happen.
|
383
|
+
#
|
384
|
+
# @param attribute_name [String, Symbol] The name of the attribute to append values to.
|
385
|
+
# @param tags [Array<String, Symbol, Hash>] The tags to add.
|
386
|
+
# @return [Object, Lumberjack::Logger] If a block is passed then returns the result of the block.
|
387
|
+
# Otherwise returns self so that calls can be chained.
|
388
|
+
def append_to(attribute_name, *tags, &block)
|
389
|
+
return self unless block || in_context?
|
390
|
+
|
391
|
+
current_tags = attribute_value(attribute_name) || []
|
392
|
+
current_tags = [current_tags] unless current_tags.is_a?(Array)
|
393
|
+
new_tags = current_tags + tags.flatten
|
394
|
+
|
395
|
+
tag(attribute_name => new_tags, &block)
|
396
|
+
end
|
397
|
+
|
398
|
+
# Set up a context block for the logger. All attributes added within the block will be cleared when
|
399
|
+
# the block exits.
|
400
|
+
#
|
401
|
+
# @param block [Proc] The block to execute with the context.
|
402
|
+
# @return [Object] The result of the block.
|
403
|
+
# @yield [Context]
|
404
|
+
def context(&block)
|
405
|
+
unless block_given?
|
406
|
+
raise ArgumentError, "A block must be provided to the context method"
|
407
|
+
end
|
408
|
+
|
409
|
+
new_context = Context.new(current_context)
|
410
|
+
new_context.parent = local_context
|
411
|
+
fiber_locals do |locals|
|
412
|
+
locals.context = new_context
|
413
|
+
block.call(new_context)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Ensure that the block of code is wrapped by a context. If there is not already
|
418
|
+
# a context in scope for this logger, one will be created.
|
419
|
+
#
|
420
|
+
# @return [Object] The result of the block.
|
421
|
+
def ensure_context(&block)
|
422
|
+
if in_context?
|
423
|
+
yield
|
424
|
+
else
|
425
|
+
context(&block)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Forks a new logger with a new context that will send output through this logger.
|
430
|
+
# The new logger will inherit the level, progname, and attributes of the current logger
|
431
|
+
# context. Any changes to those values, though, will be isolated to just the forked logger.
|
432
|
+
# Any calls to log messages will be forwarded to the parent logger for output to the
|
433
|
+
# logging device.
|
434
|
+
#
|
435
|
+
# @param level [Integer, String, Symbol, nil] The level to set on the new logger. If this
|
436
|
+
# is not specified, then the level on the parent logger will be used.
|
437
|
+
# @param progname [String, nil] The progname to set on the new logger. If this is not specified,
|
438
|
+
# then the progname on the parent logger will be used.
|
439
|
+
# @param attributes [Hash, nil] The attributes to set on the new logger. The forked logger will
|
440
|
+
# inherit all attributes from the current logging context.
|
441
|
+
# @return [ForkedLogger]
|
442
|
+
#
|
443
|
+
# @example Creating a forked logger
|
444
|
+
# child_logger = logger.fork(level: :debug, progname: "Child")
|
445
|
+
# child_logger.debug("This goes to the parent logger's device")
|
446
|
+
def fork(level: nil, progname: nil, attributes: nil)
|
447
|
+
logger = ForkedLogger.new(self)
|
448
|
+
logger.level = level if level
|
449
|
+
logger.progname = progname if progname
|
450
|
+
logger.tag!(attributes) if attributes
|
451
|
+
logger
|
452
|
+
end
|
453
|
+
|
454
|
+
# Remove attributes from the current context block.
|
455
|
+
#
|
456
|
+
# @param attribute_names [Array<String, Symbol>] The attributes to remove.
|
457
|
+
# @return [void]
|
458
|
+
def untag(*attribute_names)
|
459
|
+
attributes = local_context&.attributes
|
460
|
+
AttributesHelper.new(attributes).delete(*attribute_names) if attributes
|
461
|
+
nil
|
462
|
+
end
|
463
|
+
|
464
|
+
# Remove attributes from the default context for the logger.
|
465
|
+
#
|
466
|
+
# @param attribute_names [Array<String, Symbol>] The attributes to remove.
|
467
|
+
# @return [void]
|
468
|
+
def untag!(*attribute_names)
|
469
|
+
attributes = default_context&.attributes
|
470
|
+
AttributesHelper.new(attributes).delete(*attribute_names) if attributes
|
471
|
+
nil
|
472
|
+
end
|
473
|
+
|
474
|
+
# Return all attributes in scope on the logger including global attributes set on the Lumberjack
|
475
|
+
# context, attributes set on the logger, and attributes set on the current block for the logger.
|
476
|
+
#
|
477
|
+
# @return [Hash]
|
478
|
+
def attributes
|
479
|
+
merge_all_attributes || {}
|
480
|
+
end
|
481
|
+
|
482
|
+
# Get the value of an attribute by name from the current context.
|
483
|
+
#
|
484
|
+
# @param name [String, Symbol] The name of the attribute to get.
|
485
|
+
# @return [Object, nil] The value of the attribute or nil if the attribute does not exist.
|
486
|
+
def attribute_value(name)
|
487
|
+
name = name.join(".") if name.is_a?(Array)
|
488
|
+
AttributesHelper.new(attributes)[name]
|
489
|
+
end
|
490
|
+
|
491
|
+
# Remove all attributes on the current logger and logging context within a block.
|
492
|
+
# You can still set new block scoped attributes within the block and provide
|
493
|
+
# attributes on individual log methods.
|
494
|
+
#
|
495
|
+
# @return [void]
|
496
|
+
def clear_attributes(&block)
|
497
|
+
fiber_locals do |locals|
|
498
|
+
locals.cleared = true
|
499
|
+
context do |ctx|
|
500
|
+
ctx.clear_attributes
|
501
|
+
block.call
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# Return true if the thread is currently in a context block with a local context.
|
507
|
+
#
|
508
|
+
# @return [Boolean]
|
509
|
+
def in_context?
|
510
|
+
!!local_context
|
511
|
+
end
|
512
|
+
|
513
|
+
# Add an entry to the log. This method must be implemented by the class that includes this module.
|
514
|
+
#
|
515
|
+
# @param severity [Integer, Symbol, String] The severity of the message.
|
516
|
+
# @param message [Object] The message to log.
|
517
|
+
# @param progname [String] The name of the program that is logging the message.
|
518
|
+
# @param attributes [Hash] The attributes to add to the log entry.
|
519
|
+
# @return [void]
|
520
|
+
# @api private
|
521
|
+
def add_entry(severity, message, progname = nil, attributes = nil)
|
522
|
+
raise NotImplementedError
|
523
|
+
end
|
524
|
+
|
525
|
+
private
|
526
|
+
|
527
|
+
def current_context
|
528
|
+
local_context || default_context
|
529
|
+
end
|
530
|
+
|
531
|
+
def local_context
|
532
|
+
fiber_local&.context
|
533
|
+
end
|
534
|
+
|
535
|
+
def default_context
|
536
|
+
nil
|
537
|
+
end
|
538
|
+
|
539
|
+
# Write a log entry to the logging device.
|
540
|
+
#
|
541
|
+
# @param entry [Lumberjack::LogEntry] The log entry to write.
|
542
|
+
# @return [void]
|
543
|
+
# @api private
|
544
|
+
def write_to_device(entry)
|
545
|
+
raise NotImplementedError
|
546
|
+
end
|
547
|
+
|
548
|
+
# Dereference arguments to log calls so we can have methods with compatibility with ::Logger
|
549
|
+
def call_add_entry(severity, message_or_progname_or_attributes, progname_or_attributes, &block) # :nodoc:
|
550
|
+
severity = Severity.coerce(severity) unless severity.is_a?(Integer)
|
551
|
+
return true unless level.nil? || severity >= level
|
552
|
+
|
553
|
+
message = nil
|
554
|
+
progname = nil
|
555
|
+
attributes = nil
|
556
|
+
if block
|
557
|
+
message = block
|
558
|
+
if message_or_progname_or_attributes.is_a?(Hash)
|
559
|
+
attributes = message_or_progname_or_attributes
|
560
|
+
progname = progname_or_attributes
|
561
|
+
else
|
562
|
+
progname = message_or_progname_or_attributes
|
563
|
+
attributes = progname_or_attributes if progname_or_attributes.is_a?(Hash)
|
564
|
+
end
|
565
|
+
else
|
566
|
+
message = message_or_progname_or_attributes
|
567
|
+
if progname_or_attributes.is_a?(Hash)
|
568
|
+
attributes = progname_or_attributes
|
569
|
+
else
|
570
|
+
progname = progname_or_attributes
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
message = message.call if message.is_a?(Proc)
|
575
|
+
message = message.strip if message.is_a?(String) && message.match?(LEADING_OR_TRAILING_WHITESPACE)
|
576
|
+
return if (message.nil? || message == "") && (attributes.nil? || attributes.empty?)
|
577
|
+
|
578
|
+
add_entry(severity, message, progname, attributes)
|
579
|
+
|
580
|
+
true
|
581
|
+
end
|
582
|
+
|
583
|
+
# Merge a attributes hash into an existing attributes hash.
|
584
|
+
def merge_attributes(current_attributes, attributes)
|
585
|
+
if current_attributes.nil? || current_attributes.empty?
|
586
|
+
attributes
|
587
|
+
elsif attributes.nil?
|
588
|
+
current_attributes
|
589
|
+
else
|
590
|
+
current_attributes.merge(attributes)
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
def merge_all_attributes
|
595
|
+
attributes = nil
|
596
|
+
|
597
|
+
unless fiber_local&.cleared
|
598
|
+
global_context_attributes = Lumberjack.context_attributes
|
599
|
+
if global_context_attributes && !global_context_attributes.empty?
|
600
|
+
attributes ||= {}
|
601
|
+
attributes.merge!(global_context_attributes)
|
602
|
+
end
|
603
|
+
|
604
|
+
default_attributes = default_context&.attributes
|
605
|
+
if default_attributes && !default_attributes.empty?
|
606
|
+
attributes ||= {}
|
607
|
+
attributes.merge!(default_attributes)
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
context_attributes = current_context&.attributes
|
612
|
+
if context_attributes && !context_attributes.empty?
|
613
|
+
attributes ||= {}
|
614
|
+
attributes.merge!(context_attributes)
|
615
|
+
end
|
616
|
+
|
617
|
+
attributes
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|