lumberjack 2.0.4 → 2.1.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +0 -17
  3. data/CHANGELOG.md +58 -0
  4. data/README.md +35 -0
  5. data/UPGRADE_GUIDE.md +1 -1
  6. data/VERSION +1 -1
  7. data/lib/lumberjack/attribute_formatter.rb +0 -51
  8. data/lib/lumberjack/context.rb +6 -1
  9. data/lib/lumberjack/context_locals.rb +10 -2
  10. data/lib/lumberjack/context_logger.rb +14 -4
  11. data/lib/lumberjack/device/buffer.rb +52 -37
  12. data/lib/lumberjack/device/multi.rb +2 -2
  13. data/lib/lumberjack/device/test.rb +70 -13
  14. data/lib/lumberjack/device/writer.rb +35 -12
  15. data/lib/lumberjack/device.rb +0 -2
  16. data/lib/lumberjack/device_registry.rb +7 -6
  17. data/lib/lumberjack/entry_formatter.rb +4 -2
  18. data/lib/lumberjack/formatter/structured_formatter.rb +4 -4
  19. data/lib/lumberjack/formatter/tags_formatter.rb +3 -3
  20. data/lib/lumberjack/formatter.rb +0 -11
  21. data/lib/lumberjack/formatter_registry.rb +7 -6
  22. data/lib/lumberjack/io_compatibility.rb +8 -0
  23. data/lib/lumberjack/log_entry.rb +13 -46
  24. data/lib/lumberjack/log_entry_matcher/indifferent_hash.rb +83 -0
  25. data/lib/lumberjack/log_entry_matcher/score.rb +12 -4
  26. data/lib/lumberjack/log_entry_matcher.rb +229 -28
  27. data/lib/lumberjack/logger.rb +19 -185
  28. data/lib/lumberjack/template.rb +65 -42
  29. data/lib/lumberjack/template_registry.rb +7 -6
  30. data/lib/lumberjack/utils.rb +1 -26
  31. data/lib/lumberjack.rb +5 -25
  32. data/lumberjack.gemspec +0 -2
  33. metadata +2 -21
  34. data/lib/lumberjack/device/date_rolling_log_file.rb +0 -22
  35. data/lib/lumberjack/device/size_rolling_log_file.rb +0 -22
  36. data/lib/lumberjack/formatter/tagged_message.rb +0 -17
  37. data/lib/lumberjack/tag_context.rb +0 -15
  38. data/lib/lumberjack/tag_formatter.rb +0 -31
  39. data/lib/lumberjack/tags.rb +0 -36
@@ -12,8 +12,16 @@ module Lumberjack
12
12
  # device in testing scenarios but can be used anywhere log entry filtering
13
13
  # is needed.
14
14
  #
15
+ # A matcher can optionally be constructed with an entry formatter. Filter
16
+ # values are always compared raw first. If a raw comparison fails, the filter
17
+ # value is run through the formatter and compared again. Since log entries
18
+ # are formatted before they are written to a device, this allows expectations
19
+ # to be written with unformatted values (an Exception, for example) and still
20
+ # match the formatted values captured on the entry.
21
+ #
15
22
  # @see Lumberjack::Device::Test
16
23
  class LogEntryMatcher
24
+ require_relative "log_entry_matcher/indifferent_hash"
17
25
  require_relative "log_entry_matcher/score"
18
26
 
19
27
  # Create a new log entry matcher with optional filtering criteria. All
@@ -27,14 +35,29 @@ module Lumberjack
27
35
  # Accepts numeric levels or symbolic names (:debug, :info, etc.)
28
36
  # @param progname [Object, nil] Pattern to match against program names.
29
37
  # Supports strings, regular expressions, or any object responding to ===
30
- # @param attributes [Hash, nil] Hash of attribute patterns to match against
31
- # log entry attributes. Supports nested attribute matching and dot notation
32
- def initialize(message: nil, severity: nil, progname: nil, attributes: nil)
38
+ # @param attributes [Hash, Object, nil] Hash of attribute patterns to match against
39
+ # log entry attributes. Supports nested attribute matching and dot notation.
40
+ # Any other object is matched against the entire attributes hash with ===
41
+ # so matchers like RSpec's hash_including can be used.
42
+ # @param formatter [Lumberjack::EntryFormatter, Lumberjack::Logger, nil] Optional
43
+ # formatter used to format filter values when a raw comparison fails. A Logger
44
+ # can be passed to use its entry formatter. The message filter is formatted with
45
+ # the message formatter; when the result is a MessageAttributes, only the message
46
+ # part is used and the derived attributes are ignored. Attribute filter values are
47
+ # formatted with the attribute formatter using their dot notation names. Pattern
48
+ # objects (classes, regular expressions, ranges, procs, hashes, and test framework
49
+ # matchers) are never formatted.
50
+ # @raise [ArgumentError] If the formatter is not an EntryFormatter or a Logger.
51
+ def initialize(message: nil, severity: nil, progname: nil, attributes: nil, formatter: nil)
33
52
  message = message.strip if message.is_a?(String)
34
53
  @message_filter = message
35
54
  @severity_filter = Severity.coerce(severity) if severity
36
55
  @progname_filter = progname
37
- @attributes_filter = Utils.expand_attributes(attributes) if attributes
56
+ if attributes
57
+ @attributes_filter = attributes.is_a?(Hash) ? Utils.expand_attributes(attributes) : attributes
58
+ end
59
+ @formatter = resolve_formatter(formatter)
60
+ @formatted_attribute_filters = {}
38
61
  end
39
62
 
40
63
  # Test whether a log entry matches all specified criteria. The entry must
@@ -44,16 +67,56 @@ module Lumberjack
44
67
  # @param entry [Lumberjack::LogEntry] The log entry to test against the matcher
45
68
  # @return [Boolean] True if the entry matches all specified criteria, false otherwise
46
69
  def match?(entry)
47
- return false unless match_filter?(entry.message, @message_filter)
48
- return false unless match_filter?(entry.severity, @severity_filter)
49
- return false unless match_filter?(entry.progname, @progname_filter)
70
+ diff(entry).empty?
71
+ end
72
+
73
+ # Compare a log entry against the matcher criteria and return only the fields
74
+ # that do not match. An empty hash means the entry matches, so
75
+ # +diff(entry).empty?+ is always equal to +match?(entry)+.
76
+ #
77
+ # The returned hash uses string keys for the fields ("message", "severity",
78
+ # "progname", and "attributes"). Each mismatched field maps to a hash with
79
+ # +:expected+ and +:actual+ (the entry value). Severity values are converted
80
+ # to labels on both sides for readability. When a formatter is set and the
81
+ # filter value was formatted, +:expected+ shows the formatted filter value
82
+ # so both sides of the mismatch are in the same form.
83
+ #
84
+ # Attribute mismatches are reported per attribute using dot notation keys.
85
+ # A missing attribute is reported with +actual: nil+. An attribute that was
86
+ # expected to be absent (a nil or empty filter value) is reported with the
87
+ # raw filter as +:expected+ and the entry value as +:actual+. When the
88
+ # attributes filter is not a hash (a matcher object applied to the whole
89
+ # attributes hash), a failure is reported as a single hash with +:expected+
90
+ # and +:actual+ keys instead of per attribute detail.
91
+ #
92
+ # @param entry [Lumberjack::LogEntry] The log entry to compare against the matcher
93
+ # @return [Hash] A hash of the fields that do not match; empty if the entry matches
94
+ def diff(entry)
95
+ result = {}
96
+
97
+ unless match_message?(entry.message)
98
+ result["message"] = {expected: expected_message, actual: entry.message}
99
+ end
100
+
101
+ unless match_filter?(entry.severity, @severity_filter)
102
+ result["severity"] = {expected: Severity.level_to_label(@severity_filter), actual: entry.severity_label}
103
+ end
104
+
105
+ unless match_filter?(entry.progname, @progname_filter)
106
+ result["progname"] = {expected: @progname_filter, actual: entry.progname}
107
+ end
50
108
 
51
109
  if @attributes_filter
52
- attributes = Utils.expand_attributes(entry.attributes)
53
- return false unless match_attributes?(attributes, @attributes_filter)
110
+ attributes = IndifferentHash.wrap(Utils.expand_attributes(entry.attributes))
111
+ if @attributes_filter.is_a?(Hash)
112
+ mismatches = attribute_mismatches(attributes, @attributes_filter)
113
+ result["attributes"] = mismatches unless mismatches.empty?
114
+ elsif !match_filter?(attributes, @attributes_filter)
115
+ result["attributes"] = {expected: @attributes_filter, actual: attributes}
116
+ end
54
117
  end
55
118
 
56
- true
119
+ result
57
120
  end
58
121
 
59
122
  # Find the closest matching log entry from a list of candidates. This method
@@ -81,6 +144,21 @@ module Lumberjack
81
144
  )
82
145
  end
83
146
 
147
+ # Coerce the formatter argument into an EntryFormatter or nil.
148
+ #
149
+ # @param formatter [Lumberjack::EntryFormatter, Lumberjack::Logger, nil] The formatter argument.
150
+ # @return [Lumberjack::EntryFormatter, nil] The resolved entry formatter.
151
+ def resolve_formatter(formatter)
152
+ return nil if formatter.nil?
153
+ return formatter if formatter.is_a?(Lumberjack::EntryFormatter)
154
+
155
+ if formatter.respond_to?(:formatter) && formatter.formatter.is_a?(Lumberjack::EntryFormatter)
156
+ formatter.formatter
157
+ else
158
+ raise ArgumentError.new("formatter must be a Lumberjack::EntryFormatter or Lumberjack::Logger")
159
+ end
160
+ end
161
+
84
162
  # Apply a filter pattern against a value using case equality. Returns true
85
163
  # if no filter is specified (nil) or if the filter matches the value.
86
164
  #
@@ -93,34 +171,157 @@ module Lumberjack
93
171
  filter === value
94
172
  end
95
173
 
96
- # Recursively match nested attribute structures against filter patterns.
97
- # Handles both simple attribute matching and complex nested hash structures
98
- # with support for partial matching and empty value detection.
174
+ # Match the message filter against an entry message. If the raw comparison
175
+ # fails, the filter is formatted with the message formatter and compared again.
176
+ #
177
+ # @param message [Object] The entry message.
178
+ # @return [Boolean] True if the message matches.
179
+ def match_message?(message)
180
+ return true if match_filter?(message, @message_filter)
181
+ return false if @formatter.nil? || @message_filter.nil? || pattern_filter?(@message_filter)
182
+
183
+ formatted = formatted_message_filter
184
+ return false if formatted.equal?(@message_filter)
185
+
186
+ match_filter?(message, formatted)
187
+ end
188
+
189
+ # The message filter value to report in a diff. The formatted value is used
190
+ # when the formatter changed the filter so both sides of a mismatch are in
191
+ # the same form.
192
+ #
193
+ # @return [Object] The message filter value to report.
194
+ def expected_message
195
+ if @formatter && !@message_filter.nil? && !pattern_filter?(@message_filter)
196
+ formatted_message_filter
197
+ else
198
+ @message_filter
199
+ end
200
+ end
201
+
202
+ # Format the message filter with the message formatter. The result is
203
+ # memoized so the formatter is only invoked once per matcher.
99
204
  #
100
- # @param attributes [Hash] The expanded attributes hash from the log entry
101
- # @param filter [Hash] The filter patterns to match against attributes
102
- # @return [Boolean] True if all filter patterns match their corresponding attributes
103
- def match_attributes?(attributes, filter)
104
- return true unless filter
105
- return false unless attributes
106
-
107
- filter.all? do |name, value_filter|
205
+ # @return [Object] The formatted message filter.
206
+ def formatted_message_filter
207
+ return @formatted_message_filter if defined?(@formatted_message_filter)
208
+
209
+ value = @message_filter
210
+ message_formatter = @formatter.message_formatter
211
+ if message_formatter.respond_to?(:format)
212
+ begin
213
+ value = message_formatter.format(@message_filter)
214
+ rescue
215
+ value = @message_filter
216
+ end
217
+ end
218
+ value = value.message if value.is_a?(MessageAttributes)
219
+ value = value.strip if value.is_a?(String)
220
+ @formatted_message_filter = value
221
+ end
222
+
223
+ # Recursively compare attribute filter patterns against entry attributes and
224
+ # collect the mismatches. Keys in the returned hash use dot notation.
225
+ #
226
+ # @param attributes [Hash] The expanded attributes hash from the log entry.
227
+ # @param filter [Hash] The filter patterns to match against the attributes.
228
+ # @param path [String, nil] The dot notation path of the current nesting level.
229
+ # @param allow_formatting [Boolean] Whether filter values can be formatted on a failed comparison.
230
+ # @return [Hash] The mismatched attributes keyed by dot notation name.
231
+ def attribute_mismatches(attributes, filter, path = nil, allow_formatting: true)
232
+ mismatches = {}
233
+
234
+ filter.each do |name, value_filter|
108
235
  name = name.to_s
109
- attribute_values = attributes[name]
110
- if attribute_values.is_a?(Hash)
236
+ key = path ? "#{path}.#{name}" : name
237
+ attribute_value = attributes[name]
238
+
239
+ if attribute_value.is_a?(Hash)
111
240
  if value_filter.is_a?(Hash)
112
- match_attributes?(attribute_values, value_filter)
241
+ mismatches.merge!(attribute_mismatches(attribute_value, value_filter, key, allow_formatting: allow_formatting))
113
242
  else
114
- match_filter?(attribute_values, value_filter)
243
+ add_leaf_mismatches(mismatches, key, attribute_value, value_filter, allow_formatting)
115
244
  end
116
245
  elsif value_filter.nil? || (value_filter.is_a?(Enumerable) && value_filter.empty?)
117
- attribute_values.nil? || (attribute_values.is_a?(Array) && attribute_values.empty?)
246
+ empty_value = attribute_value.nil? || (attribute_value.is_a?(Array) && attribute_value.empty?)
247
+ mismatches[key] = {expected: value_filter, actual: attribute_value} unless empty_value
118
248
  elsif attributes.include?(name)
119
- match_filter?(attribute_values, value_filter)
249
+ add_leaf_mismatches(mismatches, key, attribute_value, value_filter, allow_formatting)
120
250
  else
121
- false
251
+ mismatches[key] = {expected: value_filter, actual: nil}
252
+ end
253
+ end
254
+
255
+ mismatches
256
+ end
257
+
258
+ # Match a single attribute value against a filter and add any mismatches to
259
+ # the collector. If the raw comparison fails, the filter is formatted with the
260
+ # attribute formatter and compared again. Mismatches are reported with the
261
+ # formatted filter value so both sides are in the same form. A formatted result
262
+ # that is a hash is compared recursively against the entry value with formatting
263
+ # disabled since its values are already formatted, and mismatches are reported
264
+ # per attribute under the leaf's dot notation name.
265
+ #
266
+ # @param mismatches [Hash] The mismatch collector.
267
+ # @param path [String] The dot notation name of the attribute.
268
+ # @param value [Object] The entry attribute value.
269
+ # @param filter [Object] The filter pattern.
270
+ # @param allow_formatting [Boolean] Whether the filter can be formatted on a failed comparison.
271
+ # @return [void]
272
+ def add_leaf_mismatches(mismatches, path, value, filter, allow_formatting)
273
+ return if match_filter?(value, filter)
274
+
275
+ unless allow_formatting && @formatter && !pattern_filter?(filter)
276
+ mismatches[path] = {expected: filter, actual: value}
277
+ return
278
+ end
279
+
280
+ formatted = formatted_attribute_filter(path, filter)
281
+ if formatted.equal?(filter)
282
+ mismatches[path] = {expected: filter, actual: value}
283
+ elsif formatted.is_a?(Hash)
284
+ if value.is_a?(Hash)
285
+ mismatches.merge!(attribute_mismatches(value, Utils.expand_attributes(formatted), path, allow_formatting: false))
286
+ else
287
+ mismatches[path] = {expected: formatted, actual: value}
288
+ end
289
+ elsif !match_filter?(value, formatted)
290
+ mismatches[path] = {expected: formatted, actual: value}
291
+ end
292
+ end
293
+
294
+ # Determine if a filter is a pattern that must never be formatted since
295
+ # formatting it would destroy its matching behavior.
296
+ #
297
+ # @param filter [Object] The filter to check.
298
+ # @return [Boolean] True if the filter is a pattern object.
299
+ def pattern_filter?(filter)
300
+ filter.is_a?(Module) || filter.is_a?(Regexp) || filter.is_a?(Range) ||
301
+ filter.is_a?(Proc) || filter.is_a?(Hash) || filter.respond_to?(:matches?)
302
+ end
303
+
304
+ # Format an attribute filter value with the attribute formatter using its dot
305
+ # notation name so name based formatters apply. Results are memoized per name.
306
+ # The raw filter is returned if formatting fails or removes the attribute.
307
+ #
308
+ # @param path [String] The dot notation name of the attribute.
309
+ # @param filter [Object] The filter value to format.
310
+ # @return [Object] The formatted filter value.
311
+ def formatted_attribute_filter(path, filter)
312
+ return @formatted_attribute_filters[path] if @formatted_attribute_filters.include?(path)
313
+
314
+ attribute_formatter = @formatter.attribute_formatter
315
+ formatted = filter
316
+ if attribute_formatter.respond_to?(:format)
317
+ begin
318
+ result = attribute_formatter.format({path => filter})
319
+ formatted = result.fetch(path, filter) if result.is_a?(Hash)
320
+ rescue
321
+ formatted = filter
122
322
  end
123
323
  end
324
+ @formatted_attribute_filters[path] = formatted
124
325
  end
125
326
  end
126
327
  end
@@ -69,11 +69,9 @@ module Lumberjack
69
69
  # log files will be rolled when they get to the size specified in shift_size and the number of
70
70
  # files to keep will be determined by this value. Otherwise it will be interpreted as a date
71
71
  # rolling value and must be one of "daily", "weekly", or "monthly". This parameter has no
72
- # effect unless the device parameter is a file path or file stream. This can also be
73
- # specified with the :roll keyword argument.
72
+ # effect unless the device parameter is a file path or file stream.
74
73
  # @param shift_size [Integer] The size in bytes of the log files before rolling them. This can
75
74
  # be passed as a string with a unit suffix of K, M, or G (e.g. "10M" for 10 megabytes).
76
- # This can also be specified with the :max_size keyword argument.
77
75
  # @param level [Integer, Symbol, String] The logging level below which messages will be ignored.
78
76
  # @param progname [String] The name of the program that will be recorded with each log entry.
79
77
  # @param formatter [Lumberjack::EntryFormatter, Lumberjack::Formatter, ::Logger::Formatter, :default, #call]
@@ -95,16 +93,7 @@ module Lumberjack
95
93
  level: DEBUG, progname: nil, formatter: nil, datetime_format: nil,
96
94
  binmode: false, shift_period_suffix: "%Y%m%d", **kwargs)
97
95
  init_context_locals!
98
-
99
- if shift_age.is_a?(Hash)
100
- Lumberjack::Utils.deprecated("Logger.new(options)", "Passing a Hash as the second argument to Logger.new is deprecated and will be removed in version 2.1; use keyword arguments instead.")
101
- options = shift_age
102
- level = options[:level] if options.include?(:level)
103
- progname = options[:progname] if options.include?(:progname)
104
- formatter = options[:formatter] if options.include?(:formatter)
105
- datetime_format = options[:datetime_format] if options.include?(:datetime_format)
106
- kwargs = options.merge(kwargs)
107
- end
96
+ @recursion_guard_key = :"lumberjack_logging_#{object_id}"
108
97
 
109
98
  self.isolation_level = kwargs.delete(:isolation_level) || Lumberjack.isolation_level
110
99
 
@@ -113,36 +102,13 @@ module Lumberjack
113
102
  device_options = kwargs.merge(shift_age: shift_age, shift_size: size_with_units(shift_size), binmode: binmode, shift_period_suffix: shift_period_suffix)
114
103
  device_options[:standard_logger_formatter] = formatter if standard_logger_formatter?(formatter)
115
104
 
116
- if device_options.include?(:roll)
117
- Utils.deprecated("Logger.options(:roll)", "Lumberjack::Logger :roll option is deprecated and will be removed in version 2.1; use the shift_age argument instead.")
118
- device_options[:shift_age] = device_options.delete(:roll) unless shift_age != 0
119
- end
120
-
121
- if device_options.include?(:max_size)
122
- Utils.deprecated("Logger.options(:max_size)", "Lumberjack::Logger :max_size option is deprecated and will be removed in version 2.1; use the shift_size argument instead.")
123
- device_options[:shift_age] = 10 if shift_age == 0
124
- device_options[:shift_size] = device_options.delete(:max_size)
125
- end
126
-
127
- message_formatter = nil
128
- if device_options.include?(:message_formatter)
129
- Utils.deprecated("Logger.options(:message_formatter)", "Lumberjack::Logger :message_formatter option is deprecated and will be removed in version 2.1; use the formatter argument instead to specify an EntryFormatter.")
130
- message_formatter = device_options.delete(:message_formatter)
131
- end
132
-
133
- attribute_formatter = nil
134
- if device_options.include?(:tag_formatter)
135
- Utils.deprecated("Logger.options(:tag_formatter)", "Lumberjack::Logger :tag_formatter option is deprecated and will be removed in version 2.1; use the formatter argument instead to specify an EntryFormatter.")
136
- attribute_formatter = device_options.delete(:tag_formatter)
137
- end
138
-
139
105
  @logdev = Device.open_device(logdev, device_options)
140
106
 
141
107
  @context = Context.new
142
108
  self.level = level || DEBUG
143
109
  self.progname = progname
144
110
 
145
- self.formatter = build_entry_formatter(formatter, message_formatter, attribute_formatter)
111
+ self.formatter = build_entry_formatter(formatter)
146
112
  self.datetime_format = datetime_format if datetime_format
147
113
 
148
114
  @closed = false
@@ -169,7 +135,7 @@ module Lumberjack
169
135
  # @param value [Lumberjack::EntryFormatter, ::Logger::Formatter, #call] The formatter to use.
170
136
  # @return [void]
171
137
  def formatter=(value)
172
- @formatter = build_entry_formatter(value, nil, nil)
138
+ @formatter = build_entry_formatter(value)
173
139
  end
174
140
 
175
141
  # Get the timestamp format on the device if it has one.
@@ -219,20 +185,6 @@ module Lumberjack
219
185
  formatter.attribute_formatter = value
220
186
  end
221
187
 
222
- # @deprecated Use {#attribute_formatter} instead.
223
- def tag_formatter
224
- Utils.deprecated("Logger#tag_formatter", "Lumberjack::Logger#tag_formatter is deprecated and will be removed in version 2.1; use attribute_formatter instead.") do
225
- formatter.attributes.attribute_formatter
226
- end
227
- end
228
-
229
- # @deprecated Use {#attribute_formatter=} instead.
230
- def tag_formatter=(value)
231
- Utils.deprecated("Logger#tag_formatter=", "Lumberjack::Logger#tag_formatter= is deprecated and will be removed in version 2.1; use attribute_formatter= instead.") do
232
- formatter.attributes.attribute_formatter = value
233
- end
234
- end
235
-
236
188
  # Flush the logging device. Messages are not guaranteed to be written until this method is called.
237
189
  #
238
190
  # @return [void]
@@ -269,125 +221,6 @@ module Lumberjack
269
221
  self
270
222
  end
271
223
 
272
- # Set the program name that is associated with log messages. If a block
273
- # is given, the program name will be valid only within the block.
274
- #
275
- # @param value [String] The program name to use.
276
- # @return [void]
277
- # @deprecated Use with_progname or progname= instead.
278
- def set_progname(value, &block)
279
- Utils.deprecated("Logger#set_progname", "Lumberjack::Logger#set_progname is deprecated and will be removed in version 2.1; use with_progname or progname= instead.") do
280
- if block
281
- with_progname(value, &block)
282
- else
283
- self.progname = value
284
- end
285
- end
286
- end
287
-
288
- # Alias method for #attributes to provide backward compatibility with version 1.x API. This
289
- # method will eventually be removed.
290
- #
291
- # @return [Hash]
292
- # @deprecated Use {#attributes} instead
293
- def tags
294
- Utils.deprecated("Logger#tags", "Lumberjack::Logger#tags is deprecated and will be removed in version 2.1; use attributes instead.") do
295
- attributes
296
- end
297
- end
298
-
299
- # Alias method for #attribute_value to provide backward compatibility with version 1.x API. This
300
- # method will eventually be removed.
301
- #
302
- # @return [Hash]
303
- # @deprecated Use {#attribute_value} instead
304
- def tag_value(name)
305
- Utils.deprecated("Logger#tag_value", "Lumberjack::Logger#tag_value is deprecated and will be removed in version 2.1; use attribute_value instead.") do
306
- attribute_value(name)
307
- end
308
- end
309
-
310
- # Use tag! instead
311
- #
312
- # @return [void]
313
- # @deprecated Use {#tag!} instead.
314
- def tag_globally(tags)
315
- Utils.deprecated("Logger#tag_globally", "Lumberjack::Logger#tag_globally is deprecated and will be removed in version 2.1; use tag! instead.") do
316
- tag!(tags)
317
- end
318
- end
319
-
320
- # Use context? instead
321
- #
322
- # @return [Boolean]
323
- # @deprecated Use {#in_context?} instead.
324
- def in_tag_context?
325
- Utils.deprecated("Logger#in_tag_context?", "Lumberjack::Logger#in_tag_context? is deprecated and will be removed in version 2.1; use in_context? instead.") do
326
- context?
327
- end
328
- end
329
-
330
- # Remove a tag from the current context block. If this is called inside a context block,
331
- # the attributes will only be removed for the duration of that block. Otherwise they will be removed
332
- # from the global attributes.
333
- #
334
- # @param tag_names [Array<String, Symbol>] The attributes to remove.
335
- # @return [void]
336
- # @deprecated Use untag or untag! instead.
337
- def remove_tag(*tag_names)
338
- Utils.deprecated("Logger#remove_tag", "Lumberjack::Logger#remove_tag is deprecated and will be removed in version 2.1; use untag or untag! instead.") do
339
- attributes = current_context&.attributes
340
- AttributesHelper.new(attributes).delete(*tag_names) if attributes
341
- end
342
- end
343
-
344
- # Alias for append_to(:tagged) for compatibility with ActiveSupport support in Lumberjack 1.x.
345
- # This functionality has been moved to the lumberjack_rails gem. Note that in that gem the
346
- # tags are added to the :tags attribute instead of the :tagged attribute.
347
- #
348
- # @see append_to
349
- # @deprecated This implementation is deprecated. Install the lumberjack_rails gem for full support.
350
- def tagged(*tags, &block)
351
- deprecation_message = "Install the lumberjack_rails gem for full support of the tagged method."
352
- Utils.deprecated("Logger#tagged", deprecation_message) do
353
- append_to(:tagged, *tags, &block)
354
- end
355
- end
356
-
357
- # Alias for clear_attributes.
358
- #
359
- # @see clear_attributes
360
- # @deprecated Use clear_attributes instead.
361
- def untagged(&block)
362
- Utils.deprecated("Logger#untagged", "Lumberjack::Logger#untagged is deprecated and will be removed in version 2.1; use clear_attributes instead.") do
363
- clear_attributes(&block)
364
- end
365
- end
366
-
367
- # Alias for with_level for compatibility with ActiveSupport loggers. This functionality
368
- # has been moved to the lumberjack_rails gem.
369
- #
370
- # @see with_level
371
- # @deprecated This implementation is deprecated. Install the lumberjack_rails gem for full support.
372
- def log_at(level, &block)
373
- deprecation_message = "Install the lumberjack_rails gem for full support of the log_at method."
374
- Utils.deprecated("Logger#log_at", deprecation_message) do
375
- with_level(level, &block)
376
- end
377
- end
378
-
379
- # Alias for with_level for compatibilty with ActiveSupport loggers. This functionality
380
- # has been moved to the lumberjack_rails gem.
381
- #
382
- # @see with_level
383
- # @deprecated This implementation is deprecated. Install the lumberjack_rails gem for full support.
384
- def silence(level = Logger::ERROR, &block)
385
- deprecation_message = "Install the lumberjack_rails gem for full support of the silence method."
386
- Utils.deprecated("Logger#silence", deprecation_message) do
387
- with_level(level, &block)
388
- end
389
- end
390
-
391
224
  # Add an entry to the log.
392
225
  #
393
226
  # @param severity [Integer, Symbol, String] The severity of the message.
@@ -400,25 +233,30 @@ module Lumberjack
400
233
  return false unless device
401
234
 
402
235
  # Prevent infinite recursion if logging is attempted from within a logging call.
403
- if current_context_locals&.logging
236
+ # The guard is stored in fiber-local storage since recursion is a property of the
237
+ # current call stack, which belongs to exactly one fiber.
238
+ if Thread.current[@recursion_guard_key]
404
239
  log_to_stderr(severity, message)
405
240
  return false
406
241
  end
407
242
 
408
243
  severity = Severity.label_to_level(severity) unless severity.is_a?(Integer)
409
244
 
410
- new_context_locals do |locals|
411
- locals.logging = true # protection from infinite loops
245
+ begin
246
+ Thread.current[@recursion_guard_key] = true
412
247
 
248
+ locals = current_context_locals
413
249
  time = Time.now
414
- progname ||= self.progname
250
+ progname ||= locals&.context&.progname || default_context&.progname
415
251
  attributes = nil unless attributes.is_a?(Hash)
416
- attributes = merge_attributes(merge_all_attributes, attributes)
252
+ attributes = merge_all_attributes(locals, attributes)
417
253
  message, attributes = formatter.format(message, attributes) if formatter
418
254
 
419
255
  entry = Lumberjack::LogEntry.new(time, severity, message, progname, Process.pid, attributes)
420
256
 
421
257
  write_to_device(entry)
258
+ ensure
259
+ Thread.current[@recursion_guard_key] = nil
422
260
  end
423
261
 
424
262
  true
@@ -449,18 +287,14 @@ module Lumberjack
449
287
  raise e if Lumberjack.raise_logger_errors?
450
288
  end
451
289
 
452
- def build_entry_formatter(formatter, message_formatter, attribute_formatter) # :nodoc:
453
- entry_formatter = formatter if formatter.is_a?(Lumberjack::EntryFormatter)
454
-
455
- unless entry_formatter
456
- message_formatter ||= formatter if formatter.is_a?(Lumberjack::Formatter) || formatter == :default
457
- entry_formatter = Lumberjack::EntryFormatter.new
458
- end
290
+ def build_entry_formatter(formatter) # :nodoc:
291
+ return formatter if formatter.is_a?(Lumberjack::EntryFormatter)
459
292
 
460
- message_formatter = Lumberjack::Formatter.default if message_formatter == :default
293
+ entry_formatter = Lumberjack::EntryFormatter.new
461
294
 
295
+ message_formatter = formatter if formatter.is_a?(Lumberjack::Formatter)
296
+ message_formatter = Lumberjack::Formatter.default if formatter == :default
462
297
  entry_formatter.message_formatter = message_formatter if message_formatter
463
- entry_formatter.attribute_formatter = attribute_formatter if attribute_formatter
464
298
 
465
299
  entry_formatter
466
300
  end