lumberjack 1.2.9 → 1.3.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +244 -0
  3. data/CHANGELOG.md +78 -2
  4. data/README.md +176 -58
  5. data/VERSION +1 -1
  6. data/lib/lumberjack/context.rb +5 -5
  7. data/lib/lumberjack/device/date_rolling_log_file.rb +1 -1
  8. data/lib/lumberjack/device/log_file.rb +1 -1
  9. data/lib/lumberjack/device/multi.rb +1 -1
  10. data/lib/lumberjack/device/null.rb +1 -1
  11. data/lib/lumberjack/device/rolling_log_file.rb +2 -2
  12. data/lib/lumberjack/device/size_rolling_log_file.rb +1 -1
  13. data/lib/lumberjack/device/writer.rb +13 -9
  14. data/lib/lumberjack/device.rb +1 -1
  15. data/lib/lumberjack/formatter/date_time_formatter.rb +2 -2
  16. data/lib/lumberjack/formatter/exception_formatter.rb +2 -2
  17. data/lib/lumberjack/formatter/id_formatter.rb +1 -1
  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 +1 -1
  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 +1 -1
  27. data/lib/lumberjack/formatter/tagged_message.rb +39 -0
  28. data/lib/lumberjack/formatter/truncate_formatter.rb +1 -1
  29. data/lib/lumberjack/formatter.rb +29 -14
  30. data/lib/lumberjack/log_entry.rb +25 -15
  31. data/lib/lumberjack/logger.rb +132 -40
  32. data/lib/lumberjack/rack/context.rb +21 -2
  33. data/lib/lumberjack/rack/request_id.rb +7 -3
  34. data/lib/lumberjack/rack/unit_of_work.rb +6 -2
  35. data/lib/lumberjack/rack.rb +1 -1
  36. data/lib/lumberjack/severity.rb +13 -1
  37. data/lib/lumberjack/tag_formatter.rb +102 -27
  38. data/lib/lumberjack/tagged_logger_support.rb +6 -1
  39. data/lib/lumberjack/tags.rb +1 -7
  40. data/lib/lumberjack/template.rb +3 -3
  41. data/lib/lumberjack/utils.rb +133 -0
  42. data/lib/lumberjack.rb +12 -6
  43. data/lumberjack.gemspec +2 -2
  44. metadata +12 -6
@@ -11,13 +11,14 @@ module Lumberjack
11
11
  class TagFormatter
12
12
  def initialize
13
13
  @formatters = {}
14
+ @class_formatters = {}
14
15
  @default_formatter = nil
15
16
  end
16
17
 
17
18
  # Add a default formatter applied to all tag values. This can either be a Lumberjack::Formatter
18
19
  # or an object that responds to `call` or a block.
19
20
  #
20
- # @param [Lumberjack::Formatter, #call, nil] formatter The formatter to use.
21
+ # @param formatter [Lumberjack::Formatter, #call, nil] The formatter to use.
21
22
  # If this is nil, then the block will be used as the formatter.
22
23
  # @return [Lumberjack::TagFormatter] self
23
24
  def default(formatter = nil, &block)
@@ -35,22 +36,37 @@ module Lumberjack
35
36
  self
36
37
  end
37
38
 
38
- # Add a formatter for specific tag names. This can either be a Lumberjack::Formatter
39
- # or an object that responds to `call` or a block. The default formatter will not be
40
- # applied.
39
+ # Add a formatter for specific tag names or object classes. This can either be a Lumberjack::Formatter
40
+ # or an object that responds to `call` or a block. The formatter will be applied if it matches either a tag name
41
+ # or if the tag value is an instance of a registered class. Tag name formatters will take precedence
42
+ # over class formatters. The default formatter will not be applied to a value if a tag formatter
43
+ # is applied to it.
41
44
  #
42
- # @param [String, Array<String>] names The tag names to apply the formatter to.
43
- # @param [Lumberjack::Formatter, #call, nil] formatter The formatter to use.
45
+ # Name formatters can be applied to nested hashes using dot syntax. For example, if you add a formatter
46
+ # for "foo.bar", it will be applied to the value of the "bar" key in the "foo" tag if that value is a hash.
47
+ #
48
+ # Class formatters will be applied recursively to nested hashes and arrays.
49
+ #
50
+ # @param names_or_classes [String, Module, Array<String, Module>] The tag names or object classes
51
+ # to apply the formatter to.
52
+ # @param formatter [Lumberjack::Formatter, #call, nil] The formatter to use.
44
53
  # If this is nil, then the block will be used as the formatter.
45
54
  # @return [Lumberjack::TagFormatter] self
46
- def add(names, formatter = nil, &block)
55
+ #
56
+ # @example
57
+ # tag_formatter.add("password", &:redact)
58
+ def add(names_or_classes, formatter = nil, &block)
47
59
  formatter ||= block
48
60
  formatter = dereference_formatter(formatter)
49
61
  if formatter.nil?
50
62
  remove(key)
51
63
  else
52
- Array(names).each do |name|
53
- @formatters[name.to_s] = formatter
64
+ Array(names_or_classes).each do |key|
65
+ if key.is_a?(Module)
66
+ @class_formatters[key] = formatter
67
+ else
68
+ @formatters[key.to_s] = formatter
69
+ end
54
70
  end
55
71
  end
56
72
  self
@@ -58,11 +74,15 @@ module Lumberjack
58
74
 
59
75
  # Remove formatters for specific tag names. The default formatter will still be applied.
60
76
  #
61
- # @param [String, Array<String>] names The tag names to remove the formatter from.
77
+ # @param names_or_classes [String, Module, Array<String, Module>] The tag names or classes to remove the formatter from.
62
78
  # @return [Lumberjack::TagFormatter] self
63
- def remove(names)
64
- Array(names).each do |name|
65
- @formatters.delete(name.to_s)
79
+ def remove(names_or_classes)
80
+ Array(names_or_classes).each do |key|
81
+ if key.is_a?(Module)
82
+ @class_formatters.delete(key)
83
+ else
84
+ @formatters.delete(key.to_s)
85
+ end
66
86
  end
67
87
  self
68
88
  end
@@ -78,28 +98,66 @@ module Lumberjack
78
98
 
79
99
  # Format a hash of tags using the formatters
80
100
  #
81
- # @param [Hash] tags The tags to format.
101
+ # @param tags [Hash] The tags to format.
82
102
  # @return [Hash] The formatted tags.
83
103
  def format(tags)
84
104
  return nil if tags.nil?
85
- if @default_formatter.nil? && (@formatters.empty? || (@formatters.keys & tags.keys).empty?)
86
- tags
105
+ if @default_formatter.nil? && @formatters.empty? && @class_formatters.empty?
106
+ return tags
107
+ end
108
+
109
+ formated_tags(tags)
110
+ end
111
+
112
+ private
113
+
114
+ def formated_tags(tags, skip_classes: nil, prefix: nil)
115
+ formatted = {}
116
+
117
+ tags.each do |name, value|
118
+ name = name.to_s
119
+ formatted[name] = formatted_tag_value(name, value, skip_classes: skip_classes, prefix: prefix)
120
+ end
121
+
122
+ formatted
123
+ end
124
+
125
+ def formatted_tag_value(name, value, skip_classes: nil, prefix: nil)
126
+ prefixed_name = prefix ? "#{prefix}#{name}" : name
127
+ using_class_formatter = false
128
+
129
+ formatter = @formatters[prefixed_name]
130
+ if formatter.nil? && (skip_classes.nil? || !skip_classes.include?(value.class))
131
+ formatter = class_formatter(value.class)
132
+ using_class_formatter = true if formatter
133
+ end
134
+
135
+ formatter ||= @default_formatter
136
+
137
+ formatted_value = if formatter.is_a?(Lumberjack::Formatter)
138
+ formatter.format(value)
139
+ elsif formatter.respond_to?(:call)
140
+ formatter.call(value)
87
141
  else
88
- formatted = {}
89
- tags.each do |name, value|
90
- formatter = (@formatters[name.to_s] || @default_formatter)
91
- if formatter.is_a?(Lumberjack::Formatter)
92
- value = formatter.format(value)
93
- elsif formatter.respond_to?(:call)
94
- value = formatter.call(value)
142
+ value
143
+ end
144
+
145
+ if formatted_value.is_a?(Enumerable)
146
+ skip_classes ||= []
147
+ skip_classes << value.class if using_class_formatter
148
+ sub_prefix = "#{prefixed_name}."
149
+
150
+ formatted_value = if formatted_value.is_a?(Hash)
151
+ formated_tags(formatted_value, skip_classes: skip_classes, prefix: sub_prefix)
152
+ else
153
+ formatted_value.collect do |item|
154
+ formatted_tag_value(nil, item, skip_classes: skip_classes, prefix: sub_prefix)
95
155
  end
96
- formatted[name.to_s] = value
97
156
  end
98
- formatted
99
157
  end
100
- end
101
158
 
102
- private
159
+ formatted_value
160
+ end
103
161
 
104
162
  def dereference_formatter(formatter)
105
163
  if formatter.is_a?(TaggedLoggerSupport::Formatter)
@@ -111,5 +169,22 @@ module Lumberjack
111
169
  formatter
112
170
  end
113
171
  end
172
+
173
+ def class_formatter(klass)
174
+ formatter = @class_formatters[klass]
175
+ return formatter if formatter
176
+
177
+ formatters = @class_formatters.select { |k, _| klass <= k }
178
+ return formatters.values.first if formatters.length <= 1
179
+
180
+ superclass = klass.superclass
181
+ while superclass
182
+ formatter = formatters[superclass]
183
+ return formatter if formatter
184
+ superclass = superclass.superclass
185
+ end
186
+
187
+ formatters.values.first
188
+ end
114
189
  end
115
190
  end
@@ -46,7 +46,12 @@ module Lumberjack
46
46
  tagged_values = Array(tag_hash["tagged"] || self.tags["tagged"])
47
47
  tag_hash["tagged"] = tagged_values + [tag]
48
48
  end
49
- tag(tag_hash, &block)
49
+
50
+ if block || in_tag_context?
51
+ tag(tag_hash, &block)
52
+ else
53
+ tag_globally(tag_hash)
54
+ end
50
55
  end
51
56
 
52
57
  def push_tags(*tags)
@@ -12,14 +12,8 @@ module Lumberjack
12
12
  return nil if hash.nil?
13
13
  if hash.keys.all? { |key| key.is_a?(String) }
14
14
  hash
15
- elsif hash.respond_to?(:transform_keys)
16
- hash.transform_keys(&:to_s)
17
15
  else
18
- copy = {}
19
- hash.each do |key, value|
20
- copy[key.to_s] = value
21
- end
22
- copy
16
+ hash.transform_keys(&:to_s)
23
17
  end
24
18
  end
25
19
 
@@ -1,4 +1,4 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  # A template converts entries to strings. Templates can contain the following place holders to
@@ -98,8 +98,8 @@ module Lumberjack
98
98
  def tag_args(tags, tag_vars)
99
99
  return [nil] * (tag_vars.size + 1) if tags.nil? || tags.size == 0
100
100
 
101
- tags_string = ""
102
- tags.each do |name, value|
101
+ tags_string = +""
102
+ Lumberjack::Utils.flatten_tags(tags).each do |name, value|
103
103
  unless value.nil? || tag_vars.include?(name)
104
104
  value = value.to_s
105
105
  value = value.gsub(Lumberjack::LINE_SEPARATOR, " ") if value.include?(Lumberjack::LINE_SEPARATOR)
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Lumberjack
6
+ module Utils
7
+ UNDEFINED = Object.new.freeze
8
+ private_constant :UNDEFINED
9
+
10
+ NON_SLUGGABLE_PATTERN = /[^A-Za-z0-9_.-]+/.freeze
11
+ private_constant :NON_SLUGGABLE_PATTERN
12
+
13
+ @deprecations = nil
14
+ @deprecations_lock = nil
15
+ @hostname = UNDEFINED
16
+
17
+ class << self
18
+ # 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".
22
+ #
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.
26
+ def deprecated(method, message)
27
+ @deprecations_lock ||= Mutex.new
28
+ unless @deprecations&.include?(method)
29
+ @deprecations_lock.synchronize do
30
+ @deprecations ||= {}
31
+ unless @deprecations.include?(method)
32
+ trace = caller[3..-1]
33
+ unless ENV["VERBOSE_LUMBERJACK_DEPRECATION_WARNING"] == "true"
34
+ trace = [trace.first]
35
+ @deprecations[method] = true
36
+ end
37
+ message = "DEPRECATION WARNING: #{message} Called from #{trace.join("\n")}"
38
+ warn(message) unless ENV["LUMBERJACK_NO_DEPRECATION_WARNINGS"] == "true"
39
+ end
40
+ end
41
+ end
42
+
43
+ yield
44
+ end
45
+
46
+ # Get the hostname of the machine. The returned value will be in UTF-8 encoding.
47
+ #
48
+ # @return [String] The hostname of the machine.
49
+ def hostname
50
+ if @hostname.equal?(UNDEFINED)
51
+ @hostname = force_utf8(Socket.gethostname)
52
+ end
53
+ @hostname
54
+ end
55
+
56
+ # Set the hostname to a specific value. If this is not specified, it will use the system hostname.
57
+ #
58
+ # @param hostname [String]
59
+ # @return [void]
60
+ def hostname=(hostname)
61
+ @hostname = force_utf8(hostname)
62
+ end
63
+
64
+ # Generate a global process ID that includes the hostname and process ID.
65
+ #
66
+ # @return [String] The global process ID.
67
+ def global_pid
68
+ if hostname
69
+ "#{hostname}-#{Process.pid}"
70
+ else
71
+ Process.pid.to_s
72
+ end
73
+ end
74
+
75
+ # Generate a global thread ID that includes the global process ID and the thread name.
76
+ #
77
+ # @return [String] The global thread ID.
78
+ def global_thread_id
79
+ "#{global_pid}-#{thread_name}"
80
+ end
81
+
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.
85
+ #
86
+ # @param thread [Thread] The thread to get the name for. Defaults to the current thread.
87
+ # @return [String] The name of the thread.
88
+ def thread_name(thread = Thread.current)
89
+ thread.name ? slugify(thread.name) : thread.object_id.to_s(36)
90
+ end
91
+
92
+ # Force encode a string to UTF-8. Any invalid byte sequences will be
93
+ # ignored and replaced with an empty string.
94
+ #
95
+ # @param str [String] The string to encode.
96
+ # @return [String] The UTF-8 encoded string.
97
+ def force_utf8(str)
98
+ return nil if str.nil?
99
+
100
+ str.dup.force_encoding("ASCII-8BIT").encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
101
+ end
102
+
103
+ # Flatten a tag hash to a single level hash with dot notation for nested keys.
104
+ #
105
+ # @param tag_hash [Hash] The hash to flatten.
106
+ # @return [Hash] The flattened hash.
107
+ def flatten_tags(tag_hash)
108
+ return {} unless tag_hash.is_a?(Hash)
109
+
110
+ tag_hash.each_with_object({}) do |(key, value), result|
111
+ if value.is_a?(Hash)
112
+ value.each do |sub_key, sub_value|
113
+ result["#{key}.#{sub_key}"] = sub_value
114
+ end
115
+ else
116
+ result[key] = value
117
+ end
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def slugify(str)
124
+ return nil if str.nil?
125
+
126
+ str = str.gsub(NON_SLUGGABLE_PATTERN, "-")
127
+ str.delete_prefix!("-")
128
+ str.chomp!("-")
129
+ str
130
+ end
131
+ end
132
+ end
133
+ end
data/lib/lumberjack.rb CHANGED
@@ -1,9 +1,10 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require "rbconfig"
4
4
  require "time"
5
5
  require "securerandom"
6
6
  require "logger"
7
+ require "fiber"
7
8
 
8
9
  module Lumberjack
9
10
  LINE_SEPARATOR = ((RbConfig::CONFIG["host_os"] =~ /mswin/i) ? "\r\n" : "\n")
@@ -21,6 +22,7 @@ module Lumberjack
21
22
  require_relative "lumberjack/tagged_logging"
22
23
  require_relative "lumberjack/template"
23
24
  require_relative "lumberjack/rack"
25
+ require_relative "lumberjack/utils"
24
26
 
25
27
  class << self
26
28
  # Define a unit of work within a block. Within the block supplied to this
@@ -33,19 +35,23 @@ module Lumberjack
33
35
  # For the common use case of treating a single web request as a unit of work, see the
34
36
  # Lumberjack::Rack::UnitOfWork class.
35
37
  #
36
- # @param [String] id The id for the unit of work.
38
+ # @param id [String] The id for the unit of work.
37
39
  # @return [void]
40
+ # @deprecated Use tags instead. This will be removed in version 2.0.
38
41
  def unit_of_work(id = nil)
39
- id ||= SecureRandom.hex(6)
40
- context do
41
- context[:unit_of_work_id] = id
42
- yield
42
+ 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
43
+ id ||= SecureRandom.hex(6)
44
+ context do
45
+ context[:unit_of_work_id] = id
46
+ yield
47
+ end
43
48
  end
44
49
  end
45
50
 
46
51
  # Get the UniqueIdentifier for the current unit of work.
47
52
  #
48
53
  # @return [String, nil] The id for the current unit of work.
54
+ # @deprecated Use tags instead. This will be removed in version 2.0.
49
55
  def unit_of_work_id
50
56
  context[:unit_of_work_id]
51
57
  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 very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger."
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."
8
8
  spec.homepage = "https://github.com/bdurand/lumberjack"
9
9
  spec.license = "MIT"
10
10
 
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.require_paths = ["lib"]
27
27
 
28
- spec.required_ruby_version = ">= 2.3.0"
28
+ spec.required_ruby_version = ">= 2.5.0"
29
29
 
30
30
  spec.add_development_dependency "bundler"
31
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.9
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-19 00:00:00.000000000 Z
11
+ date: 2025-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -31,6 +31,7 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - ARCHITECTURE.md
34
35
  - CHANGELOG.md
35
36
  - MIT_LICENSE.txt
36
37
  - README.md
@@ -50,11 +51,15 @@ files:
50
51
  - lib/lumberjack/formatter/exception_formatter.rb
51
52
  - lib/lumberjack/formatter/id_formatter.rb
52
53
  - lib/lumberjack/formatter/inspect_formatter.rb
54
+ - lib/lumberjack/formatter/multiply_formatter.rb
53
55
  - lib/lumberjack/formatter/object_formatter.rb
54
56
  - lib/lumberjack/formatter/pretty_print_formatter.rb
57
+ - lib/lumberjack/formatter/redact_formatter.rb
58
+ - lib/lumberjack/formatter/round_formatter.rb
55
59
  - lib/lumberjack/formatter/string_formatter.rb
56
60
  - lib/lumberjack/formatter/strip_formatter.rb
57
61
  - lib/lumberjack/formatter/structured_formatter.rb
62
+ - lib/lumberjack/formatter/tagged_message.rb
58
63
  - lib/lumberjack/formatter/truncate_formatter.rb
59
64
  - lib/lumberjack/log_entry.rb
60
65
  - lib/lumberjack/logger.rb
@@ -68,6 +73,7 @@ files:
68
73
  - lib/lumberjack/tagged_logging.rb
69
74
  - lib/lumberjack/tags.rb
70
75
  - lib/lumberjack/template.rb
76
+ - lib/lumberjack/utils.rb
71
77
  - lumberjack.gemspec
72
78
  homepage: https://github.com/bdurand/lumberjack
73
79
  licenses:
@@ -81,16 +87,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
81
87
  requirements:
82
88
  - - ">="
83
89
  - !ruby/object:Gem::Version
84
- version: 2.3.0
90
+ version: 2.5.0
85
91
  required_rubygems_version: !ruby/object:Gem::Requirement
86
92
  requirements:
87
93
  - - ">="
88
94
  - !ruby/object:Gem::Version
89
95
  version: '0'
90
96
  requirements: []
91
- rubygems_version: 3.4.12
97
+ rubygems_version: 3.4.10
92
98
  signing_key:
93
99
  specification_version: 4
94
- summary: A simple, powerful, and very fast logging utility that can be a drop in replacement
95
- for Logger or ActiveSupport::BufferedLogger.
100
+ summary: A simple, powerful, and fast logging utility with excellent structured logging
101
+ support that can be a drop in replacement for the standard library Logger.
96
102
  test_files: []