lumberjack 1.2.7 → 1.4.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +244 -0
  3. data/CHANGELOG.md +251 -56
  4. data/README.md +197 -62
  5. data/VERSION +1 -1
  6. data/lib/lumberjack/context.rb +25 -5
  7. data/lib/lumberjack/device/date_rolling_log_file.rb +17 -8
  8. data/lib/lumberjack/device/log_file.rb +14 -7
  9. data/lib/lumberjack/device/multi.rb +8 -7
  10. data/lib/lumberjack/device/null.rb +2 -2
  11. data/lib/lumberjack/device/rolling_log_file.rb +46 -22
  12. data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
  13. data/lib/lumberjack/device/writer.rb +45 -21
  14. data/lib/lumberjack/device.rb +28 -13
  15. data/lib/lumberjack/formatter/date_time_formatter.rb +5 -5
  16. data/lib/lumberjack/formatter/exception_formatter.rb +4 -4
  17. data/lib/lumberjack/formatter/id_formatter.rb +4 -3
  18. data/lib/lumberjack/formatter/inspect_formatter.rb +1 -1
  19. data/lib/lumberjack/formatter/multiply_formatter.rb +25 -0
  20. data/lib/lumberjack/formatter/object_formatter.rb +1 -1
  21. data/lib/lumberjack/formatter/pretty_print_formatter.rb +7 -5
  22. data/lib/lumberjack/formatter/redact_formatter.rb +23 -0
  23. data/lib/lumberjack/formatter/round_formatter.rb +21 -0
  24. data/lib/lumberjack/formatter/string_formatter.rb +1 -1
  25. data/lib/lumberjack/formatter/strip_formatter.rb +1 -1
  26. data/lib/lumberjack/formatter/structured_formatter.rb +3 -1
  27. data/lib/lumberjack/formatter/tagged_message.rb +39 -0
  28. data/lib/lumberjack/formatter/truncate_formatter.rb +27 -0
  29. data/lib/lumberjack/formatter.rb +96 -28
  30. data/lib/lumberjack/log_entry.rb +90 -19
  31. data/lib/lumberjack/logger.rb +318 -86
  32. data/lib/lumberjack/rack/context.rb +21 -2
  33. data/lib/lumberjack/rack/request_id.rb +8 -4
  34. data/lib/lumberjack/rack/unit_of_work.rb +7 -3
  35. data/lib/lumberjack/rack.rb +4 -4
  36. data/lib/lumberjack/severity.rb +22 -3
  37. data/lib/lumberjack/tag_context.rb +78 -0
  38. data/lib/lumberjack/tag_formatter.rb +124 -25
  39. data/lib/lumberjack/tagged_logger_support.rb +26 -12
  40. data/lib/lumberjack/tagged_logging.rb +1 -1
  41. data/lib/lumberjack/tags.rb +8 -8
  42. data/lib/lumberjack/template.rb +17 -5
  43. data/lib/lumberjack/utils.rb +182 -0
  44. data/lib/lumberjack.rb +64 -35
  45. data/lumberjack.gemspec +18 -15
  46. metadata +23 -54
@@ -1,4 +1,4 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  # Logger is a thread safe logging object. It has a compatible API with the Ruby
@@ -21,7 +21,7 @@ module Lumberjack
21
21
  # monitoring thread, but its use is highly recommended.
22
22
  #
23
23
  # Each log entry records the log message and severity along with the time it was logged, the
24
- # program name, process id, and unit of work id. The message will be converted to a string, but
24
+ # program name, process id, and an optional hash of tags. The message will be converted to a string, but
25
25
  # otherwise, it is up to the device how these values are recorded. Messages are converted to strings
26
26
  # using a Formatter associated with the logger.
27
27
  class Logger
@@ -36,8 +36,8 @@ module Lumberjack
36
36
  # Set the name of the program to attach to log entries.
37
37
  attr_writer :progname
38
38
 
39
- # The device being written to
40
- attr_accessor :device
39
+ # The Formatter used only for log entry messages.
40
+ attr_accessor :message_formatter
41
41
 
42
42
  # The TagFormatter used for formatting tags for output
43
43
  attr_accessor :tag_formatter
@@ -51,28 +51,30 @@ module Lumberjack
51
51
  # If it is :null, it will be a Null device that won't record any output.
52
52
  # Otherwise, it will be assumed to be file path and wrapped in a Device::LogFile class.
53
53
  #
54
- # This method can take the following options:
55
- #
56
- # * :level - The logging level below which messages will be ignored.
57
- # * :formatter - The formatter to use for outputting messages to the log.
58
- # * :datetime_format - The format to use for log timestamps.
59
- # * :tag_formatter - The TagFormatter to use for formatting tags.
60
- # * :progname - The name of the program that will be recorded with each log entry.
61
- # * :flush_seconds - The maximum number of seconds between flush calls.
62
- # * :roll - If the log device is a file path, it will be a Device::DateRollingLogFile if this is set.
63
- # * :max_size - If the log device is a file path, it will be a Device::SizeRollingLogFile if this is set.
64
- #
65
54
  # All other options are passed to the device constuctor.
66
- def initialize(device = STDOUT, options = {})
55
+ #
56
+ # @param [Lumberjack::Device, Object, Symbol, String] device The device to log to.
57
+ # @param [Hash] options The options for the logger.
58
+ # @option options [Integer, Symbol, String] :level The logging level below which messages will be ignored.
59
+ # @option options [Lumberjack::Formatter] :formatter The formatter to use for outputting messages to the log.
60
+ # @option options [String] :datetime_format The format to use for log timestamps.
61
+ # @option options [Lumberjack::Formatter] :message_formatter The MessageFormatter to use for formatting log messages.
62
+ # @option options [Lumberjack::TagFormatter] :tag_formatter The TagFormatter to use for formatting tags.
63
+ # @option options [String] :progname The name of the program that will be recorded with each log entry.
64
+ # @option options [Numeric] :flush_seconds The maximum number of seconds between flush calls.
65
+ # @option options [Boolean] :roll If the log device is a file path, it will be a Device::DateRollingLogFile if this is set.
66
+ # @option options [Integer] :max_size If the log device is a file path, it will be a Device::SizeRollingLogFile if this is set.
67
+ def initialize(device = $stdout, options = {})
67
68
  options = options.dup
68
69
  self.level = options.delete(:level) || INFO
69
70
  self.progname = options.delete(:progname)
70
71
  max_flush_seconds = options.delete(:flush_seconds).to_f
71
72
 
72
- @device = open_device(device, options) if device
73
+ @logdev = open_device(device, options) if device
73
74
  self.formatter = (options[:formatter] || Formatter.new)
74
- @tag_formatter = (options[:tag_formatter] || TagFormatter.new)
75
- time_format = (options[:datetime_format] || options[:time_format])
75
+ @message_formatter = options[:message_formatter] || Formatter.empty
76
+ @tag_formatter = options[:tag_formatter] || TagFormatter.new
77
+ time_format = options[:datetime_format] || options[:time_format]
76
78
  self.datetime_format = time_format if time_format
77
79
  @last_flushed_at = Time.now
78
80
  @silencer = true
@@ -82,12 +84,32 @@ module Lumberjack
82
84
  create_flusher_thread(max_flush_seconds) if max_flush_seconds > 0
83
85
  end
84
86
 
87
+ # Get the logging device that is used to write log entries.
88
+ #
89
+ # @return [Lumberjack::Device] The logging device.
90
+ def device
91
+ @logdev
92
+ end
93
+
94
+ # Set the logging device to a new device.
95
+ #
96
+ # @param [Lumberjack::Device] device The new logging device.
97
+ # @return [void]
98
+ def device=(device)
99
+ @logdev = device.nil? ? nil : open_device(device, {})
100
+ end
101
+
85
102
  # Get the timestamp format on the device if it has one.
103
+ #
104
+ # @return [String, nil] The timestamp format or nil if the device doesn't support it.
86
105
  def datetime_format
87
106
  device.datetime_format if device.respond_to?(:datetime_format)
88
107
  end
89
108
 
90
109
  # Set the timestamp format on the device if it is supported.
110
+ #
111
+ # @param [String] format The timestamp format.
112
+ # @return [void]
91
113
  def datetime_format=(format)
92
114
  if device.respond_to?(:datetime_format=)
93
115
  device.datetime_format = format
@@ -96,6 +118,8 @@ module Lumberjack
96
118
 
97
119
  # Get the level of severity of entries that are logged. Entries with a lower
98
120
  # severity level will be ignored.
121
+ #
122
+ # @return [Integer] The severity level.
99
123
  def level
100
124
  thread_local_value(:lumberjack_logger_level) || @level
101
125
  end
@@ -104,22 +128,34 @@ module Lumberjack
104
128
 
105
129
  # Set the log level using either an integer level like Logger::INFO or a label like
106
130
  # :info or "info"
131
+ #
132
+ # @param [Integer, Symbol, String] value The severity level.
133
+ # @return [void]
107
134
  def level=(value)
108
- if value.is_a?(Integer)
109
- @level = value
110
- else
111
- @level = Severity::label_to_level(value)
112
- end
135
+ @level = Severity.coerce(value)
113
136
  end
114
137
 
115
138
  alias_method :sev_threshold=, :level=
116
139
 
140
+ # Adjust the log level during the block execution for the current Fiber only.
141
+ #
142
+ # @param [Integer, Symbol, String] severity The severity level.
143
+ # @return [Object] The result of the block.
144
+ def with_level(severity, &block)
145
+ push_thread_local_value(:lumberjack_logger_level, Severity.coerce(severity), &block)
146
+ end
147
+
117
148
  # Set the Lumberjack::Formatter used to format objects for logging as messages.
149
+ #
150
+ # @param [Lumberjack::Formatter, Object] value The formatter to use.
151
+ # @return [void]
118
152
  def formatter=(value)
119
153
  @_formatter = (value.is_a?(TaggedLoggerSupport::Formatter) ? value.__formatter : value)
120
154
  end
121
155
 
122
156
  # Get the Lumberjack::Formatter used to format objects for logging as messages.
157
+ #
158
+ # @return [Lumberjack::Formatter] The formatter.
123
159
  def formatter
124
160
  if respond_to?(:tagged)
125
161
  # Wrap in an object that supports ActiveSupport::TaggedLogger API
@@ -136,8 +172,10 @@ module Lumberjack
136
172
  # The tags added with this method are just strings so they are stored in the logger tags
137
173
  # in an array under the "tagged" tag. So calling `logger.tagged("foo", "bar")` will result
138
174
  # in tags `{"tagged" => ["foo", "bar"]}`.
175
+ #
176
+ # @return [Lumberjack::Logger] self.
139
177
  def tagged_logger!
140
- self.extend(TaggedLoggerSupport)
178
+ extend(TaggedLoggerSupport)
141
179
  self
142
180
  end
143
181
 
@@ -149,40 +187,52 @@ module Lumberjack
149
187
  # The severity can be passed in either as one of the Severity constants,
150
188
  # or as a Severity label.
151
189
  #
152
- # === Example
190
+ # @param [Integer, Symbol, String] severity The severity of the message.
191
+ # @param [Object] message The message to log.
192
+ # @param [String] progname The name of the program that is logging the message.
193
+ # @param [Hash] tags The tags to add to the log entry.
194
+ # @return [void]
195
+ #
196
+ # @example
153
197
  #
154
198
  # logger.add_entry(Logger::ERROR, exception)
155
199
  # logger.add_entry(Logger::INFO, "Request completed")
156
200
  # logger.add_entry(:warn, "Request took a long time")
157
201
  # logger.add_entry(Logger::DEBUG){"Start processing with options #{options.inspect}"}
158
202
  def add_entry(severity, message, progname = nil, tags = nil)
159
- begin
160
- severity = Severity.label_to_level(severity) unless severity.is_a?(Integer)
161
- return true unless device && severity && severity >= level
203
+ severity = Severity.label_to_level(severity) unless severity.is_a?(Integer)
204
+ return true unless device && severity && severity >= level
205
+ return true if Thread.current[:lumberjack_logging]
162
206
 
163
- return true if Thread.current[:lumberjack_logging]
164
- Thread.current[:lumberjack_logging] = true
207
+ begin
208
+ Thread.current[:lumberjack_logging] = true # Prevent circular calls to add_entry
165
209
 
166
210
  time = Time.now
211
+
167
212
  message = message.call if message.is_a?(Proc)
168
- message = formatter.format(message)
213
+ msg_class_formatter = message_formatter&.formatter_for(message.class)
214
+ if msg_class_formatter
215
+ message = msg_class_formatter.call(message)
216
+ elsif formatter
217
+ message = formatter.format(message)
218
+ end
219
+ message_tags = nil
220
+ if message.is_a?(Formatter::TaggedMessage)
221
+ message_tags = message.tags
222
+ message = message.message
223
+ end
224
+
169
225
  progname ||= self.progname
226
+ message_tags = Utils.flatten_tags(message_tags) if message_tags
170
227
 
171
228
  current_tags = self.tags
172
229
  tags = nil unless tags.is_a?(Hash)
173
- if current_tags.empty?
174
- tags = Tags.stringify_keys(tags) unless tags.nil?
175
- else
176
- if tags.nil?
177
- tags = current_tags.dup
178
- else
179
- tags = current_tags.merge(Tags.stringify_keys(tags))
180
- end
181
- end
230
+ tags = merge_tags(current_tags, tags)
231
+ tags = merge_tags(tags, message_tags) if message_tags
182
232
  tags = Tags.expand_runtime_values(tags)
183
233
  tags = tag_formatter.format(tags) if tag_formatter
184
234
 
185
- entry = LogEntry.new(time, severity, message, progname, $$, tags)
235
+ entry = LogEntry.new(time, severity, message, progname, Process.pid, tags)
186
236
  write_to_device(entry)
187
237
  ensure
188
238
  Thread.current[:lumberjack_logging] = nil
@@ -191,6 +241,11 @@ module Lumberjack
191
241
  end
192
242
 
193
243
  # ::Logger compatible method to add a log entry.
244
+ #
245
+ # @param [Integer, Symbol, String] severity The severity of the message.
246
+ # @param [Object] message The message to log.
247
+ # @param [String] progname The name of the program that is logging the message.
248
+ # @return [void]
194
249
  def add(severity, message = nil, progname = nil, &block)
195
250
  if message.nil?
196
251
  if block
@@ -206,6 +261,8 @@ module Lumberjack
206
261
  alias_method :log, :add
207
262
 
208
263
  # Flush the logging device. Messages are not guaranteed to be written until this method is called.
264
+ #
265
+ # @return [void]
209
266
  def flush
210
267
  device.flush
211
268
  @last_flushed_at = Time.now
@@ -213,102 +270,170 @@ module Lumberjack
213
270
  end
214
271
 
215
272
  # Close the logging device.
273
+ #
274
+ # @return [void]
216
275
  def close
217
276
  flush
218
277
  device.close if device.respond_to?(:close)
219
278
  @closed = true
220
279
  end
221
280
 
281
+ # Returns +true+ if the logging device is closed.
282
+ #
283
+ # @return [Boolean] +true+ if the logging device is closed.
222
284
  def closed?
223
285
  @closed
224
286
  end
225
287
 
288
+ # Reopen the logging device.
289
+ #
290
+ # @param [Object] logdev passed through to the logging device.
226
291
  def reopen(logdev = nil)
227
292
  @closed = false
228
293
  device.reopen(logdev) if device.respond_to?(:reopen)
229
294
  end
230
295
 
231
296
  # Log a +FATAL+ message. The message can be passed in either the +message+ argument or in a block.
297
+ #
298
+ # @param [Object] message_or_progname_or_tags The message to log or progname
299
+ # if the message is passed in a block.
300
+ # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags
301
+ # if the message is passed in a block.
302
+ # @return [void]
232
303
  def fatal(message_or_progname_or_tags = nil, progname_or_tags = nil, &block)
233
304
  call_add_entry(FATAL, message_or_progname_or_tags, progname_or_tags, &block)
234
305
  end
235
306
 
236
307
  # Return +true+ if +FATAL+ messages are being logged.
308
+ #
309
+ # @return [Boolean]
237
310
  def fatal?
238
311
  level <= FATAL
239
312
  end
240
313
 
241
314
  # Set the log level to fatal.
315
+ #
316
+ # @return [void]
242
317
  def fatal!
243
318
  self.level = FATAL
244
319
  end
245
320
 
246
321
  # Log an +ERROR+ message. The message can be passed in either the +message+ argument or in a block.
322
+ #
323
+ # @param [Object] message_or_progname_or_tags The message to log or progname
324
+ # if the message is passed in a block.
325
+ # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags
326
+ # if the message is passed in a block.
327
+ # @return [void]
247
328
  def error(message_or_progname_or_tags = nil, progname_or_tags = nil, &block)
248
329
  call_add_entry(ERROR, message_or_progname_or_tags, progname_or_tags, &block)
249
330
  end
250
331
 
251
332
  # Return +true+ if +ERROR+ messages are being logged.
333
+ #
334
+ # @return [Boolean]
252
335
  def error?
253
336
  level <= ERROR
254
337
  end
255
338
 
256
339
  # Set the log level to error.
340
+ #
341
+ # @return [void]
257
342
  def error!
258
343
  self.level = ERROR
259
344
  end
260
345
 
261
346
  # Log a +WARN+ message. The message can be passed in either the +message+ argument or in a block.
347
+ #
348
+ # @param [Object] message_or_progname_or_tags The message to log or progname
349
+ # if the message is passed in a block.
350
+ # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags
351
+ # if the message is passed in a block.
352
+ # @return [void]
262
353
  def warn(message_or_progname_or_tags = nil, progname_or_tags = nil, &block)
263
354
  call_add_entry(WARN, message_or_progname_or_tags, progname_or_tags, &block)
264
355
  end
265
356
 
266
357
  # Return +true+ if +WARN+ messages are being logged.
358
+ #
359
+ # @return [Boolean]
267
360
  def warn?
268
361
  level <= WARN
269
362
  end
270
363
 
271
364
  # Set the log level to warn.
365
+ #
366
+ # @return [void]
272
367
  def warn!
273
368
  self.level = WARN
274
369
  end
275
370
 
276
371
  # Log an +INFO+ message. The message can be passed in either the +message+ argument or in a block.
372
+ #
373
+ # @param [Object] message_or_progname_or_tags The message to log or progname
374
+ # if the message is passed in a block.
375
+ # @param [String] progname_or_tags The name of the program that is logging the message or tags
376
+ # if the message is passed in a block.
377
+ # @return [void]
277
378
  def info(message_or_progname_or_tags = nil, progname_or_tags = nil, &block)
278
379
  call_add_entry(INFO, message_or_progname_or_tags, progname_or_tags, &block)
279
380
  end
280
381
 
281
382
  # Return +true+ if +INFO+ messages are being logged.
383
+ #
384
+ # @return [Boolean]
282
385
  def info?
283
386
  level <= INFO
284
387
  end
285
388
 
286
389
  # Set the log level to info.
390
+ #
391
+ # @return [void]
287
392
  def info!
288
393
  self.level = INFO
289
394
  end
290
395
 
291
396
  # Log a +DEBUG+ message. The message can be passed in either the +message+ argument or in a block.
397
+ #
398
+ # @param [Object] message_or_progname_or_tags The message to log or progname
399
+ # if the message is passed in a block.
400
+ # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags
401
+ # if the message is passed in a block.
402
+ # @return [void]
292
403
  def debug(message_or_progname_or_tags = nil, progname_or_tags = nil, &block)
293
404
  call_add_entry(DEBUG, message_or_progname_or_tags, progname_or_tags, &block)
294
405
  end
295
406
 
296
407
  # Return +true+ if +DEBUG+ messages are being logged.
408
+ #
409
+ # @return [Boolean]
297
410
  def debug?
298
411
  level <= DEBUG
299
412
  end
300
413
 
301
414
  # Set the log level to debug.
415
+ #
416
+ # @return [void]
302
417
  def debug!
303
418
  self.level = DEBUG
304
419
  end
305
420
 
306
421
  # Log a message when the severity is not known. Unknown messages will always appear in the log.
307
422
  # The message can be passed in either the +message+ argument or in a block.
423
+ #
424
+ # @param [Object] message_or_progname_or_tags The message to log or progname
425
+ # if the message is passed in a block.
426
+ # @param [String, Hash] progname_or_tags The name of the program that is logging the message or tags
427
+ # if the message is passed in a block.
428
+ # @return [void]
308
429
  def unknown(message_or_progname_or_tags = nil, progname_or_tags = nil, &block)
309
430
  call_add_entry(UNKNOWN, message_or_progname_or_tags, progname_or_tags, &block)
310
431
  end
311
432
 
433
+ # Add a message when the severity is not known.
434
+ #
435
+ # @param [Object] msg The message to log.
436
+ # @return [void]
312
437
  def <<(msg)
313
438
  add_entry(UNKNOWN, msg)
314
439
  end
@@ -316,7 +441,10 @@ module Lumberjack
316
441
  # Silence the logger by setting a new log level inside a block. By default, only +ERROR+ or +FATAL+
317
442
  # messages will be logged.
318
443
  #
319
- # === Example
444
+ # @param [Integer, String, Symbol] temporary_level The log level to use inside the block.
445
+ # @return [Object] The result of the block.
446
+ #
447
+ # @example
320
448
  #
321
449
  # logger.level = Logger::INFO
322
450
  # logger.silence do
@@ -325,7 +453,7 @@ module Lumberjack
325
453
  def silence(temporary_level = ERROR, &block)
326
454
  if silencer
327
455
  unless temporary_level.is_a?(Integer)
328
- temporary_level = Severity::label_to_level(temporary_level)
456
+ temporary_level = Severity.label_to_level(temporary_level)
329
457
  end
330
458
  push_thread_local_value(:lumberjack_logger_level, temporary_level, &block)
331
459
  else
@@ -333,8 +461,19 @@ module Lumberjack
333
461
  end
334
462
  end
335
463
 
464
+ # Provided for compatibility with ActiveSupport::LoggerThreadSafeLevel to temporarily set the log level.
465
+ #
466
+ # @param [Integer, String, Symbol] level The log level to use inside the block.
467
+ # @return [Object] The result of the block.
468
+ def log_at(level, &block)
469
+ with_level(level, &block)
470
+ end
471
+
336
472
  # Set the program name that is associated with log messages. If a block
337
473
  # is given, the program name will be valid only within the block.
474
+ #
475
+ # @param [String] value The program name to use.
476
+ # @return [void]
338
477
  def set_progname(value, &block)
339
478
  if block
340
479
  push_thread_local_value(:lumberjack_logger_progname, value, &block)
@@ -343,58 +482,142 @@ module Lumberjack
343
482
  end
344
483
  end
345
484
 
485
+ # Set the logger progname for the duration of the block.
486
+ #
487
+ # @yield [Object] The block to execute with the program name set.
488
+ # @param [String] value The program name to use.
489
+ # @return [Object] The result of the block.
490
+ def with_progname(value, &block)
491
+ set_progname(value, &block)
492
+ end
493
+
346
494
  # Get the program name associated with log messages.
495
+ #
496
+ # @return [String]
347
497
  def progname
348
498
  thread_local_value(:lumberjack_logger_progname) || @progname
349
499
  end
350
500
 
351
501
  # Set a hash of tags on logger. If a block is given, the tags will only be set
352
- # for the duration of the block. If this method is called inside such a block,
353
- # the tags will only be defined on the tags in that block. When the parent block
354
- # exits, all the tags will be reverted. If there is no block, then the tags will
355
- # be defined as global and apply to all log statements.
502
+ # for the duration of the block. Otherwise the tags will be applied on the current
503
+ # logger context for the duration of that context.
504
+ #
505
+ # If there is no block or context, the tags will be applied to the global context.
506
+ # This behavior is deprecated. Use the `tag_globally` method to set global tags instead.
507
+ #
508
+ # @param [Hash] tags The tags to set.
509
+ # @return [void]
356
510
  def tag(tags, &block)
357
- tags = Tags.stringify_keys(tags)
358
511
  thread_tags = thread_local_value(:lumberjack_logger_tags)
359
512
  if block
360
- merged_tags = (thread_tags ? thread_tags.merge(tags) : tags.dup)
513
+ merged_tags = (thread_tags ? thread_tags.dup : {})
514
+ TagContext.new(merged_tags).tag(tags)
361
515
  push_thread_local_value(:lumberjack_logger_tags, merged_tags, &block)
362
516
  elsif thread_tags
363
- thread_tags.merge!(tags)
517
+ TagContext.new(thread_tags).tag(tags)
518
+ nil
364
519
  else
365
- @tags.merge!(tags)
520
+ Utils.deprecated("Lumberjack::Logger#tag", "Lumberjack::Logger#tag must be called with a block or inside a context block. In version 2.0 it will no longer be used for setting global tags. Use Lumberjack::Logger#tag_globally instead.") do
521
+ tag_globally(tags)
522
+ end
366
523
  end
367
- nil
368
524
  end
369
525
 
370
- # Remove a tag from the current tag context. If this is called inside a block to a
371
- # call to `tag`, the tags will only be removed for the duration of that block. Otherwise
372
- # they will be removed from the global tags.
373
- def remove_tag(*tag_names)
374
- thread_tags = thread_local_value(:lumberjack_logger_tags)
375
- if thread_tags
376
- tag_names.each { |name| thread_tags.delete(name.to_s) }
526
+ # Set up a context block for the logger. All tags added within the block will be cleared when
527
+ # the block exits.
528
+ #
529
+ # @param [Proc] block The block to execute with the tag context.
530
+ # @return [TagContext] If no block is passed, then a Lumberjack::TagContext is returned that can be used
531
+ # to interact with the tags (add, remove, etc.).
532
+ # @yield [TagContext] If a block is passed, it will be yielded a TagContext object that can be used to
533
+ # add or remove tags within the context.
534
+ def context(&block)
535
+ if block
536
+ thread_tags = thread_local_value(:lumberjack_logger_tags)&.dup
537
+ thread_tags ||= {}
538
+ push_thread_local_value(:lumberjack_logger_tags, thread_tags) do
539
+ block.call(TagContext.new(thread_tags))
540
+ end
377
541
  else
378
- tag_names.each { |name| @tags.delete(name.to_s) }
542
+ TagContext.new(thread_local_value(:lumberjack_logger_tags) || {})
379
543
  end
380
544
  end
381
545
 
546
+ # Add global tags to the logger that will appear on all log entries.
547
+ #
548
+ # @param [Hash] tags The tags to set.
549
+ # @return [void]
550
+ def tag_globally(tags)
551
+ TagContext.new(@tags).tag(tags)
552
+ nil
553
+ end
554
+
555
+ # Remove a tag from the current tag context. If this is called inside a tag context,
556
+ # the tags will only be removed for the duration of that block. Otherwise they will be removed
557
+ # from the global tags.
558
+ #
559
+ # @param [Array<String, Symbol>] tag_names The tags to remove.
560
+ # @return [void]
561
+ def remove_tag(*tag_names)
562
+ tags = thread_local_value(:lumberjack_logger_tags) || @tags
563
+ TagContext.new(tags).delete(*tag_names)
564
+ end
565
+
382
566
  # Return all tags in scope on the logger including global tags set on the Lumberjack
383
- # context, tags set on the logger, and tags set on the current block for the logger
567
+ # context, tags set on the logger, and tags set on the current block for the logger.
568
+ #
569
+ # @return [Hash]
384
570
  def tags
385
571
  tags = {}
386
572
  context_tags = Lumberjack.context_tags
387
573
  tags.merge!(context_tags) if context_tags && !context_tags.empty?
388
- tags.merge!(@tags) if !@tags.empty?
574
+ tags.merge!(@tags) if !@tags.empty? && !thread_local_value(:lumberjack_logger_untagged)
389
575
  scope_tags = thread_local_value(:lumberjack_logger_tags)
390
576
  tags.merge!(scope_tags) if scope_tags && !scope_tags.empty?
391
577
  tags
392
578
  end
393
579
 
580
+ # Get the value of a tag by name from the current tag context.
581
+ #
582
+ # @param [String, Symbol] name The name of the tag to get.
583
+ # @return [Object, nil] The value of the tag or nil if the tag does not exist.
584
+ def tag_value(name)
585
+ name = name.join(".") if name.is_a?(Array)
586
+ TagContext.new(tags)[name]
587
+ end
588
+
589
+ # Remove all tags on the current logger and logging context within a block.
590
+ # You can still set new block scoped tags within theuntagged block and provide
591
+ # tags on individual log methods.
592
+ #
593
+ # @return [void]
594
+ def untagged(&block)
595
+ Lumberjack.use_context(nil) do
596
+ scope_tags = thread_local_value(:lumberjack_logger_tags)
597
+ untagged = thread_local_value(:lumberjack_logger_untagged)
598
+ begin
599
+ set_thread_local_value(:lumberjack_logger_untagged, true)
600
+ set_thread_local_value(:lumberjack_logger_tags, nil)
601
+ tag({}, &block)
602
+ ensure
603
+ set_thread_local_value(:lumberjack_logger_untagged, untagged)
604
+ set_thread_local_value(:lumberjack_logger_tags, scope_tags)
605
+ end
606
+ end
607
+ end
608
+
609
+ # Return true if the thread is currently in a Lumberjack::Context block.
610
+ # When the logger is in a context block, tagging will only apply to that block.
611
+ #
612
+ # @return [Boolean]
613
+ def in_tag_context?
614
+ !!thread_local_value(:lumberjack_logger_tags)
615
+ end
616
+
394
617
  private
395
618
 
396
619
  # Dereference arguments to log calls so we can have methods with compatibility with ::Logger
397
- def call_add_entry(severity, message_or_progname_or_tags, progname_or_tags, &block) #:nodoc:
620
+ def call_add_entry(severity, message_or_progname_or_tags, progname_or_tags, &block) # :nodoc:
398
621
  message = nil
399
622
  progname = nil
400
623
  tags = nil
@@ -418,8 +641,19 @@ module Lumberjack
418
641
  add_entry(severity, message, progname, tags)
419
642
  end
420
643
 
644
+ # Merge a tags hash into an existing tags hash.
645
+ def merge_tags(current_tags, tags)
646
+ if current_tags.nil? || current_tags.empty?
647
+ tags
648
+ elsif tags.nil?
649
+ current_tags
650
+ else
651
+ current_tags.merge(tags)
652
+ end
653
+ end
654
+
421
655
  # Set a local value for a thread tied to this object.
422
- def set_thread_local_value(name, value) #:nodoc:
656
+ def set_thread_local_value(name, value) # :nodoc:
423
657
  values = Thread.current[name]
424
658
  unless values
425
659
  values = {}
@@ -434,13 +668,13 @@ module Lumberjack
434
668
  end
435
669
 
436
670
  # Get a local value for a thread tied to this object.
437
- def thread_local_value(name) #:nodoc:
671
+ def thread_local_value(name) # :nodoc:
438
672
  values = Thread.current[name]
439
673
  values[self] if values
440
674
  end
441
675
 
442
676
  # Set a local value for a thread tied to this object within a block.
443
- def push_thread_local_value(name, value) #:nodoc:
677
+ def push_thread_local_value(name, value) # :nodoc:
444
678
  save_val = thread_local_value(name)
445
679
  set_thread_local_value(name, value)
446
680
  begin
@@ -451,7 +685,7 @@ module Lumberjack
451
685
  end
452
686
 
453
687
  # Open a logging device.
454
- def open_device(device, options) #:nodoc:
688
+ def open_device(device, options) # :nodoc:
455
689
  if device.nil?
456
690
  nil
457
691
  elsif device.is_a?(Device)
@@ -472,28 +706,26 @@ module Lumberjack
472
706
  end
473
707
  end
474
708
 
475
- def write_to_device(entry) #:nodoc:
476
- begin
477
- device.write(entry)
478
- rescue => e
479
- $stderr.puts("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}")
480
- $stderr.puts(entry.to_s)
481
- end
709
+ def write_to_device(entry) # :nodoc:
710
+ device.write(entry)
711
+ rescue => e
712
+ # rubocop:disable Style/StderrPuts
713
+ $stderr.puts("#{e.class.name}: #{e.message}#{" at " + e.backtrace.first if e.backtrace}")
714
+ $stderr.puts(entry.to_s)
715
+ # rubocop:enable Style/StderrPuts
482
716
  end
483
717
 
484
718
  # Create a thread that will periodically call flush.
485
- def create_flusher_thread(flush_seconds) #:nodoc:
719
+ def create_flusher_thread(flush_seconds) # :nodoc:
486
720
  if flush_seconds > 0
487
- begin
488
- logger = self
489
- Thread.new do
490
- while !closed?
491
- begin
492
- sleep(flush_seconds)
493
- logger.flush if Time.now - logger.last_flushed_at >= flush_seconds
494
- rescue => e
495
- $stderr.puts("Error flushing log: #{e.inspect}")
496
- end
721
+ logger = self
722
+ Thread.new do
723
+ until closed?
724
+ begin
725
+ sleep(flush_seconds)
726
+ logger.flush if Time.now - logger.last_flushed_at >= flush_seconds
727
+ rescue => e
728
+ warn("Error flushing log: #{e.inspect}")
497
729
  end
498
730
  end
499
731
  end