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
@@ -1,50 +1,175 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lumberjack
4
- # A template converts entries to strings. Templates can contain the following place holders to
5
- # reference log entry values:
4
+ # A flexible template system for converting log entries into formatted strings.
5
+ # Templates use mustache style placeholders to create customizable log output formats.
6
6
  #
7
- # * :time
8
- # * :severity
9
- # * :progname
10
- # * :tags
11
- # * :message
7
+ # The template system supports the following built-in placeholders:
12
8
  #
13
- # Any other words prefixed with a colon will be substituted with the value of the tag with that name.
14
- # If your tag name contains characters other than alpha numerics and the underscore, you must surround it
15
- # with curly brackets: `:{http.request-id}`.
9
+ # - <code>{{time}}</code> - The log entry timestamp
10
+ # - <code>{{severity}}</code> - The severity level (DEBUG, INFO, WARN, ERROR, FATAL). The severity
11
+ # can also be formatted in a variety of ways with an optional format specifier.
12
+ # Supported formats include:
13
+ # - <code>{{severity(padded)}}</code> - Right padded so that all values are five characters
14
+ # - <code>{{severity(char)}}</code> - Single character representation (D, I, W, E, F)
15
+ # - <code>{{severity(emoji)}}</code> - Emoji representation
16
+ # - <code>{{severity(level)}}</code> - Numeric level representation
17
+ # - <code>{{progname}}</code> - The program name that generated the entry
18
+ # - <code>{{pid}}</code> - The process ID
19
+ # - <code>{{message}}</code> - The main log message content
20
+ # - <code>{{attributes}}</code> - All custom attributes formatted as key:value pairs
21
+ #
22
+ # Custom attribute placeholders can also be put in the double bracket placeholders.
23
+ # Any attributes explicitly added to the template in their own placeholder will be removed
24
+ # from the general list of attributes.
25
+ #
26
+ # @example Basic template usage
27
+ # template = Lumberjack::Template.new("[{{time}} {{severity}}] {{message}}")
28
+ # # Output: [2023-08-21T10:30:15.123 INFO] User logged in
29
+ #
30
+ # @example Multi-line message formatting
31
+ # template = Lumberjack::Template.new(
32
+ # "[{{time}} {{severity}}] {{message}}",
33
+ # additional_lines: "\n | {{message}}"
34
+ # )
35
+ # # Output:
36
+ # # [2023-08-21T10:30:15.123 INFO] First line
37
+ # # | Second line
38
+ # # | Third line
39
+ #
40
+ # @example Custom attribute placeholders
41
+ # # The user_id attribute will be put before the message instead of with the rest of the attributes.
42
+ # template = Lumberjack::Template.new("[{{time}} {{severity}}] (usr:{{user_id}} {{message}} -- {{attributes}})")
16
43
  class Template
17
- TEMPLATE_ARGUMENT_ORDER = %w[:time :severity :progname :pid :message :tags].freeze
44
+ DEFAULT_FIRST_LINE_TEMPLATE = "[{{time}} {{severity(padded)}} {{progname}}({{pid}})] {{message}} {{attributes}}"
45
+ STDLIB_FIRST_LINE_TEMPLATE = "{{severity(char)}}, [{{time}} {{pid}}] {{severity(padded)}} -- {{progname}}: {{message}} {{attributes}}"
46
+ DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> {{message}}"
47
+ DEFAULT_ATTRIBUTE_FORMAT = "[%s:%s]"
48
+
49
+ TemplateRegistry.add(:default, DEFAULT_FIRST_LINE_TEMPLATE)
50
+ TemplateRegistry.add(:stdlib, STDLIB_FIRST_LINE_TEMPLATE)
51
+ TemplateRegistry.add(:message, "{{message}}")
52
+
53
+ # A wrapper template that delegates formatting to a standard Ruby Logger formatter.
54
+ # This provides compatibility with existing Logger::Formatter implementations while
55
+ # maintaining the Template interface for consistent usage within Lumberjack.
56
+ class StandardFormatterTemplate < Template
57
+ # Create a new wrapper for a standard Ruby Logger formatter.
58
+ #
59
+ # @param formatter [Logger::Formatter] The formatter to wrap
60
+ def initialize(formatter)
61
+ @formatter = formatter
62
+ end
63
+
64
+ # Format a log entry using the wrapped formatter.
65
+ #
66
+ # @param entry [Lumberjack::LogEntry] The log entry to format
67
+ # @return [String] The formatted log entry
68
+ def call(entry)
69
+ @formatter.call(entry.severity_label, entry.time, entry.progname, entry.message)
70
+ end
71
+
72
+ # Set the datetime format on the wrapped formatter if supported.
73
+ #
74
+ # @param value [String] The datetime format string
75
+ # @return [void]
76
+ def datetime_format=(value)
77
+ @formatter.datetime_format = value if @formatter.respond_to?(:datetime_format=)
78
+ end
79
+
80
+ # Get the datetime format from the wrapped formatter if supported.
81
+ #
82
+ # @return [String, nil] The datetime format string, or nil if not supported
83
+ def datetime_format
84
+ @formatter.datetime_format if @formatter.respond_to?(:datetime_format)
85
+ end
86
+ end
87
+
88
+ TEMPLATE_ARGUMENT_ORDER = %w[
89
+ time
90
+ severity
91
+ severity(padded)
92
+ severity(char)
93
+ severity(emoji)
94
+ severity(level)
95
+ progname
96
+ pid
97
+ message
98
+ attributes
99
+ ].freeze
100
+
18
101
  MILLISECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N"
19
102
  MICROSECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N"
20
- PLACEHOLDER_PATTERN = /:(([a-z0-9_]+)|({[^}]+}))/i.freeze
103
+ PLACEHOLDER_PATTERN = /{{ *((?:[^}]|}(?!}))*) *}}/i
104
+ V1_PLACEHOLDER_PATTERN = /:[a-z0-9_.-]+/i
105
+ RESET_CHAR = "\e[0m"
106
+ private_constant :TEMPLATE_ARGUMENT_ORDER, :MILLISECOND_TIME_FORMAT, :MICROSECOND_TIME_FORMAT, :PLACEHOLDER_PATTERN, :V1_PLACEHOLDER_PATTERN, :RESET_CHAR
21
107
 
22
- # Create a new template from the markup. The +first_line+ argument is used to format only the first
23
- # line of a message. Additional lines will be added to the message unformatted. If you wish to format
24
- # the additional lines, use the :additional_lines options to specify a template. Note that you'll need
25
- # to provide the line separator character in this template if you want to keep the message on multiple lines.
26
- #
27
- # The time will be formatted as YYYY-MM-DDTHH:MM:SSS.SSS by default. If you wish to change the format, you
28
- # can specify the :time_format option which can be either a time format template as documented in
29
- # +Time#strftime+ or the values +:milliseconds+ or +:microseconds+ to use the standard format with the
30
- # specified precision.
31
- #
32
- # Messages will have white space stripped from both ends.
108
+ class << self
109
+ def colorize_entry(formatted_string, entry)
110
+ color_start = entry.severity_data.terminal_color
111
+ formatted_string.split(Lumberjack::LINE_SEPARATOR).collect do |line|
112
+ "\e7#{color_start}#{line}\e8"
113
+ end.join(Lumberjack::LINE_SEPARATOR)
114
+ end
115
+ end
116
+
117
+ # Create a new template with customizable formatting options. The template
118
+ # supports different formatting for single-line and multi-line messages,
119
+ # custom time formatting, and configurable attribute display.
33
120
  #
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.
36
- def initialize(first_line, options = {})
37
- @first_line_template, @first_line_tags = compile(first_line)
38
- additional_lines = options[:additional_lines] || "#{Lumberjack::LINE_SEPARATOR}:message"
39
- @additional_line_template, @additional_line_tags = compile(additional_lines)
121
+ # @param first_line [String, nil] Template for formatting the first line of messages.
122
+ # Defaults to <code>[{{ time }} {{ severity(padded) }} {{ progname }}({{ pid }})] {{ message }} {{ attributes }}</code>
123
+ # @param additional_lines [String, nil] Template for formatting additional lines
124
+ # in multi-line messages. Defaults to <code>\\n> {{ message }}</code>
125
+ # @param time_format [String, Symbol, nil] Time formatting specification. Can be:
126
+ # - A strftime format string (e.g., "%Y-%m-%d %H:%M:%S")
127
+ # - +:milliseconds+ for ISO format with millisecond precision (default)
128
+ # - +:microseconds+ for ISO format with microsecond precision
129
+ # @param attribute_format [String, nil] Printf-style format for individual attributes.
130
+ # Must contain exactly two %s placeholders for name and value. Defaults to "[%s:%s]"
131
+ # @param colorize [Boolean] Whether to colorize the log entry based on severity (default: false)
132
+ # @raise [ArgumentError] If attribute_format doesn't contain exactly two %s placeholders
133
+ def initialize(first_line = nil, additional_lines: nil, time_format: nil, attribute_format: nil, colorize: false)
134
+ first_line ||= DEFAULT_FIRST_LINE_TEMPLATE
135
+ first_line = "#{first_line.chomp}#{Lumberjack::LINE_SEPARATOR}"
136
+ if !first_line.include?("{{") && first_line.match?(V1_PLACEHOLDER_PATTERN)
137
+ Utils.deprecated("Template.v1", "Templates now use {{placeholder}} instead of :placeholder and :tags has been replaced with {{attributes}}.") do
138
+ @first_line_template, @first_line_attributes = compile_v1(first_line)
139
+ end
140
+ else
141
+ @first_line_template, @first_line_attributes = compile(first_line)
142
+ end
143
+
144
+ additional_lines ||= DEFAULT_ADDITIONAL_LINES_TEMPLATE
145
+ if !additional_lines.include?("{{") && additional_lines.match?(V1_PLACEHOLDER_PATTERN)
146
+ Utils.deprecated("Template.v1", "Templates now use {{placeholder}} instead of :placeholder and :tags has been replaced with {{attributes}}.") do
147
+ @additional_line_template, @additional_line_attributes = compile_v1(additional_lines)
148
+ end
149
+ else
150
+ @additional_line_template, @additional_line_attributes = compile(additional_lines)
151
+ end
152
+
153
+ @attribute_template = attribute_format || DEFAULT_ATTRIBUTE_FORMAT
154
+ unless @attribute_template.scan("%s").size == 2
155
+ raise ArgumentError.new("attribute_format must be a printf template with exactly two '%s' placeholders")
156
+ end
157
+
40
158
  # Formatting the time is relatively expensive, so only do it if it will be used
41
- @template_include_time = first_line.include?(":time") || additional_lines.include?(":time")
42
- self.datetime_format = (options[:time_format] || :milliseconds)
159
+ @template_include_time = "#{@first_line_template} #{@additional_line_template}".include?("%1$s")
160
+ self.datetime_format = (time_format || :milliseconds)
161
+
162
+ @colorize = colorize
43
163
  end
44
164
 
45
- # Set the format used to format the time.
165
+ # Set the datetime format used for timestamp formatting in the template.
166
+ # This method accepts both strftime format strings and symbolic shortcuts.
46
167
  #
47
- # @param [String] format The format to use to format the time.
168
+ # @param format [String, Symbol] The datetime format specification:
169
+ # - String: A strftime format pattern (e.g., "%Y-%m-%d %H:%M:%S")
170
+ # - +:milliseconds+: ISO format with millisecond precision (YYYY-MM-DDTHH:MM:SS.sss)
171
+ # - +:microseconds+: ISO format with microsecond precision (YYYY-MM-DDTHH:MM:SS.ssssss)
172
+ # @return [void]
48
173
  def datetime_format=(format)
49
174
  if format == :milliseconds
50
175
  format = MILLISECOND_TIME_FORMAT
@@ -54,17 +179,19 @@ module Lumberjack
54
179
  @time_formatter = Formatter::DateTimeFormatter.new(format)
55
180
  end
56
181
 
57
- # Get the format used to format the time.
182
+ # Get the current datetime format string used for timestamp formatting.
58
183
  #
59
- # @return [String]
184
+ # @return [String] The strftime format string currently in use
60
185
  def datetime_format
61
186
  @time_formatter.format
62
187
  end
63
188
 
64
- # Convert an entry into a string using the template.
189
+ # Convert a log entry into a formatted string using the template. This method
190
+ # handles both single-line and multi-line messages, applying the appropriate
191
+ # templates and performing placeholder substitution.
65
192
  #
66
- # @param [Lumberjack::LogEntry] entry The entry to convert to a string.
67
- # @return [String] The entry converted to a string.
193
+ # @param entry [Lumberjack::LogEntry] The log entry to format
194
+ # @return [String] The formatted log entry string
68
195
  def call(entry)
69
196
  return entry unless entry.is_a?(LogEntry)
70
197
 
@@ -76,58 +203,121 @@ module Lumberjack
76
203
  end
77
204
 
78
205
  formatted_time = @time_formatter.call(entry.time) if @template_include_time
79
- format_args = [formatted_time, entry.severity_label, entry.progname, entry.pid, first_line]
80
- tag_arguments = tag_args(entry.tags, @first_line_tags)
81
- message = (@first_line_template % (format_args + tag_arguments))
82
- message.rstrip! if message.end_with?(" ")
206
+ severity = entry.severity_data
207
+ format_args = [
208
+ formatted_time,
209
+ severity.label,
210
+ severity.padded_label,
211
+ severity.char,
212
+ severity.emoji,
213
+ severity.level,
214
+ entry.progname,
215
+ entry.pid,
216
+ first_line
217
+ ]
218
+ append_attribute_args!(format_args, entry.attributes, @first_line_attributes)
219
+ message = (@first_line_template % format_args)
83
220
 
84
221
  if additional_lines && !additional_lines.empty?
85
- tag_arguments = tag_args(entry.tags, @additional_line_tags) unless @additional_line_tags == @first_line_tags
222
+ format_args.slice!(9, format_args.size)
223
+ append_attribute_args!(format_args, entry.attributes, @additional_line_attributes)
224
+
225
+ message_length = message.length
226
+ message.chomp!(Lumberjack::LINE_SEPARATOR)
227
+ chomped = message.length != message_length
228
+
86
229
  additional_lines.each do |line|
87
- format_args[format_args.size - 1] = line
88
- line_message = (@additional_line_template % (format_args + tag_arguments)).rstrip
89
- line_message.rstrip! if line_message.end_with?(" ")
230
+ format_args[8] = line
231
+ line_message = @additional_line_template % format_args
90
232
  message << line_message
91
233
  end
234
+
235
+ message << Lumberjack::LINE_SEPARATOR if chomped
92
236
  end
237
+
238
+ message = Template.colorize_entry(message, entry) if @colorize
239
+
93
240
  message
94
241
  end
95
242
 
96
243
  private
97
244
 
98
- def tag_args(tags, tag_vars)
99
- return [nil] * (tag_vars.size + 1) if tags.nil? || tags.size == 0
245
+ # Build the arguments array for sprintf formatting by appending attribute values.
246
+ # This method handles both the general :attributes placeholder and specific
247
+ # attribute placeholders defined in the template.
248
+ #
249
+ # @param args [Array] The existing format arguments array to modify
250
+ # @param attributes [Hash, nil] The log entry attributes hash
251
+ # @param attribute_vars [Array<String>] List of specific attribute names used in template
252
+ # @return [void]
253
+ def append_attribute_args!(args, attributes, attribute_vars)
254
+ if attributes.nil? || attributes.size == 0
255
+ (attribute_vars.length + 1).times { args << nil }
256
+ return
257
+ end
100
258
 
101
- tags_string = +""
102
- Lumberjack::Utils.flatten_tags(tags).each do |name, value|
103
- unless value.nil? || tag_vars.include?(name)
259
+ attributes_string = +""
260
+ attributes.each do |name, value|
261
+ unless value.nil? || attribute_vars.include?(name)
104
262
  value = value.to_s
105
263
  value = value.gsub(Lumberjack::LINE_SEPARATOR, " ") if value.include?(Lumberjack::LINE_SEPARATOR)
106
- tags_string << "[#{name}:#{value}] "
264
+ attributes_string << " "
265
+ attributes_string << @attribute_template % [name, value]
107
266
  end
108
267
  end
109
268
 
110
- args = [tags_string.chop]
111
- tag_vars.each do |name|
112
- args << tags[name]
269
+ args << attributes_string
270
+ attribute_vars.each do |name|
271
+ args << attributes[name]
113
272
  end
114
- args
115
273
  end
116
274
 
117
- # Compile the template string into a value that can be used with sprintf.
275
+ # Parse and compile a template string into a sprintf-compatible format string
276
+ # and extract attribute variable names. This method handles placeholder
277
+ # substitution and escape sequence processing.
278
+ #
279
+ # @param template [String] The raw template string with placeholders
280
+ # @return [Array<String, Array<String>>] A tuple of [compiled_template, attribute_vars]
118
281
  def compile(template) # :nodoc:
119
- tag_vars = []
282
+ template = template.gsub(/ ({{ *)attributes( *}})/, "\\1attributes\\2")
283
+ template = template.gsub(/%(?!%)/, "%%")
284
+
285
+ attribute_vars = []
120
286
  template = template.gsub(PLACEHOLDER_PATTERN) do |match|
121
- var_name = match.sub("{", "").sub("}", "")
287
+ var_name = match.sub(/{{ */, "").sub(/ *}}/, "")
288
+ position = TEMPLATE_ARGUMENT_ORDER.index(var_name)
289
+ if position
290
+ "%#{position + 1}$s"
291
+ else
292
+ attribute_vars << var_name
293
+ "%#{TEMPLATE_ARGUMENT_ORDER.size + attribute_vars.size}$s"
294
+ end
295
+ end
296
+ [template, attribute_vars]
297
+ end
298
+
299
+ # Parse and compile a template string into a sprintf-compatible format string
300
+ # and extract attribute variable names. This method handles placeholder
301
+ # substitution and escape sequence processing.
302
+ #
303
+ # @param template [String] The raw template string with placeholders
304
+ # @return [Array<String, Array<String>>] A tuple of [compiled_template, attribute_vars]
305
+ def compile_v1(template) # :nodoc:
306
+ template = template.gsub(":tags", ":attributes").gsub(/ ?:attributes/, ":attributes")
307
+ template = template.gsub(/%(?!%)/, "%%")
308
+
309
+ attribute_vars = []
310
+ template = template.gsub(V1_PLACEHOLDER_PATTERN) do |match|
311
+ var_name = match[1, match.length]
122
312
  position = TEMPLATE_ARGUMENT_ORDER.index(var_name)
123
313
  if position
124
314
  "%#{position + 1}$s"
125
315
  else
126
- tag_vars << var_name[1, var_name.length]
127
- "%#{TEMPLATE_ARGUMENT_ORDER.size + tag_vars.size}$s"
316
+ attribute_vars << var_name
317
+ "%#{TEMPLATE_ARGUMENT_ORDER.size + attribute_vars.size}$s"
128
318
  end
129
319
  end
130
- [template, tag_vars]
320
+ [template, attribute_vars]
131
321
  end
132
322
  end
133
323
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ class TemplateRegistry
5
+ @templates = {}
6
+
7
+ class << self
8
+ # Register a log template class with a symbol.
9
+ #
10
+ # @param name [Symbol] The name of the template.
11
+ # @param template [String, Class, #call] The log template to register.
12
+ def add(name, template)
13
+ unless template.is_a?(String) || template.is_a?(Class) || template.respond_to?(:call)
14
+ raise ArgumentError.new("template must be a String, Class, or respond to :call")
15
+ end
16
+
17
+ @templates[name.to_sym] = template
18
+ end
19
+
20
+ # Remove a template from the registry. raise ArgumentError.new("template must be a String, Class, or respond to :call")
21
+ #
22
+ # @param name [Symbol] The name of the template to remove.
23
+ # @return [void]
24
+ def remove(name)
25
+ @templates.delete(name.to_sym)
26
+ end
27
+
28
+ # Check if a template is registered.
29
+ #
30
+ # @param name [Symbol] The name of the template.
31
+ # @return [Boolean] True if the template is registered, false otherwise.
32
+ def registered?(name)
33
+ @templates.include(name.to_sym)
34
+ end
35
+
36
+ # Get a registered log template class by its symbol.
37
+ #
38
+ # @param name [Symbol] The symbol of the registered log template class.
39
+ # @return [Class, nil] The registered log template class, or nil if not found.
40
+ def template(name, options = {})
41
+ template = @templates[name.to_sym]
42
+ if template.is_a?(Class)
43
+ template.new(options)
44
+ elsif template.is_a?(String)
45
+ template_options = options.slice(:additional_lines, :time_format, :attribute_format, :colorize)
46
+ Template.new(template, **template_options)
47
+ else
48
+ template
49
+ end
50
+ end
51
+
52
+ # List all registered log template symbols.
53
+ #
54
+ # @return [Array<Symbol>] An array of all registered log template symbols.
55
+ def registered_templates
56
+ @templates.dup
57
+ end
58
+ end
59
+ end
60
+ end