lumberjack 1.2.10 → 1.3.1

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.
@@ -5,7 +5,7 @@ module Lumberjack
5
5
  class Context
6
6
  attr_reader :tags
7
7
 
8
- # @param [Context] parent_context A parent context to inherit tags from.
8
+ # @param parent_context [Context] A parent context to inherit tags from.
9
9
  def initialize(parent_context = nil)
10
10
  @tags = {}
11
11
  @tags.merge!(parent_context.tags) if parent_context
@@ -13,7 +13,7 @@ module Lumberjack
13
13
 
14
14
  # Set tags on the context.
15
15
  #
16
- # @param [Hash] tags The tags to set.
16
+ # @param tags [Hash] The tags to set.
17
17
  # @return [void]
18
18
  def tag(tags)
19
19
  tags.each do |key, value|
@@ -23,7 +23,7 @@ module Lumberjack
23
23
 
24
24
  # Get a context tag.
25
25
  #
26
- # @param [String, Symbol] key The tag key.
26
+ # @param key [String, Symbol] The tag key.
27
27
  # @return [Object] The tag value.
28
28
  def [](key)
29
29
  @tags[key.to_s]
@@ -31,8 +31,8 @@ module Lumberjack
31
31
 
32
32
  # Set a context tag.
33
33
  #
34
- # @param [String, Symbol] key The tag key.
35
- # @param [Object] value The tag value.
34
+ # @param key [String, Symbol] The tag key.
35
+ # @param value [Object] The tag value.
36
36
  # @return [void]
37
37
  def []=(key, value)
38
38
  @tags[key.to_s] = value
@@ -18,7 +18,7 @@ module Lumberjack
18
18
  def initialize(path, options = {})
19
19
  @path = File.expand_path(path)
20
20
  @keep = options[:keep]
21
- super(path, options)
21
+ super
22
22
  @file_inode = begin
23
23
  stream.lstat.ino
24
24
  rescue
@@ -55,9 +55,8 @@ module Lumberjack
55
55
  # :additional_lines and :time_format options will be passed through to the
56
56
  # Template constuctor.
57
57
  #
58
- # The default template is "[:time :severity :progname(:pid) #:unit_of_work_id] :message"
59
- # with additional lines formatted as "\n [#:unit_of_work_id] :message". The unit of
60
- # work id will only appear if it is present.
58
+ # The default template is "[:time :severity :progname(:pid)] :message"
59
+ # with additional lines formatted as "\n :message".
61
60
  #
62
61
  # The size of the internal buffer in bytes can be set by providing :buffer_size (defaults to 32K).
63
62
  #
@@ -68,12 +67,12 @@ module Lumberjack
68
67
  @stream = stream
69
68
  @stream.sync = true if @stream.respond_to?(:sync=)
70
69
  @buffer = Buffer.new
71
- @buffer_size = (options[:buffer_size] || 0)
72
- template = (options[:template] || DEFAULT_FIRST_LINE_TEMPLATE)
70
+ @buffer_size = options[:buffer_size] || 0
71
+ template = options[:template] || DEFAULT_FIRST_LINE_TEMPLATE
73
72
  if template.respond_to?(:call)
74
73
  @template = template
75
74
  else
76
- additional_lines = (options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE)
75
+ additional_lines = options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE
77
76
  @template = Template.new(template, additional_lines: additional_lines, time_format: options[:time_format])
78
77
  end
79
78
  end
@@ -150,6 +149,13 @@ module Lumberjack
150
149
  end
151
150
  end
152
151
 
152
+ # Return the underlying stream. Provided for API compatibility with Logger devices.
153
+ #
154
+ # @return [IO] The underlying stream.
155
+ def dev
156
+ @stream
157
+ end
158
+
153
159
  protected
154
160
 
155
161
  # Set the underlying stream.
@@ -163,8 +169,6 @@ module Lumberjack
163
169
  def write_to_stream(lines)
164
170
  return if lines.empty?
165
171
  lines = lines.first if lines.is_a?(Array) && lines.size == 1
166
-
167
- out = nil
168
172
  out = if lines.is_a?(Array)
169
173
  "#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}"
170
174
  else
@@ -16,7 +16,7 @@ module Lumberjack
16
16
  if @format && obj.respond_to?(:strftime)
17
17
  obj.strftime(@format)
18
18
  elsif obj.respond_to?(:iso8601)
19
- obj.iso8601
19
+ obj.iso8601(6)
20
20
  else
21
21
  obj.to_s
22
22
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # This formatter can be used to multiply a numeric value by a specified multiplier and
6
+ # optionally round to a specified number of decimal places.
7
+ class MultiplyFormatter
8
+ # @param multiplier [Numeric] The multiplier to apply to the value.
9
+ # @param decimals [Integer, nil] The number of decimal places to round the result to.
10
+ # If nil, no rounding is applied.
11
+ def initialize(multiplier, decimals = nil)
12
+ @multiplier = multiplier
13
+ @decimals = decimals
14
+ end
15
+
16
+ def call(value)
17
+ return value unless value.is_a?(Numeric)
18
+
19
+ value *= @multiplier
20
+ value = value.round(@decimals) if @decimals
21
+ value
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # Log sensitive information in a redacted format showing the firat and last
6
+ # characters of the value, with the rest replaced by asterisks. The number of
7
+ # characters shown is dependent onthe length of the value; short values will
8
+ # not show any characters in order to avoid revealing too much information.
9
+ class RedactFormatter
10
+ def call(obj)
11
+ return obj unless obj.is_a?(String)
12
+
13
+ if obj.length > 8
14
+ "#{obj[0..1]}#{"*" * (obj.length - 4)}#{obj[-2..-1]}"
15
+ elsif obj.length > 5
16
+ "#{obj[0]}#{"*" * (obj.length - 2)}#{obj[-1]}"
17
+ else
18
+ "*****"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # Round numeric values to a set number of decimal places. This is useful when logging
6
+ # floating point numbers to reduce noise and rounding errors in the logs.
7
+ class RoundFormatter
8
+ def initialize(precision = 3)
9
+ @precision = precision
10
+ end
11
+
12
+ def call(obj)
13
+ if obj.is_a?(Numeric)
14
+ obj.round(@precision)
15
+ else
16
+ obj
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ class Formatter
5
+ # This class can be used as the return value from a formatter `call` method to
6
+ # extract additional tags from an object being logged. This can be useful when there
7
+ # using structured logging to include important metadata in the log message.
8
+ #
9
+ # @example
10
+ # # Automatically add tags with error details when logging an exception.
11
+ # logger.add_formatter(Exception, ->(e) {
12
+ # Lumberjack::Formatter::TaggedMessage.new(e.message, {
13
+ # error: {
14
+ # message: e.message,
15
+ # class: e.class.name,
16
+ # trace: e.backtrace
17
+ # }
18
+ # })
19
+ # })
20
+ class TaggedMessage
21
+ attr_reader :message, :tags
22
+
23
+ # @param [Formatter] formatter The formatter to apply the transformation to.
24
+ # @param [Proc] transform The transformation function to apply to the formatted string.
25
+ def initialize(message, tags)
26
+ @message = message
27
+ @tags = tags || {}
28
+ end
29
+
30
+ def to_s
31
+ inspect
32
+ end
33
+
34
+ def inspect
35
+ {message: @message, tags: @tags}.inspect
36
+ end
37
+ end
38
+ end
39
+ end
@@ -16,12 +16,16 @@ module Lumberjack
16
16
  require_relative "formatter/exception_formatter"
17
17
  require_relative "formatter/id_formatter"
18
18
  require_relative "formatter/inspect_formatter"
19
+ require_relative "formatter/multiply_formatter"
19
20
  require_relative "formatter/object_formatter"
20
21
  require_relative "formatter/pretty_print_formatter"
22
+ require_relative "formatter/redact_formatter"
23
+ require_relative "formatter/round_formatter"
21
24
  require_relative "formatter/string_formatter"
22
25
  require_relative "formatter/strip_formatter"
23
26
  require_relative "formatter/structured_formatter"
24
27
  require_relative "formatter/truncate_formatter"
28
+ require_relative "formatter/tagged_message"
25
29
 
26
30
  class << self
27
31
  # Returns a new empty formatter with no mapping. For historical reasons, a formatter
@@ -66,12 +70,12 @@ module Lumberjack
66
70
  # help avoid loading dependency issues. This applies only to classes; modules cannot be
67
71
  # passed in as strings.
68
72
  #
69
- # @param [Class, Module, String, Array<Class, Module, String>] klass The class or module to add a formatter for.
70
- # @param [Symbol, Class, String, #call] formatter The formatter to use for the class.
73
+ # @param klass [Class, Module, String, Array<Class, Module, String>] The class or module to add a formatter for.
74
+ # @param formatter [Symbol, Class, String, #call] The formatter to use for the class.
71
75
  # If a symbol is passed in, it will be used to load one of the predefined formatters.
72
76
  # If a class is passed in, it will be initialized with the args passed in.
73
77
  # Otherwise, the object will be used as the formatter and must respond to call method.
74
- # @param [Array] args Arguments to pass to the formatter when it is initialized.
78
+ # @param args [Array] Arguments to pass to the formatter when it is initialized.
75
79
  # @yield [obj] A block that will be used as the formatter for the class.
76
80
  # @yieldparam [Object] obj The object to format.
77
81
  # @yieldreturn [String] The formatted string.
@@ -129,7 +133,7 @@ module Lumberjack
129
133
  # help avoid loading dependency issues. This applies only to classes; modules cannot be
130
134
  # passed in as strings.
131
135
  #
132
- # @param [Class, Module, String, Array<Class, Module, String>] klass The class or module to remove the formatters for.
136
+ # @param klass [Class, Module, String, Array<Class, Module, String>] The class or module to remove the formatters for.
133
137
  # @return [self] Returns itself so that remove statements can be chained together.
134
138
  def remove(klass)
135
139
  Array(klass).each do |k|
@@ -152,9 +156,16 @@ module Lumberjack
152
156
  self
153
157
  end
154
158
 
159
+ # Return true if their are no registered formatters.
160
+ #
161
+ # @return [Boolean] true if there are no registered formatters, false otherwise.
162
+ def empty?
163
+ @class_formatters.empty? && @module_formatters.empty?
164
+ end
165
+
155
166
  # Format a message object by applying all formatters attached to it.
156
167
  #
157
- # @param [Object] message The message object to format.
168
+ # @param message [Object] The message object to format.
158
169
  # @return [Object] The formatted object.
159
170
  def format(message)
160
171
  formatter = formatter_for(message.class)
@@ -168,18 +179,22 @@ module Lumberjack
168
179
  # Compatibility with the Logger::Formatter signature. This method will just convert the message
169
180
  # object to a string and ignores the other parameters.
170
181
  #
171
- # @param [Integer, String, Symbol] severity The severity of the message.
172
- # @param [Time] timestamp The time the message was logged.
173
- # @param [String] progname The name of the program logging the message.
174
- # @param [Object] msg The message object to format.
182
+ # @param severity [Integer, String, Symbol] The severity of the message.
183
+ # @param timestamp [Time] The time the message was logged.
184
+ # @param progname [String] The name of the program logging the message.
185
+ # @param msg [Object] The message object to format.
175
186
  def call(severity, timestamp, progname, msg)
176
- "#{format(msg)}#{Lumberjack::LINE_SEPARATOR}"
187
+ formatted_message = format(msg)
188
+ formatted_message = formatted_message.message if formatted_message.is_a?(TaggedMessage)
189
+ "#{formatted_message}#{Lumberjack::LINE_SEPARATOR}"
177
190
  end
178
191
 
179
- private
180
-
181
192
  # Find the formatter for a class by looking it up using the class hierarchy.
182
- def formatter_for(klass) # :nodoc:
193
+ #
194
+ # @api private
195
+ def formatter_for(klass)
196
+ return nil if empty?
197
+
183
198
  check_modules = true
184
199
  until klass.nil?
185
200
  formatter = @class_formatters[klass.name]
@@ -8,16 +8,17 @@ module Lumberjack
8
8
 
9
9
  TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
10
10
 
11
+ # @deprecated Will be removed in version 2.0.
11
12
  UNIT_OF_WORK_ID = "unit_of_work_id"
12
13
 
13
14
  # Create a new log entry.
14
15
  #
15
- # @param [Time] time The time the log entry was created.
16
- # @param [Integer, String] severity The severity of the log entry.
17
- # @param [String] message The message to log.
18
- # @param [String] progname The name of the program that created the log entry.
19
- # @param [Integer] pid The process id of the program that created the log entry.
20
- # @param [Hash] tags A hash of tags to associate with the log entry.
16
+ # @param time [Time] The time the log entry was created.
17
+ # @param severity [Integer, String] The severity of the log entry.
18
+ # @param message [String] The message to log.
19
+ # @param progname [String] The name of the program that created the log entry.
20
+ # @param pid [Integer] The process id of the program that created the log entry.
21
+ # @param tags [Hash<String, Object>] A hash of tags to associate with the log entry.
21
22
  def initialize(time, severity, message, progname, pid, tags)
22
23
  @time = time
23
24
  @severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity))
@@ -25,9 +26,9 @@ module Lumberjack
25
26
  @progname = progname
26
27
  @pid = pid
27
28
  # backward compatibility with 1.0 API where the last argument was the unit of work id
28
- @tags = if tags.nil? || tags.is_a?(Hash)
29
- tags
30
- else
29
+ @tags = if tags.is_a?(Hash)
30
+ compact_tags(tags)
31
+ elsif !tags.nil?
31
32
  {UNIT_OF_WORK_ID => tags}
32
33
  end
33
34
  end
@@ -44,17 +45,21 @@ module Lumberjack
44
45
  to_s
45
46
  end
46
47
 
47
- # Deprecated - backward compatibility with 1.0 API
48
+ # @deprecated - backward compatibility with 1.0 API. Will be removed in version 2.0.
48
49
  def unit_of_work_id
49
- tags[UNIT_OF_WORK_ID] if tags
50
+ Lumberjack::Utils.deprecated("Lumberjack::LogEntry#unit_of_work_id", "Lumberjack::LogEntry#unit_of_work_id will be removed in version 2.0") do
51
+ tags[UNIT_OF_WORK_ID] if tags
52
+ end
50
53
  end
51
54
 
52
- # Deprecated - backward compatibility with 1.0 API
55
+ # @deprecated - backward compatibility with 1.0 API. Will be removed in version 2.0.
53
56
  def unit_of_work_id=(value)
54
- if tags
55
- tags[UNIT_OF_WORK_ID] = value
56
- else
57
- @tags = {UNIT_OF_WORK_ID => value}
57
+ Lumberjack::Utils.deprecated("Lumberjack::LogEntry#unit_of_work_id=", "Lumberjack::LogEntry#unit_of_work_id= will be removed in version 2.0") do
58
+ if tags
59
+ tags[UNIT_OF_WORK_ID] = value
60
+ else
61
+ @tags = {UNIT_OF_WORK_ID => value}
62
+ end
58
63
  end
59
64
  end
60
65
 
@@ -63,6 +68,11 @@ module Lumberjack
63
68
  tags[name.to_s] if tags
64
69
  end
65
70
 
71
+ # Return true if the log entry has no message and no tags.
72
+ def empty?
73
+ (message.nil? || message == "") && (tags.nil? || tags.empty?)
74
+ end
75
+
66
76
  private
67
77
 
68
78
  def tags_to_s
@@ -70,5 +80,37 @@ module Lumberjack
70
80
  tags&.each { |name, value| tags_string << " #{name}:#{value.inspect}" }
71
81
  tags_string
72
82
  end
83
+
84
+ def compact_tags(tags)
85
+ delete_keys = nil
86
+ compacted_keys = nil
87
+
88
+ tags.each do |key, value|
89
+ if value.nil? || value == ""
90
+ delete_keys ||= []
91
+ delete_keys << key
92
+ elsif value.is_a?(Hash)
93
+ compacted_value = compact_tags(value)
94
+ if compacted_value.empty?
95
+ delete_keys ||= []
96
+ delete_keys << key
97
+ elsif !value.equal?(compacted_value)
98
+ compacted_keys ||= []
99
+ compacted_keys << [key, compacted_value]
100
+ end
101
+ elsif value.is_a?(Array) && value.empty?
102
+ delete_keys ||= []
103
+ delete_keys << key
104
+ end
105
+ end
106
+
107
+ return tags if delete_keys.nil? && compacted_keys.nil?
108
+
109
+ tags = tags.dup
110
+ delete_keys&.each { |key| tags.delete(key) }
111
+ compacted_keys&.each { |key, value| tags[key] = value }
112
+
113
+ tags
114
+ end
73
115
  end
74
116
  end