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
@@ -3,6 +3,11 @@
3
3
  require "socket"
4
4
 
5
5
  module Lumberjack
6
+ # Error raised when a deprecated method is called and the deprecation mode is set to :raise.
7
+ class DeprecationError < StandardError
8
+ end
9
+
10
+ # Utils provides utility methods and helper functions used throughout the Lumberjack logging framework.
6
11
  module Utils
7
12
  UNDEFINED = Object.new.freeze
8
13
  private_constant :UNDEFINED
@@ -16,36 +21,73 @@ module Lumberjack
16
21
 
17
22
  class << self
18
23
  # Print warning when deprecated methods are called the first time. This can be disabled
19
- # by setting the environment variable `LUMBERJACK_NO_DEPRECATION_WARNINGS` to "true".
20
- # You can see every usage of a deprecated method along with a full stack trace by setting
21
- # the environment variable `VERBOSE_LUMBERJACK_DEPRECATION_WARNING` to "true".
24
+ # by setting +Lumberjack.deprecation_mode+ to +:silent+.
25
+ #
26
+ # In order to cut down on noise, each deprecated method will only print a warning once per process.
27
+ # You can change this by setting +Lumberjack.deprecation_mode+ to +:verbose+.
28
+ #
29
+ # @param method [String, Symbol] The name of the deprecated method.
30
+ # @param message [String] The deprecation message explaining what to use instead.
31
+ # @yield The block containing the deprecated functionality to execute.
32
+ # @return [Object] The result of the yielded block.
22
33
  #
23
- # @param method [String] The name of the deprecated method.
24
- # @param message [String] Optional message to include in the warning.
25
- # @yield The block to execute after the warning.
34
+ # @example
35
+ # def old_method
36
+ # Utils.deprecated(:old_method, "Use new_method instead.") do
37
+ # # deprecated implementation
38
+ # end
39
+ # end
26
40
  def deprecated(method, message)
27
- @deprecations_lock ||= Mutex.new
28
- unless @deprecations&.include?(method)
41
+ if Lumberjack.deprecation_mode != :silent && !@deprecations&.include?(method)
42
+ @deprecations_lock ||= Mutex.new
29
43
  @deprecations_lock.synchronize do
30
44
  @deprecations ||= {}
31
45
  unless @deprecations.include?(method)
32
- trace = caller[3..-1]
33
- unless ENV["VERBOSE_LUMBERJACK_DEPRECATION_WARNING"] == "true"
34
- trace = [trace.first]
46
+ trace = ($VERBOSE && Lumberjack.deprecation_mode != :raise) ? caller[3..] : caller[3, 1]
47
+ if trace.first.start_with?(__dir__) && !$VERBOSE
48
+ non_lumberjack_caller = caller[4..].detect { |line| !line.start_with?(__dir__) }
49
+ trace = [non_lumberjack_caller] if non_lumberjack_caller
50
+ end
51
+ message = "DEPRECATION WARNING: #{message} Called from #{trace.join(Lumberjack::LINE_SEPARATOR)}"
52
+
53
+ if Lumberjack.deprecation_mode == :raise
54
+ raise DeprecationError, message
55
+ end
56
+
57
+ unless Lumberjack.deprecation_mode == :verbose
35
58
  @deprecations[method] = true
36
59
  end
37
- message = "DEPRECATION WARNING: #{message} Called from #{trace.join("\n")}"
38
- warn(message) unless ENV["LUMBERJACK_NO_DEPRECATION_WARNINGS"] == "true"
60
+
61
+ warn(message)
39
62
  end
40
63
  end
41
64
  end
42
65
 
43
- yield
66
+ yield if block_given?
67
+ end
68
+
69
+ # Helper method for tests to silence deprecation warnings within a block. You should
70
+ # not use this in production code since it will silence all deprecation warnings
71
+ # globally across all threads.
72
+ #
73
+ # @param mode [Symbol, String] The deprecation mode to set within the block. Valid values are
74
+ # :normal, :verbose, :silent, and :raise.
75
+ # @yield The block in which to silence deprecation warnings.
76
+ # @return [Object] The result of the yielded block.
77
+ def with_deprecation_mode(mode)
78
+ save_mode = Lumberjack.deprecation_mode
79
+ begin
80
+ Lumberjack.deprecation_mode = mode
81
+ yield
82
+ ensure
83
+ Lumberjack.deprecation_mode = save_mode
84
+ end
44
85
  end
45
86
 
46
87
  # Get the hostname of the machine. The returned value will be in UTF-8 encoding.
88
+ # The hostname is cached after the first call for performance.
47
89
  #
48
- # @return [String] The hostname of the machine.
90
+ # @return [String] The hostname of the machine in UTF-8 encoding.
49
91
  def hostname
50
92
  if @hostname.equal?(UNDEFINED)
51
93
  @hostname = force_utf8(Socket.gethostname)
@@ -53,83 +95,179 @@ module Lumberjack
53
95
  @hostname
54
96
  end
55
97
 
56
- # Set the hostname to a specific value. If this is not specified, it will use the system hostname.
98
+ # Get the current line of code that calls this method. This is useful for debugging
99
+ # purposes to record the exact location in your code that generated a log entry.
100
+ #
101
+ # @param root_path [String, Pathname, nil] An optional root path to strip from the file path.
102
+ # @return [String] A string representation of the caller location (file:line:method).
103
+ #
104
+ # @example Adding source location to log entries
105
+ # logger.info("Something happened", source: Lumberjack::Utils.current_line)
106
+ # # Logs: "Something happened" with source: "/path/to/file.rb:123:in `method_name'"
107
+ def current_line(root_path = nil)
108
+ location = caller_locations(1, 1)[0]
109
+ path = location.path
110
+ if root_path
111
+ root_path = root_path.to_s
112
+ root_path = "#{root_path}#{File::SEPARATOR}" unless root_path.end_with?(File::SEPARATOR)
113
+ path = path.delete_prefix(root_path)
114
+ end
115
+ "#{path}:#{location.lineno}:in `#{location.label}'"
116
+ end
117
+
118
+ # Set the hostname to a specific value. This overrides the system hostname.
119
+ # Useful for testing or when you want to use a specific identifier.
57
120
  #
58
- # @param hostname [String]
121
+ # @param hostname [String] The hostname to use.
59
122
  # @return [void]
60
123
  def hostname=(hostname)
61
124
  @hostname = force_utf8(hostname)
62
125
  end
63
126
 
64
- # Generate a global process ID that includes the hostname and process ID.
127
+ # Generate a global process identifier that includes the hostname and process ID.
128
+ # This creates a unique identifier that can distinguish processes across different machines.
65
129
  #
66
- # @return [String] The global process ID.
67
- def global_pid
130
+ # @return [String] The global process ID in the format "hostname-pid".
131
+ #
132
+ # @example
133
+ # Lumberjack::Utils.global_pid
134
+ # # => "server1-12345"
135
+ def global_pid(pid = Process.pid)
68
136
  if hostname
69
- "#{hostname}-#{Process.pid}"
137
+ "#{hostname}-#{pid}"
70
138
  else
71
- Process.pid.to_s
139
+ pid.to_s
72
140
  end
73
141
  end
74
142
 
75
- # Generate a global thread ID that includes the global process ID and the thread name.
143
+ # Generate a global thread identifier that includes the global process ID and thread name.
144
+ # This creates a unique identifier for threads across processes and machines.
145
+ #
146
+ # @return [String] The global thread ID in the format "hostname-pid-threadname".
76
147
  #
77
- # @return [String] The global thread ID.
148
+ # @example
149
+ # Lumberjack::Utils.global_thread_id
150
+ # # => "server1-12345-main" or "server1-12345-worker-1"
78
151
  def global_thread_id
79
152
  "#{global_pid}-#{thread_name}"
80
153
  end
81
154
 
82
- # Get the name of a thread. The value will be based on the thread's name if it exists.
83
- # Otherwise a unique id is generated based on the thread's object id. Only alphanumeric
84
- # characters, underscores, dashes, and periods are kept in thread name.
155
+ # Get a safe name for a thread. Uses the thread's assigned name if available,
156
+ # otherwise generates a unique identifier based on the thread's object ID.
157
+ # Non-alphanumeric characters (except underscores, dashes, and periods) are replaced
158
+ # with dashes to create URL-safe identifiers.
85
159
  #
86
160
  # @param thread [Thread] The thread to get the name for. Defaults to the current thread.
87
- # @return [String] The name of the thread.
161
+ # @return [String] A safe string identifier for the thread.
162
+ #
163
+ # @example
164
+ # Thread.current.name = "worker-thread"
165
+ # Lumberjack::Utils.thread_name # => "worker-thread"
166
+ #
167
+ # # For unnamed threads
168
+ # Lumberjack::Utils.thread_name # => "2c001a80c" (based on object_id)
88
169
  def thread_name(thread = Thread.current)
89
170
  thread.name ? slugify(thread.name) : thread.object_id.to_s(36)
90
171
  end
91
172
 
92
- # Force encode a string to UTF-8. Any invalid byte sequences will be
93
- # ignored and replaced with an empty string.
173
+ # Force encode a string to UTF-8, handling invalid byte sequences gracefully.
174
+ # Any invalid or undefined byte sequences will be replaced with an empty string,
175
+ # ensuring the result is always valid UTF-8.
176
+ #
177
+ # @param str [String, nil] The string to encode. Returns nil if input is nil.
178
+ # @return [String, nil] The UTF-8 encoded string, or nil if input was nil.
94
179
  #
95
- # @param str [String] The string to encode.
96
- # @return [String] The UTF-8 encoded string.
180
+ # @example
181
+ # # Handles strings with invalid encoding
182
+ # bad_string = "Hello\xff\xfeWorld".force_encoding("ASCII-8BIT")
183
+ # Lumberjack::Utils.force_utf8(bad_string) # => "HelloWorld"
97
184
  def force_utf8(str)
98
185
  return nil if str.nil?
99
186
 
100
187
  str.dup.force_encoding("ASCII-8BIT").encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
101
188
  end
102
189
 
103
- # Flatten a tag hash to a single level hash with dot notation for nested keys.
190
+ # Flatten a nested attribute hash into a single-level hash using dot notation for nested keys.
191
+ # This is useful for converting structured data into a flat format suitable for logging systems
192
+ # that don't support nested structures.
193
+ #
194
+ # @param attr_hash [Hash] The hash to flatten. Non-hash values are ignored.
195
+ # @return [Hash<String, Object>] A flattened hash with dot-notation keys.
196
+ #
197
+ # @example Basic flattening
198
+ # hash = {user: {id: 123, profile: {name: "Alice"}}, action: "login"}
199
+ # Lumberjack::Utils.flatten_attributes(hash)
200
+ # # => {"user.id" => 123, "user.profile.name" => "Alice", "action" => "login"}
201
+ #
202
+ # @example With mixed types
203
+ # hash = {config: {db: {host: "localhost", port: 5432}}, debug: true}
204
+ # Lumberjack::Utils.flatten_attributes(hash)
205
+ # # => {"config.db.host" => "localhost", "config.db.port" => 5432, "debug" => true}
206
+ def flatten_attributes(attr_hash)
207
+ return {} unless attr_hash.is_a?(Hash)
208
+
209
+ flatten_hash_recursive(attr_hash)
210
+ end
211
+
212
+ # Alias for {.flatten_attributes} to provide compatibility with the 1.x API.
213
+ # This method will eventually be removed in a future version.
104
214
  #
105
215
  # @param tag_hash [Hash] The hash to flatten.
106
216
  # @return [Hash<String, Object>] The flattened hash.
107
- # @example
108
- # expand_tags(user: {id: 123, name: "Alice"}, action: "login")})
109
- # # => {"user.id" => 123, "user.name" => "Alice", "action" => "login"}
217
+ # @deprecated Use {.flatten_attributes} instead.
110
218
  def flatten_tags(tag_hash)
111
- return {} unless tag_hash.is_a?(Hash)
112
-
113
- flatten_hash_recursive(tag_hash)
219
+ Utils.deprecated("Lumberjack::Utils.flatten_tags", "Lumberjack::Utils.flatten_tags is deprecated and will be removed in version 2.1; use flatten_attributes instead.") do
220
+ flatten_attributes(tag_hash)
221
+ end
114
222
  end
115
223
 
116
- # Expand a hash of tags that may contain nested hashes or dot notation keys. Dot notation tags
117
- # will be expanded into nested hashes.
224
+ # Expand a hash containing dot notation keys into a nested hash structure.
225
+ # This is the inverse operation of {.flatten_attributes} and is useful for converting
226
+ # flat attribute structures back into nested hashes.
118
227
  #
119
- # @param tags [Hash] The hash of tags to expand.
120
- # @return [Hash] The expanded hash with dot notation keys.
228
+ # @param attributes [Hash] The hash with dot notation keys to expand. Non-hash values are ignored.
229
+ # @return [Hash] A nested hash with dot notation keys expanded into nested structures.
121
230
  #
122
- # @example
123
- # expand_tags({"user.id" => 123, "user.name" => "Alice", "action" => "login"})
231
+ # @example Basic expansion
232
+ # flat = {"user.id" => 123, "user.name" => "Alice", "action" => "login"}
233
+ # Lumberjack::Utils.expand_attributes(flat)
124
234
  # # => {"user" => {"id" => 123, "name" => "Alice"}, "action" => "login"}
125
- def expand_tags(tags)
126
- return {} unless tags.is_a?(Hash)
235
+ #
236
+ # @example Deep nesting
237
+ # flat = {"app.db.host" => "localhost", "app.db.port" => 5432, "app.debug" => true}
238
+ # Lumberjack::Utils.expand_attributes(flat)
239
+ # # => {"app" => {"db" => {"host" => "localhost", "port" => 5432}, "debug" => true}}
240
+ #
241
+ # @example Mixed with existing nested structures
242
+ # mixed = {"user.id" => 123, "settings" => {"theme" => "dark"}}
243
+ # Lumberjack::Utils.expand_attributes(mixed)
244
+ # # => {"user" => {"id" => 123}, "settings" => {"theme" => "dark"}}
245
+ def expand_attributes(attributes)
246
+ return {} unless attributes.is_a?(Hash)
127
247
 
128
- expand_dot_notation_hash(tags)
248
+ expand_dot_notation_hash(attributes)
249
+ end
250
+
251
+ # Alias for {.expand_attributes} to provide compatibility with the 1.x API.
252
+ # This method will eventually be removed in a future version.
253
+ #
254
+ # @param tags [Hash] The hash to expand.
255
+ # @return [Hash] The expanded hash.
256
+ # @deprecated Use {.expand_attributes} instead.
257
+ def expand_tags(tags)
258
+ Utils.deprecated("Lumberjack::Utils.expand_tags", "Lumberjack::Utils.expand_tags is deprecated and will be removed in version 2.1; use expand_attributes instead.") do
259
+ expand_attributes(tags)
260
+ end
129
261
  end
130
262
 
131
263
  private
132
264
 
265
+ # Recursively flatten a hash, building dot notation keys for nested structures.
266
+ #
267
+ # @param hash [Hash] The hash to flatten.
268
+ # @param prefix [String, nil] The current key prefix for nested structures.
269
+ # @return [Hash<String, Object>] The flattened hash.
270
+ # @api private
133
271
  def flatten_hash_recursive(hash, prefix = nil)
134
272
  hash.each_with_object({}) do |(key, value), result|
135
273
  full_key = prefix ? "#{prefix}.#{key}" : key.to_s
@@ -141,6 +279,12 @@ module Lumberjack
141
279
  end
142
280
  end
143
281
 
282
+ # Convert a string to a URL-safe slug by replacing non-alphanumeric characters
283
+ # (except underscores, dashes, and periods) with dashes, and removing leading/trailing dashes.
284
+ #
285
+ # @param str [String, nil] The string to slugify.
286
+ # @return [String, nil] The slugified string, or nil if input was nil.
287
+ # @api private
144
288
  def slugify(str)
145
289
  return nil if str.nil?
146
290
 
@@ -150,6 +294,12 @@ module Lumberjack
150
294
  str
151
295
  end
152
296
 
297
+ # Recursively expand dot notation keys in a hash into nested structures.
298
+ #
299
+ # @param hash [Hash] The hash containing dot notation keys to expand.
300
+ # @param expanded [Hash] The target hash to store expanded results.
301
+ # @return [Hash] The expanded hash with nested structures.
302
+ # @api private
153
303
  def expand_dot_notation_hash(hash, expanded = {})
154
304
  return hash unless hash.is_a?(Hash)
155
305
 
data/lib/lumberjack.rb CHANGED
@@ -2,117 +2,225 @@
2
2
 
3
3
  require "rbconfig"
4
4
  require "time"
5
- require "securerandom"
6
5
  require "logger"
7
6
  require "fiber"
7
+ require "pathname"
8
8
 
9
+ # Lumberjack is a flexible logging framework for Ruby that extends the standard
10
+ # Logger functionality with structured logging, context isolation, and advanced
11
+ # formatting capabilities.
12
+ #
13
+ # The main features include:
14
+ # - Structured logging with attributes for machine-readable metadata
15
+ # - Context isolation for scoping logging behavior to specific code blocks
16
+ # - Flexible formatters for customizing log output
17
+ # - Multiple output devices and templates
18
+ # - Built-in testing utilities
19
+ #
20
+ # @example Basic usage
21
+ # logger = Lumberjack::Logger.new(STDOUT)
22
+ # logger.info("Hello world")
23
+ #
24
+ # @example Using contexts
25
+ # Lumberjack.context do
26
+ # Lumberjack.tag(user_id: 123)
27
+ # logger.info("User action") # Will include user_id: 123
28
+ # end
29
+ #
30
+ # @see Lumberjack::Logger
31
+ # @see Lumberjack::ContextLogger
9
32
  module Lumberjack
33
+ VERSION = File.read(File.join(__dir__, "..", "VERSION")).strip.freeze
34
+
10
35
  LINE_SEPARATOR = ((RbConfig::CONFIG["host_os"] =~ /mswin/i) ? "\r\n" : "\n")
11
36
 
12
- require_relative "lumberjack/severity"
13
- require_relative "lumberjack/formatter"
37
+ require_relative "lumberjack/device_registry"
38
+ require_relative "lumberjack/template_registry"
39
+ require_relative "lumberjack/formatter_registry"
14
40
 
41
+ require_relative "lumberjack/attribute_formatter"
42
+ require_relative "lumberjack/attributes_helper"
15
43
  require_relative "lumberjack/context"
44
+ require_relative "lumberjack/context_logger"
45
+ require_relative "lumberjack/fiber_locals"
46
+ require_relative "lumberjack/io_compatibility"
16
47
  require_relative "lumberjack/log_entry"
48
+ require_relative "lumberjack/log_entry_matcher"
17
49
  require_relative "lumberjack/device"
50
+ require_relative "lumberjack/entry_formatter"
51
+ require_relative "lumberjack/formatter"
52
+ require_relative "lumberjack/forked_logger"
18
53
  require_relative "lumberjack/logger"
19
- require_relative "lumberjack/tags"
20
- require_relative "lumberjack/tag_context"
21
- require_relative "lumberjack/tag_formatter"
22
- require_relative "lumberjack/tagged_logger_support"
23
- require_relative "lumberjack/tagged_logging"
24
- require_relative "lumberjack/template"
54
+ require_relative "lumberjack/local_log_template"
55
+ require_relative "lumberjack/message_attributes"
56
+ require_relative "lumberjack/remap_attribute"
25
57
  require_relative "lumberjack/rack"
58
+ require_relative "lumberjack/severity"
59
+ require_relative "lumberjack/template"
26
60
  require_relative "lumberjack/utils"
27
61
 
28
- class << self
29
- # Define a unit of work within a block. Within the block supplied to this
30
- # method, calling +unit_of_work_id+ will return the same value that can
31
- # This can then be used for tying together log entries.
32
- #
33
- # You can specify the id for the unit of work if desired. If you don't supply
34
- # it, a 12 digit hexidecimal number will be automatically generated for you.
35
- #
36
- # For the common use case of treating a single web request as a unit of work, see the
37
- # Lumberjack::Rack::UnitOfWork class.
38
- #
39
- # @param id [String] The id for the unit of work.
40
- # @return [void]
41
- # @deprecated Use tags instead. This will be removed in version 2.0.
42
- def unit_of_work(id = nil)
43
- Lumberjack::Utils.deprecated("Lumberjack.unit_of_work", "Lumberjack.unit_of_work will be removed in version 2.0. Use Lumberjack::Logger#tag(unit_of_work: id) instead.") do
44
- id ||= SecureRandom.hex(6)
45
- context do
46
- context[:unit_of_work_id] = id
47
- yield
48
- end
49
- end
50
- end
62
+ # Deprecated
63
+ require_relative "lumberjack/tag_context"
64
+ require_relative "lumberjack/tag_formatter"
65
+ require_relative "lumberjack/tags"
51
66
 
52
- # Get the UniqueIdentifier for the current unit of work.
53
- #
54
- # @return [String, nil] The id for the current unit of work.
55
- # @deprecated Use tags instead. This will be removed in version 2.0.
56
- def unit_of_work_id
57
- context[:unit_of_work_id]
58
- end
67
+ @global_contexts = {}
68
+ @global_contexts_mutex = Mutex.new
69
+ @deprecation_mode = nil
70
+ @raise_logger_errors = false
59
71
 
60
- # Contexts can be used to store tags that will be attached to all log entries in the block.
72
+ class << self
73
+ # Contexts can be used to store attributes that will be attached to all log entries in the block.
61
74
  # The context will apply to all Lumberjack loggers that are used within the block.
62
75
  #
63
76
  # If this method is called with a block, it will set a logging context for the scope of a block.
64
77
  # If there is already a context in scope, a new one will be created that inherits
65
- # all the tags of the parent context.
78
+ # all the attributes of the parent context.
66
79
  #
67
80
  # Otherwise, it will return the current context. If one doesn't exist, it will return a new one
68
81
  # but that context will not be in any scope.
69
82
  #
70
- # @return [Lumberjack::Context] The current context if called without a block.
83
+ # @return [Object] The result
84
+ # of the block
71
85
  def context(&block)
72
- current_context = Thread.current[:lumberjack_context]
73
- if block
74
- use_context(Context.new(current_context), &block)
86
+ use_context(Context.new(current_context), &block)
87
+ end
88
+
89
+ # Ensure that the block of code is wrapped by a global context. If there is not already
90
+ # a context in scope, one will be created.
91
+ #
92
+ # @return [Object] The result of the block.
93
+ def ensure_context(&block)
94
+ if in_context?
95
+ yield
75
96
  else
76
- current_context || Context.new
97
+ context(&block)
77
98
  end
78
99
  end
79
100
 
80
101
  # Set the context to use within a block.
81
102
  #
82
- # @param [Lumberjack::Context] context The context to use within the block.
103
+ # @param context [Lumberjack::Context] The context to use within the block.
83
104
  # @return [Object] The result of the block.
105
+ # @api private
84
106
  def use_context(context, &block)
85
- current_context = Thread.current[:lumberjack_context]
107
+ fiber_id = Fiber.current.object_id
108
+ ctx = @global_contexts[fiber_id]
86
109
  begin
87
- Thread.current[:lumberjack_context] = (context || Context.new)
110
+ @global_contexts_mutex.synchronize do
111
+ @global_contexts[fiber_id] = (context || Context.new)
112
+ end
88
113
  yield
89
114
  ensure
90
- Thread.current[:lumberjack_context] = current_context
115
+ @global_contexts_mutex.synchronize do
116
+ if ctx.nil?
117
+ @global_contexts.delete(fiber_id)
118
+ else
119
+ @global_contexts[fiber_id] = ctx
120
+ end
121
+ end
91
122
  end
92
123
  end
93
124
 
94
125
  # Return true if inside a context block.
95
126
  #
96
127
  # @return [Boolean]
128
+ def in_context?
129
+ !!@global_contexts[Fiber.current.object_id]
130
+ end
131
+
97
132
  def context?
98
- !!Thread.current[:lumberjack_context]
133
+ Utils.deprecated("Lumberjack.context?", "Lumberjack.context? is deprecated and will be removed in version 2.1; use in_context? instead.") do
134
+ in_context?
135
+ end
136
+ end
137
+
138
+ # Return attributes that will be applied to all Lumberjack loggers.
139
+ #
140
+ # @return [Hash, nil]
141
+ def context_attributes
142
+ current_context&.attributes
99
143
  end
100
144
 
101
- # Return the tags from the current context or nil if there are no tags.
145
+ # Alias for context_attributes to provide API compatibility with version 1.x.
146
+ # This method will eventually be removed.
102
147
  #
103
148
  # @return [Hash, nil]
149
+ # @deprecated Use {.context_attributes}
104
150
  def context_tags
105
- context = Thread.current[:lumberjack_context]
106
- context&.tags
151
+ Utils.deprecated("Lumberjack.context_tags", "Lumberjack.context_tags is deprecated and will be removed in version 2.1; use context_attributes instead.") do
152
+ context_attributes
153
+ end
154
+ end
155
+
156
+ # Tag all loggers with attributes on the current context.
157
+ #
158
+ # @param attributes [Hash] The attributes to set.
159
+ # @param block [Proc] optional context block in which to set the attributes.
160
+ # @return [void]
161
+ def tag(attributes, &block)
162
+ if block
163
+ context do
164
+ current_context.assign_attributes(attributes)
165
+ block.call
166
+ end
167
+ else
168
+ current_context&.assign_attributes(attributes)
169
+ end
170
+ end
171
+
172
+ # Helper method to build an entry formatter.
173
+ #
174
+ # @param block [Proc] The block to use for building the entry formatter.
175
+ # @return [Lumberjack::EntryFormatter] The built entry formatter.
176
+ # @see Lumberjack::EntryFormatter.build
177
+ def build_formatter(&block)
178
+ EntryFormatter.build(&block)
179
+ end
180
+
181
+ # Control how use of deprecated methods is handled. The default is to print a warning
182
+ # the first time a deprecated method is called. Setting this to :verbose will print
183
+ # a warning every time a deprecated method is called. Setting this to :silent will
184
+ # suppress all deprecation warnings. Setting this to :raise will raise an exception
185
+ # when a deprecated method is called.
186
+ #
187
+ # The default value can be set with the +LUMBERJACK_DEPRECATION_WARNINGS+ environment variable.
188
+ #
189
+ # @param value [Symbol, String, nil] The deprecation mode to set. Valid values are :normal,
190
+ # :verbose, :silent, and :raise.
191
+ def deprecation_mode=(value)
192
+ @deprecation_mode = value&.to_sym
107
193
  end
108
194
 
109
- # Set tags on the current context
195
+ # @return [Symbol] The current deprecation mode.
196
+ # @api private
197
+ def deprecation_mode
198
+ @deprecation_mode ||= ENV.fetch("LUMBERJACK_DEPRECATION_WARNINGS", "normal").to_sym
199
+ end
200
+
201
+ # Set whether errors encountered while logging entries should be raised. The default behavior
202
+ # is to rescue these errors and print them to standard error. Otherwise there can be no way
203
+ # to record the error since it cannot be logged.
110
204
  #
111
- # @param [Hash] tags The tags to set.
205
+ # You can set this to true in you test and development environments to catch logging errors
206
+ # before they make it to production.
207
+ #
208
+ # @param value [Boolean] Whether to raise logger errors.
112
209
  # @return [void]
113
- def tag(tags)
114
- context = Thread.current[:lumberjack_context]
115
- context&.tag(tags)
210
+ def raise_logger_errors=(value)
211
+ @raise_logger_errors = !!value
212
+ end
213
+
214
+ # @return [Boolean] Whether logger errors should be raised.
215
+ # @api private
216
+ def raise_logger_errors?
217
+ @raise_logger_errors
218
+ end
219
+
220
+ private
221
+
222
+ def current_context
223
+ @global_contexts[Fiber.current.object_id]
116
224
  end
117
225
  end
118
226
  end
data/lumberjack.gemspec CHANGED
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
4
4
  spec.authors = ["Brian Durand"]
5
5
  spec.email = ["bbdurand@gmail.com"]
6
6
 
7
- spec.summary = "A simple, powerful, and fast logging utility with excellent structured logging support that can be a drop in replacement for the standard library Logger."
7
+ spec.summary = "Extension of Ruby’s standard Logger for advanced, structured logging. Includes log entry attributes, context isolation, customizable formatters, flexible output devices, and testing tools."
8
8
  spec.homepage = "https://github.com/bdurand/lumberjack"
9
9
  spec.license = "MIT"
10
10
 
@@ -31,7 +31,9 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.require_paths = ["lib"]
33
33
 
34
- spec.required_ruby_version = ">= 2.5.0"
34
+ spec.required_ruby_version = ">= 2.7"
35
+
36
+ spec.add_runtime_dependency "logger"
35
37
 
36
38
  spec.add_development_dependency "bundler"
37
39
  end