lumberjack 1.2.7 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +244 -0
  3. data/CHANGELOG.md +251 -56
  4. data/README.md +197 -62
  5. data/VERSION +1 -1
  6. data/lib/lumberjack/context.rb +25 -5
  7. data/lib/lumberjack/device/date_rolling_log_file.rb +17 -8
  8. data/lib/lumberjack/device/log_file.rb +14 -7
  9. data/lib/lumberjack/device/multi.rb +8 -7
  10. data/lib/lumberjack/device/null.rb +2 -2
  11. data/lib/lumberjack/device/rolling_log_file.rb +46 -22
  12. data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
  13. data/lib/lumberjack/device/writer.rb +45 -21
  14. data/lib/lumberjack/device.rb +28 -13
  15. data/lib/lumberjack/formatter/date_time_formatter.rb +5 -5
  16. data/lib/lumberjack/formatter/exception_formatter.rb +4 -4
  17. data/lib/lumberjack/formatter/id_formatter.rb +4 -3
  18. data/lib/lumberjack/formatter/inspect_formatter.rb +1 -1
  19. data/lib/lumberjack/formatter/multiply_formatter.rb +25 -0
  20. data/lib/lumberjack/formatter/object_formatter.rb +1 -1
  21. data/lib/lumberjack/formatter/pretty_print_formatter.rb +7 -5
  22. data/lib/lumberjack/formatter/redact_formatter.rb +23 -0
  23. data/lib/lumberjack/formatter/round_formatter.rb +21 -0
  24. data/lib/lumberjack/formatter/string_formatter.rb +1 -1
  25. data/lib/lumberjack/formatter/strip_formatter.rb +1 -1
  26. data/lib/lumberjack/formatter/structured_formatter.rb +3 -1
  27. data/lib/lumberjack/formatter/tagged_message.rb +39 -0
  28. data/lib/lumberjack/formatter/truncate_formatter.rb +27 -0
  29. data/lib/lumberjack/formatter.rb +96 -28
  30. data/lib/lumberjack/log_entry.rb +90 -19
  31. data/lib/lumberjack/logger.rb +318 -86
  32. data/lib/lumberjack/rack/context.rb +21 -2
  33. data/lib/lumberjack/rack/request_id.rb +8 -4
  34. data/lib/lumberjack/rack/unit_of_work.rb +7 -3
  35. data/lib/lumberjack/rack.rb +4 -4
  36. data/lib/lumberjack/severity.rb +22 -3
  37. data/lib/lumberjack/tag_context.rb +78 -0
  38. data/lib/lumberjack/tag_formatter.rb +124 -25
  39. data/lib/lumberjack/tagged_logger_support.rb +26 -12
  40. data/lib/lumberjack/tagged_logging.rb +1 -1
  41. data/lib/lumberjack/tags.rb +8 -8
  42. data/lib/lumberjack/template.rb +17 -5
  43. data/lib/lumberjack/utils.rb +182 -0
  44. data/lib/lumberjack.rb +64 -35
  45. data/lumberjack.gemspec +18 -15
  46. metadata +23 -54
@@ -1,18 +1,37 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  module Rack
5
5
  # Middleware to create a global context for Lumberjack for the scope of a rack request.
6
+ #
7
+ # The optional `env_tags` parameter can be used to set up global tags from the request
8
+ # environment. This is useful for setting tags that are relevant to the entire request
9
+ # like the request id, host, etc.
6
10
  class Context
7
- def initialize(app)
11
+ # @param [Object] app The rack application.
12
+ # @param [Hash] env_tags A hash of tags to set from the request environment. If a tag value is
13
+ # a Proc, it will be called with the request `env` as an argument to allow dynamic tag values
14
+ # based on request data.
15
+ def initialize(app, env_tags = nil)
8
16
  @app = app
17
+ @env_tags = env_tags
9
18
  end
10
19
 
11
20
  def call(env)
12
21
  Lumberjack.context do
22
+ apply_tags(env) if @env_tags
13
23
  @app.call(env)
14
24
  end
15
25
  end
26
+
27
+ private
28
+
29
+ def apply_tags(env)
30
+ tags = @env_tags.transform_values do |value|
31
+ value.is_a?(Proc) ? value.call(env) : value
32
+ end
33
+ Lumberjack.tag(tags)
34
+ end
16
35
  end
17
36
  end
18
37
  end
@@ -1,22 +1,26 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  module Rack
5
5
  # Support for using the Rails ActionDispatch request id in the log.
6
6
  # The format is expected to be a random UUID and only the first chunk is used for terseness
7
7
  # if the abbreviated argument is true.
8
+ #
9
+ # @deprecated Use tags instead of request id for unit of work. Will be removed in version 2.0.
8
10
  class RequestId
9
11
  REQUEST_ID = "action_dispatch.request_id"
10
12
 
11
13
  def initialize(app, abbreviated = false)
12
- @app = app
13
- @abbreviated = abbreviated
14
+ Lumberjack::Utils.deprecated("Lumberjack::Rack::RequestId", "Lumberjack::Rack::RequestId will be removed in version 2.0") do
15
+ @app = app
16
+ @abbreviated = abbreviated
17
+ end
14
18
  end
15
19
 
16
20
  def call(env)
17
21
  request_id = env[REQUEST_ID]
18
22
  if request_id && @abbreviated
19
- request_id = request_id.split('-', 2).first
23
+ request_id = request_id.split("-", 2).first
20
24
  end
21
25
  Lumberjack.unit_of_work(request_id) do
22
26
  @app.call(env)
@@ -1,12 +1,16 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  module Rack
5
+ # @deprecated Use the Lumberjack::Rack::Context middleware instead to set a global tag
6
+ # with an identifier to tie log entries together in a unit of work. Will be removed in version 2.0.
5
7
  class UnitOfWork
6
8
  def initialize(app)
7
- @app = app
9
+ Lumberjack::Utils.deprecated("Lumberjack::Rack::UnitOfWork", "Lumberjack::Rack::UnitOfWork will be removed in version 2.0") do
10
+ @app = app
11
+ end
8
12
  end
9
-
13
+
10
14
  def call(env)
11
15
  Lumberjack.unit_of_work do
12
16
  @app.call(env)
@@ -1,9 +1,9 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  module Rack
5
- require_relative "rack/unit_of_work.rb"
6
- require_relative "rack/request_id.rb"
7
- require_relative "rack/context.rb"
5
+ require_relative "rack/unit_of_work"
6
+ require_relative "rack/request_id"
7
+ require_relative "rack/context"
8
8
  end
9
9
  end
@@ -1,4 +1,4 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
4
  # The standard severity levels for logging messages.
@@ -11,17 +11,36 @@ module Lumberjack
11
11
  FATAL = ::Logger::Severity::FATAL
12
12
  UNKNOWN = ::Logger::Severity::UNKNOWN
13
13
 
14
- SEVERITY_LABELS = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN).freeze
14
+ SEVERITY_LABELS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze
15
15
 
16
16
  class << self
17
+ # Convert a severity level to a label.
18
+ #
19
+ # @param [Integer] severity The severity level to convert.
20
+ # @return [String] The severity label.
17
21
  def level_to_label(severity)
18
22
  SEVERITY_LABELS[severity] || SEVERITY_LABELS.last
19
23
  end
20
24
 
25
+ # Convert a severity label to a level.
26
+ #
27
+ # @param [String, Symbol] label The severity label to convert.
28
+ # @return [Integer] The severity level.
21
29
  def label_to_level(label)
22
30
  SEVERITY_LABELS.index(label.to_s.upcase) || UNKNOWN
23
31
  end
24
- end
25
32
 
33
+ # Coerce a value to a severity level.
34
+ #
35
+ # @param [Integer, String, Symbol] value The value to coerce.
36
+ # @return [Integer] The severity level.
37
+ def coerce(value)
38
+ if value.is_a?(Integer)
39
+ value
40
+ else
41
+ label_to_level(value)
42
+ end
43
+ end
44
+ end
26
45
  end
27
46
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ # A tag context provides an interface for manipulating a tag hash.
5
+ class TagContext
6
+ def initialize(tags)
7
+ @tags = tags
8
+ end
9
+
10
+ # Merge new tags into the context tags. Tag values will be flattened using dot notation
11
+ # on the keys. So `{ a: { b: 'c' } }` will become `{ 'a.b' => 'c' }`.
12
+ #
13
+ # If a block is given, then the tags will only be added for the duration of the block.
14
+ #
15
+ # @param tags [Hash] The tags to set.
16
+ # @return [void]
17
+ def tag(tags)
18
+ @tags.merge!(Utils.flatten_tags(tags))
19
+ end
20
+
21
+ # Get a tag value.
22
+ #
23
+ # @param name [String, Symbol] The tag key.
24
+ # @return [Object] The tag value.
25
+ def [](name)
26
+ return nil if @tags.empty?
27
+
28
+ name = name.to_s
29
+ return @tags[name] if @tags.include?(name)
30
+
31
+ # Check for partial matches in dot notation and return the hash representing the partial match.
32
+ prefix_key = "#{name}."
33
+ matching_tags = {}
34
+ @tags.each do |key, value|
35
+ if key.start_with?(prefix_key)
36
+ # Remove the prefix to get the relative key
37
+ relative_key = key[prefix_key.length..-1]
38
+ matching_tags[relative_key] = value
39
+ end
40
+ end
41
+
42
+ return nil if matching_tags.empty?
43
+ matching_tags
44
+ end
45
+
46
+ # Set a tag value.
47
+ #
48
+ # @param name [String, Symbol] The tag name.
49
+ # @param value [Object] The tag value.
50
+ # @return [void]
51
+ def []=(name, value)
52
+ if value.is_a?(Hash)
53
+ @tags.merge!(Utils.flatten_tags(name => value))
54
+ else
55
+ @tags[name.to_s] = value
56
+ end
57
+ end
58
+
59
+ # Remove tags from the context.
60
+ #
61
+ # @param names [Array<String, Symbol>] The tag names to remove.
62
+ # @return [void]
63
+ def delete(*names)
64
+ names.each do |name|
65
+ prefix_key = "#{name}."
66
+ @tags.delete_if { |k, _| k == name.to_s || k.start_with?(prefix_key) }
67
+ end
68
+ nil
69
+ end
70
+
71
+ # Return a copy of the tags as a hash.
72
+ #
73
+ # @return [Hash]
74
+ def to_h
75
+ @tags.dup
76
+ end
77
+ end
78
+ end
@@ -9,14 +9,18 @@ module Lumberjack
9
9
  # tag_formatter.add(["password", "email"]) { |value| "***" }
10
10
  # tag_formatter.add("finished_at", Lumberjack::Formatter::DateTimeFormatter.new("%Y-%m-%dT%H:%m:%S%z"))
11
11
  class TagFormatter
12
-
13
12
  def initialize
14
13
  @formatters = {}
14
+ @class_formatters = {}
15
15
  @default_formatter = nil
16
16
  end
17
17
 
18
18
  # Add a default formatter applied to all tag values. This can either be a Lumberjack::Formatter
19
19
  # or an object that responds to `call` or a block.
20
+ #
21
+ # @param formatter [Lumberjack::Formatter, #call, nil] The formatter to use.
22
+ # If this is nil, then the block will be used as the formatter.
23
+ # @return [Lumberjack::TagFormatter] self
20
24
  def default(formatter = nil, &block)
21
25
  formatter ||= block
22
26
  formatter = dereference_formatter(formatter)
@@ -25,36 +29,67 @@ module Lumberjack
25
29
  end
26
30
 
27
31
  # Remove the default formatter.
32
+ #
33
+ # @return [Lumberjack::TagFormatter] self
28
34
  def remove_default
29
35
  @default_formatter = nil
30
36
  self
31
37
  end
32
38
 
33
- # Add a formatter for specific tag names. This can either be a Lumberjack::Formatter
34
- # or an object that responds to `call` or a block. The default formatter will not be
35
- # applied.
36
- def add(names, formatter = nil, &block)
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.
44
+ #
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.
53
+ # If this is nil, then the block will be used as the formatter.
54
+ # @return [Lumberjack::TagFormatter] self
55
+ #
56
+ # @example
57
+ # tag_formatter.add("password", &:redact)
58
+ def add(names_or_classes, formatter = nil, &block)
37
59
  formatter ||= block
38
60
  formatter = dereference_formatter(formatter)
39
61
  if formatter.nil?
40
62
  remove(key)
41
63
  else
42
- Array(names).each do |name|
43
- @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
44
70
  end
45
71
  end
46
72
  self
47
73
  end
48
74
 
49
75
  # Remove formatters for specific tag names. The default formatter will still be applied.
50
- def remove(names)
51
- Array(names).each do |name|
52
- @formatters.delete(name.to_s)
76
+ #
77
+ # @param names_or_classes [String, Module, Array<String, Module>] The tag names or classes to remove the formatter from.
78
+ # @return [Lumberjack::TagFormatter] self
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
53
86
  end
54
87
  self
55
88
  end
56
89
 
57
90
  # Remove all formatters.
91
+ #
92
+ # @return [Lumberjack::TagFormatter] self
58
93
  def clear
59
94
  @default_formatter = nil
60
95
  @formatters.clear
@@ -62,37 +97,101 @@ module Lumberjack
62
97
  end
63
98
 
64
99
  # Format a hash of tags using the formatters
100
+ #
101
+ # @param tags [Hash] The tags to format.
102
+ # @return [Hash] The formatted tags.
65
103
  def format(tags)
66
104
  return nil if tags.nil?
67
- if @default_formatter.nil? && (@formatters.empty? || (@formatters.keys & tags.keys).empty?)
68
- tags
69
- else
70
- formatted = {}
71
- tags.each do |name, value|
72
- formatter = (@formatters[name.to_s] || @default_formatter)
73
- if formatter.is_a?(Lumberjack::Formatter)
74
- value = formatter.format(value)
75
- elsif formatter.respond_to?(:call)
76
- value = formatter.call(value)
77
- end
78
- formatted[name.to_s] = value
79
- end
80
- formatted
105
+ if @default_formatter.nil? && @formatters.empty? && @class_formatters.empty?
106
+ return tags
81
107
  end
108
+
109
+ formatted_tags(tags)
82
110
  end
83
111
 
84
112
  private
85
113
 
114
+ def formatted_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 = begin
138
+ if formatter.is_a?(Lumberjack::Formatter)
139
+ formatter.format(value)
140
+ elsif formatter.respond_to?(:call)
141
+ formatter.call(value)
142
+ else
143
+ value
144
+ end
145
+ rescue SystemStackError, StandardError => e
146
+ error_message = e.class.name
147
+ error_message = "#{error_message} #{e.message}" if e.message && e.message != ""
148
+ warn("<Error formatting #{value.class.name}: #{error_message}>")
149
+ "<Error formatting #{value.class.name}: #{error_message}>"
150
+ end
151
+
152
+ if formatted_value.is_a?(Enumerable)
153
+ skip_classes ||= []
154
+ skip_classes << value.class if using_class_formatter
155
+ sub_prefix = "#{prefixed_name}."
156
+
157
+ formatted_value = if formatted_value.is_a?(Hash)
158
+ formatted_tags(formatted_value, skip_classes: skip_classes, prefix: sub_prefix)
159
+ else
160
+ formatted_value.collect do |item|
161
+ formatted_tag_value(nil, item, skip_classes: skip_classes, prefix: sub_prefix)
162
+ end
163
+ end
164
+ end
165
+
166
+ formatted_value
167
+ end
168
+
86
169
  def dereference_formatter(formatter)
87
170
  if formatter.is_a?(TaggedLoggerSupport::Formatter)
88
171
  formatter.__formatter
89
172
  elsif formatter.is_a?(Symbol)
90
- formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/){|m| $~[2].upcase}}Formatter"
173
+ formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/) { |m| $~[2].upcase }}Formatter"
91
174
  Formatter.const_get(formatter_class_name).new
92
175
  else
93
176
  formatter
94
177
  end
95
178
  end
96
179
 
180
+ def class_formatter(klass)
181
+ formatter = @class_formatters[klass]
182
+ return formatter if formatter
183
+
184
+ formatters = @class_formatters.select { |k, _| klass <= k }
185
+ return formatters.values.first if formatters.length <= 1
186
+
187
+ superclass = klass.superclass
188
+ while superclass
189
+ formatter = formatters[superclass]
190
+ return formatter if formatter
191
+ superclass = superclass.superclass
192
+ end
193
+
194
+ formatters.values.first
195
+ end
97
196
  end
98
197
  end
@@ -6,7 +6,6 @@ require "forwardable"
6
6
  module Lumberjack
7
7
  # Methods to make Lumberjack::Logger API compatible with ActiveSupport::TaggedLogger.
8
8
  module TaggedLoggerSupport
9
-
10
9
  class Formatter < DelegateClass(Lumberjack::Formatter)
11
10
  extend Forwardable
12
11
  def_delegators :@logger, :tagged, :push_tags, :pop_tags, :clear_tags!
@@ -39,15 +38,19 @@ module Lumberjack
39
38
  end
40
39
 
41
40
  # Compatibility with ActiveSupport::TaggedLogging which only supports adding tags as strings.
42
- # If a tag looks like "key:value" or "key=value", it will be added as a key value pair.
43
- # Otherwise it will be appended to a list named "tagged".
41
+ # Tags will be added to the "tagged" key in the logger's tags hash as an array.
44
42
  def tagged(*tags, &block)
45
- tag_hash = {}
46
- tags.flatten.each do |tag|
47
- tagged_values = Array(tag_hash["tagged"] || self.tags["tagged"])
48
- tag_hash["tagged"] = tagged_values + [tag]
43
+ tagged_values = Array(tag_value("tagged"))
44
+ flattened_tags = tags.flatten.collect(&:to_s).reject do |tag|
45
+ tag.respond_to?(:blank?) ? tag.blank? : tag.empty?
46
+ end
47
+ tagged_values += flattened_tags unless flattened_tags.empty?
48
+
49
+ if block || in_tag_context?
50
+ tag("tagged" => tagged_values, &block)
51
+ else
52
+ tag_globally("tagged" => tagged_values)
49
53
  end
50
- tag(tag_hash, &block)
51
54
  end
52
55
 
53
56
  def push_tags(*tags)
@@ -55,13 +58,24 @@ module Lumberjack
55
58
  end
56
59
 
57
60
  def pop_tags(size = 1)
58
- tagged_values = Array(@tags["tagged"])
59
- tagged_values = (tagged_values.size > size ? tagged_values[0, tagged_values.size - size] : nil)
60
- tag("tagged" => tagged_values)
61
+ tagged_values = tag_value("tagged")
62
+ return unless tagged_values.is_a?(Array)
63
+
64
+ tagged_values = ((tagged_values.size > size) ? tagged_values[0, tagged_values.size - size] : nil)
65
+
66
+ if in_tag_context?
67
+ tag("tagged" => tagged_values)
68
+ else
69
+ tag_globally("tagged" => tagged_values)
70
+ end
61
71
  end
62
72
 
63
73
  def clear_tags!
64
- tag("tagged" => nil)
74
+ if in_tag_context?
75
+ tag("tagged" => nil)
76
+ else
77
+ tag_globally("tagged" => nil)
78
+ end
65
79
  end
66
80
  end
67
81
  end
@@ -10,7 +10,7 @@ module Lumberjack
10
10
  base.singleton_class.send(:prepend, ClassMethods)
11
11
  end
12
12
  end
13
-
13
+
14
14
  module ClassMethods
15
15
  def new(logger)
16
16
  if logger.is_a?(Lumberjack::Logger)
@@ -5,29 +5,29 @@ module Lumberjack
5
5
  class << self
6
6
  # Transform hash keys to strings. This method exists for optimization and backward compatibility.
7
7
  # If a hash already has string keys, it will be returned as is.
8
+ #
9
+ # @param [Hash] hash The hash to transform.
10
+ # @return [Hash] The hash with string keys.
8
11
  def stringify_keys(hash)
9
12
  return nil if hash.nil?
10
13
  if hash.keys.all? { |key| key.is_a?(String) }
11
14
  hash
12
- elsif hash.respond_to?(:transform_keys)
13
- hash.transform_keys(&:to_s)
14
15
  else
15
- copy = {}
16
- hash.each do |key, value|
17
- copy[key.to_s] = value
18
- end
19
- copy
16
+ hash.transform_keys(&:to_s)
20
17
  end
21
18
  end
22
19
 
23
20
  # Ensure keys are strings and expand any values in a hash that are Proc's by calling them and replacing
24
21
  # the value with the result. This allows setting global tags with runtime values.
22
+ #
23
+ # @param [Hash] hash The hash to transform.
24
+ # @return [Hash] The hash with string keys and expanded values.
25
25
  def expand_runtime_values(hash)
26
26
  return nil if hash.nil?
27
27
  if hash.all? { |key, value| key.is_a?(String) && !value.is_a?(Proc) }
28
28
  return hash
29
29
  end
30
-
30
+
31
31
  copy = {}
32
32
  hash.each do |key, value|
33
33
  if value.is_a?(Proc) && (value.arity == 0 || value.arity == -1)
@@ -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
@@ -14,7 +14,7 @@ module Lumberjack
14
14
  # If your tag name contains characters other than alpha numerics and the underscore, you must surround it
15
15
  # with curly brackets: `:{http.request-id}`.
16
16
  class Template
17
- TEMPLATE_ARGUMENT_ORDER = %w(:time :severity :progname :pid :message :tags).freeze
17
+ TEMPLATE_ARGUMENT_ORDER = %w[:time :severity :progname :pid :message :tags].freeze
18
18
  MILLISECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N"
19
19
  MICROSECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N"
20
20
  PLACEHOLDER_PATTERN = /:(([a-z0-9_]+)|({[^}]+}))/i.freeze
@@ -30,6 +30,9 @@ module Lumberjack
30
30
  # specified precision.
31
31
  #
32
32
  # Messages will have white space stripped from both ends.
33
+ #
34
+ # @param [String] first_line The template to use to format the first line of a message.
35
+ # @param [Hash] options The options for the template.
33
36
  def initialize(first_line, options = {})
34
37
  @first_line_template, @first_line_tags = compile(first_line)
35
38
  additional_lines = options[:additional_lines] || "#{Lumberjack::LINE_SEPARATOR}:message"
@@ -39,6 +42,9 @@ module Lumberjack
39
42
  self.datetime_format = (options[:time_format] || :milliseconds)
40
43
  end
41
44
 
45
+ # Set the format used to format the time.
46
+ #
47
+ # @param [String] format The format to use to format the time.
42
48
  def datetime_format=(format)
43
49
  if format == :milliseconds
44
50
  format = MILLISECOND_TIME_FORMAT
@@ -48,11 +54,17 @@ module Lumberjack
48
54
  @time_formatter = Formatter::DateTimeFormatter.new(format)
49
55
  end
50
56
 
57
+ # Get the format used to format the time.
58
+ #
59
+ # @return [String]
51
60
  def datetime_format
52
61
  @time_formatter.format
53
62
  end
54
63
 
55
64
  # Convert an entry into a string using the template.
65
+ #
66
+ # @param [Lumberjack::LogEntry] entry The entry to convert to a string.
67
+ # @return [String] The entry converted to a string.
56
68
  def call(entry)
57
69
  return entry unless entry.is_a?(LogEntry)
58
70
 
@@ -86,8 +98,8 @@ module Lumberjack
86
98
  def tag_args(tags, tag_vars)
87
99
  return [nil] * (tag_vars.size + 1) if tags.nil? || tags.size == 0
88
100
 
89
- tags_string = String.new
90
- tags.each do |name, value|
101
+ tags_string = +""
102
+ Lumberjack::Utils.flatten_tags(tags).each do |name, value|
91
103
  unless value.nil? || tag_vars.include?(name)
92
104
  value = value.to_s
93
105
  value = value.gsub(Lumberjack::LINE_SEPARATOR, " ") if value.include?(Lumberjack::LINE_SEPARATOR)
@@ -103,7 +115,7 @@ module Lumberjack
103
115
  end
104
116
 
105
117
  # Compile the template string into a value that can be used with sprintf.
106
- def compile(template) #:nodoc:
118
+ def compile(template) # :nodoc:
107
119
  tag_vars = []
108
120
  template = template.gsub(PLACEHOLDER_PATTERN) do |match|
109
121
  var_name = match.sub("{", "").sub("}", "")