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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +524 -176
  3. data/CHANGELOG.md +89 -0
  4. data/README.md +604 -211
  5. data/UPGRADE_GUIDE.md +80 -0
  6. data/VERSION +1 -1
  7. data/lib/lumberjack/attribute_formatter.rb +451 -0
  8. data/lib/lumberjack/attributes_helper.rb +100 -0
  9. data/lib/lumberjack/context.rb +120 -23
  10. data/lib/lumberjack/context_logger.rb +620 -0
  11. data/lib/lumberjack/device/buffer.rb +209 -0
  12. data/lib/lumberjack/device/date_rolling_log_file.rb +10 -62
  13. data/lib/lumberjack/device/log_file.rb +76 -29
  14. data/lib/lumberjack/device/logger_wrapper.rb +137 -0
  15. data/lib/lumberjack/device/multi.rb +92 -30
  16. data/lib/lumberjack/device/null.rb +26 -8
  17. data/lib/lumberjack/device/size_rolling_log_file.rb +13 -54
  18. data/lib/lumberjack/device/test.rb +337 -0
  19. data/lib/lumberjack/device/writer.rb +184 -176
  20. data/lib/lumberjack/device.rb +134 -15
  21. data/lib/lumberjack/device_registry.rb +90 -0
  22. data/lib/lumberjack/entry_formatter.rb +357 -0
  23. data/lib/lumberjack/fiber_locals.rb +55 -0
  24. data/lib/lumberjack/forked_logger.rb +143 -0
  25. data/lib/lumberjack/formatter/date_time_formatter.rb +14 -3
  26. data/lib/lumberjack/formatter/exception_formatter.rb +12 -2
  27. data/lib/lumberjack/formatter/id_formatter.rb +13 -1
  28. data/lib/lumberjack/formatter/inspect_formatter.rb +14 -1
  29. data/lib/lumberjack/formatter/multiply_formatter.rb +10 -0
  30. data/lib/lumberjack/formatter/object_formatter.rb +13 -1
  31. data/lib/lumberjack/formatter/pretty_print_formatter.rb +15 -2
  32. data/lib/lumberjack/formatter/redact_formatter.rb +18 -3
  33. data/lib/lumberjack/formatter/round_formatter.rb +12 -0
  34. data/lib/lumberjack/formatter/string_formatter.rb +9 -1
  35. data/lib/lumberjack/formatter/strip_formatter.rb +13 -1
  36. data/lib/lumberjack/formatter/structured_formatter.rb +18 -2
  37. data/lib/lumberjack/formatter/tagged_message.rb +10 -32
  38. data/lib/lumberjack/formatter/tags_formatter.rb +32 -0
  39. data/lib/lumberjack/formatter/truncate_formatter.rb +8 -1
  40. data/lib/lumberjack/formatter.rb +271 -141
  41. data/lib/lumberjack/formatter_registry.rb +84 -0
  42. data/lib/lumberjack/io_compatibility.rb +133 -0
  43. data/lib/lumberjack/local_log_template.rb +209 -0
  44. data/lib/lumberjack/log_entry.rb +154 -79
  45. data/lib/lumberjack/log_entry_matcher/score.rb +276 -0
  46. data/lib/lumberjack/log_entry_matcher.rb +126 -0
  47. data/lib/lumberjack/logger.rb +328 -556
  48. data/lib/lumberjack/message_attributes.rb +38 -0
  49. data/lib/lumberjack/rack/context.rb +66 -15
  50. data/lib/lumberjack/rack.rb +0 -2
  51. data/lib/lumberjack/remap_attribute.rb +24 -0
  52. data/lib/lumberjack/severity.rb +52 -15
  53. data/lib/lumberjack/tag_context.rb +8 -71
  54. data/lib/lumberjack/tag_formatter.rb +22 -188
  55. data/lib/lumberjack/tags.rb +15 -21
  56. data/lib/lumberjack/template.rb +252 -62
  57. data/lib/lumberjack/template_registry.rb +60 -0
  58. data/lib/lumberjack/utils.rb +198 -48
  59. data/lib/lumberjack.rb +167 -59
  60. data/lumberjack.gemspec +4 -2
  61. metadata +41 -15
  62. data/lib/lumberjack/device/rolling_log_file.rb +0 -145
  63. data/lib/lumberjack/rack/request_id.rb +0 -31
  64. data/lib/lumberjack/rack/unit_of_work.rb +0 -21
  65. data/lib/lumberjack/tagged_logger_support.rb +0 -81
  66. 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