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.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +524 -176
- data/CHANGELOG.md +89 -0
- data/README.md +604 -211
- data/UPGRADE_GUIDE.md +80 -0
- data/VERSION +1 -1
- data/lib/lumberjack/attribute_formatter.rb +451 -0
- data/lib/lumberjack/attributes_helper.rb +100 -0
- data/lib/lumberjack/context.rb +120 -23
- data/lib/lumberjack/context_logger.rb +620 -0
- data/lib/lumberjack/device/buffer.rb +209 -0
- data/lib/lumberjack/device/date_rolling_log_file.rb +10 -62
- data/lib/lumberjack/device/log_file.rb +76 -29
- data/lib/lumberjack/device/logger_wrapper.rb +137 -0
- data/lib/lumberjack/device/multi.rb +92 -30
- data/lib/lumberjack/device/null.rb +26 -8
- data/lib/lumberjack/device/size_rolling_log_file.rb +13 -54
- data/lib/lumberjack/device/test.rb +337 -0
- data/lib/lumberjack/device/writer.rb +184 -176
- data/lib/lumberjack/device.rb +134 -15
- data/lib/lumberjack/device_registry.rb +90 -0
- data/lib/lumberjack/entry_formatter.rb +357 -0
- data/lib/lumberjack/fiber_locals.rb +55 -0
- data/lib/lumberjack/forked_logger.rb +143 -0
- data/lib/lumberjack/formatter/date_time_formatter.rb +14 -3
- data/lib/lumberjack/formatter/exception_formatter.rb +12 -2
- data/lib/lumberjack/formatter/id_formatter.rb +13 -1
- data/lib/lumberjack/formatter/inspect_formatter.rb +14 -1
- data/lib/lumberjack/formatter/multiply_formatter.rb +10 -0
- data/lib/lumberjack/formatter/object_formatter.rb +13 -1
- data/lib/lumberjack/formatter/pretty_print_formatter.rb +15 -2
- data/lib/lumberjack/formatter/redact_formatter.rb +18 -3
- data/lib/lumberjack/formatter/round_formatter.rb +12 -0
- data/lib/lumberjack/formatter/string_formatter.rb +9 -1
- data/lib/lumberjack/formatter/strip_formatter.rb +13 -1
- data/lib/lumberjack/formatter/structured_formatter.rb +18 -2
- data/lib/lumberjack/formatter/tagged_message.rb +10 -32
- data/lib/lumberjack/formatter/tags_formatter.rb +32 -0
- data/lib/lumberjack/formatter/truncate_formatter.rb +8 -1
- data/lib/lumberjack/formatter.rb +271 -141
- data/lib/lumberjack/formatter_registry.rb +84 -0
- data/lib/lumberjack/io_compatibility.rb +133 -0
- data/lib/lumberjack/local_log_template.rb +209 -0
- data/lib/lumberjack/log_entry.rb +154 -79
- data/lib/lumberjack/log_entry_matcher/score.rb +276 -0
- data/lib/lumberjack/log_entry_matcher.rb +126 -0
- data/lib/lumberjack/logger.rb +328 -556
- data/lib/lumberjack/message_attributes.rb +38 -0
- data/lib/lumberjack/rack/context.rb +66 -15
- data/lib/lumberjack/rack.rb +0 -2
- data/lib/lumberjack/remap_attribute.rb +24 -0
- data/lib/lumberjack/severity.rb +52 -15
- data/lib/lumberjack/tag_context.rb +8 -71
- data/lib/lumberjack/tag_formatter.rb +22 -188
- data/lib/lumberjack/tags.rb +15 -21
- data/lib/lumberjack/template.rb +252 -62
- data/lib/lumberjack/template_registry.rb +60 -0
- data/lib/lumberjack/utils.rb +198 -48
- data/lib/lumberjack.rb +167 -59
- data/lumberjack.gemspec +4 -2
- metadata +41 -15
- data/lib/lumberjack/device/rolling_log_file.rb +0 -145
- data/lib/lumberjack/rack/request_id.rb +0 -31
- data/lib/lumberjack/rack/unit_of_work.rb +0 -21
- data/lib/lumberjack/tagged_logger_support.rb +0 -81
- data/lib/lumberjack/tagged_logging.rb +0 -29
@@ -1,206 +1,214 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Lumberjack
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
4
|
+
# A versatile logging device that writes formatted log entries to IO streams.
|
5
|
+
# This device serves as the foundation for most output-based logging, converting
|
6
|
+
# LogEntry objects into formatted strings using configurable templates and
|
7
|
+
# writing them to any IO-compatible stream.
|
8
|
+
#
|
9
|
+
# The Writer device supports extensive customization through templates, encoding
|
10
|
+
# options, stream management, and error handling. It can write to files, console
|
11
|
+
# output, network streams, or any object that implements the IO interface.
|
12
|
+
#
|
13
|
+
# Templates can be either string-based (compiled into Template objects) or
|
14
|
+
# callable objects (Procs, lambdas) for maximum flexibility. The device handles
|
15
|
+
# character encoding, whitespace normalization, and provides robust error
|
16
|
+
# recovery when stream operations fail.
|
17
|
+
#
|
18
|
+
# @see Template
|
19
|
+
class Device::Writer < Device
|
20
|
+
EDGE_WHITESPACE_PATTERN = /\A\s|[ \t\f\v][\r\n]*\z/
|
21
|
+
|
22
|
+
# Initialize a new Writer device with configurable formatting and stream options.
|
23
|
+
# The device supports multiple template types, encoding control, and stream
|
24
|
+
# behavior configuration for flexible output handling.
|
25
|
+
#
|
26
|
+
# @param stream [IO, #write] The target stream for log output. Can be any object
|
27
|
+
# that responds to write(), including File objects, STDOUT/STDERR, StringIO,
|
28
|
+
# network streams, or custom IO-like objects
|
29
|
+
# @param options [Hash] Configuration options for the writer device
|
30
|
+
#
|
31
|
+
# @option options [String, Proc, nil] :template The formatting template for log entries.
|
32
|
+
# - String: Compiled into a Template object (default: "[:time :severity :progname(:pid)] :message")
|
33
|
+
# - Proc: Called with LogEntry, should return formatted string
|
34
|
+
# - nil: Uses default template
|
35
|
+
#
|
36
|
+
# @option options [Logger::Formatter] :standard_logger_formatter Use a Ruby Logger
|
37
|
+
# formatter for compatibility with existing logging code
|
38
|
+
#
|
39
|
+
# @option options [String, nil] :additional_lines Template for formatting additional
|
40
|
+
# lines in multi-line messages (default: "\n :message")
|
41
|
+
#
|
42
|
+
# @option options [String, Symbol] :time_format Format for timestamps in templates.
|
43
|
+
# Accepts strftime patterns or :milliseconds/:microseconds shortcuts
|
44
|
+
#
|
45
|
+
# @option options [String] :attribute_format Printf-style format for attributes
|
46
|
+
# with exactly two %s placeholders for name and value (default: "[%s:%s]")
|
47
|
+
#
|
48
|
+
# @option options [Boolean] :autoflush (true) Whether to automatically flush
|
49
|
+
# the stream after each write for immediate output
|
50
|
+
#
|
51
|
+
# @option options [Boolean] :binmode (false) Whether to treat the stream as
|
52
|
+
# binary, skipping UTF-8 encoding conversion
|
53
|
+
#
|
54
|
+
# @option options [Boolean] :colorize (false) Whether to colorize log output
|
55
|
+
def initialize(stream, options = {})
|
56
|
+
@stream = stream
|
57
|
+
@stream.sync = true if @stream.respond_to?(:sync=) && options[:autoflush] != false
|
25
58
|
|
26
|
-
|
27
|
-
@values << string
|
28
|
-
@size += string.size
|
29
|
-
end
|
59
|
+
@binmode = options[:binmode]
|
30
60
|
|
31
|
-
|
32
|
-
|
33
|
-
|
61
|
+
if options[:standard_logger_formatter]
|
62
|
+
@template = Template::StandardFormatterTemplate.new(options[:standard_logger_formatter])
|
63
|
+
else
|
64
|
+
template = options[:template]
|
34
65
|
|
35
|
-
|
36
|
-
return nil if @values.empty?
|
37
|
-
popped = @values
|
38
|
-
clear
|
39
|
-
popped
|
40
|
-
end
|
66
|
+
template = TemplateRegistry.template(template, options) if template.is_a?(Symbol)
|
41
67
|
|
42
|
-
|
43
|
-
|
44
|
-
@size = 0
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# Create a new device to write log entries to a stream. Entries are converted to strings
|
49
|
-
# using a Template. The template can be specified using the :template option. This can
|
50
|
-
# either be a Proc or a string that will compile into a Template object.
|
51
|
-
#
|
52
|
-
# If the template is a Proc, it should accept an LogEntry as its only argument and output a string.
|
53
|
-
#
|
54
|
-
# If the template is a template string, it will be used to create a Template. The
|
55
|
-
# :additional_lines and :time_format options will be passed through to the
|
56
|
-
# Template constuctor.
|
57
|
-
#
|
58
|
-
# The default template is "[:time :severity :progname(:pid)] :message"
|
59
|
-
# with additional lines formatted as "\n :message".
|
60
|
-
#
|
61
|
-
# The size of the internal buffer in bytes can be set by providing :buffer_size (defaults to 32K).
|
62
|
-
#
|
63
|
-
# @param [IO] stream The stream to write log entries to.
|
64
|
-
# @param [Hash] options The options for the device.
|
65
|
-
def initialize(stream, options = {})
|
66
|
-
@lock = Mutex.new
|
67
|
-
@stream = stream
|
68
|
-
@stream.sync = true if @stream.respond_to?(:sync=)
|
69
|
-
@buffer = Buffer.new
|
70
|
-
@buffer_size = options[:buffer_size] || 0
|
71
|
-
template = options[:template] || DEFAULT_FIRST_LINE_TEMPLATE
|
72
|
-
if template.respond_to?(:call)
|
73
|
-
@template = template
|
68
|
+
@template = if template.respond_to?(:call)
|
69
|
+
template
|
74
70
|
else
|
75
|
-
|
76
|
-
|
71
|
+
Template.new(
|
72
|
+
template,
|
73
|
+
additional_lines: options[:additional_lines],
|
74
|
+
time_format: options[:time_format],
|
75
|
+
attribute_format: options[:attribute_format],
|
76
|
+
colorize: options[:colorize]
|
77
|
+
)
|
77
78
|
end
|
78
79
|
end
|
80
|
+
end
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
82
|
+
# Write a log entry to the stream with automatic formatting and error handling.
|
83
|
+
# The entry is converted to a string using the configured template, processed
|
84
|
+
# for encoding and whitespace, and written to the stream with robust error recovery.
|
85
|
+
#
|
86
|
+
# @param entry [LogEntry, String] The log entry to write. LogEntry objects are
|
87
|
+
# formatted using the template, while strings are written directly after
|
88
|
+
# encoding and whitespace processing
|
89
|
+
# @return [void]
|
90
|
+
def write(entry)
|
91
|
+
string = (entry.is_a?(LogEntry) ? @template.call(entry) : entry)
|
92
|
+
return if string.nil?
|
93
|
+
|
94
|
+
if !@binmode && string.encoding != Encoding::UTF_8
|
95
|
+
string = string.encode("UTF-8", invalid: :replace, undef: :replace)
|
88
96
|
end
|
89
97
|
|
90
|
-
|
91
|
-
|
92
|
-
# @param [LogEntry, String] entry The entry to write to the stream.
|
93
|
-
# @return [void]
|
94
|
-
def write(entry)
|
95
|
-
string = (entry.is_a?(LogEntry) ? @template.call(entry) : entry)
|
96
|
-
return if string.nil?
|
97
|
-
string = string.strip
|
98
|
-
return if string.length == 0
|
99
|
-
|
100
|
-
unless string.encoding == Encoding::UTF_8
|
101
|
-
string = string.encode("UTF-8", invalid: :replace, undef: :replace)
|
102
|
-
end
|
98
|
+
string = string.strip if string.match?(EDGE_WHITESPACE_PATTERN)
|
99
|
+
return if string.length == 0 || string == Lumberjack::LINE_SEPARATOR
|
103
100
|
|
104
|
-
|
105
|
-
|
106
|
-
@buffer << string
|
107
|
-
end
|
108
|
-
flush if @buffer.size >= buffer_size
|
109
|
-
else
|
110
|
-
flush if respond_to?(:before_flush, true)
|
111
|
-
write_to_stream(string)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# Close the underlying stream.
|
116
|
-
#
|
117
|
-
# @return [void]
|
118
|
-
def close
|
119
|
-
flush
|
120
|
-
stream.close
|
121
|
-
end
|
101
|
+
write_to_stream(string)
|
102
|
+
end
|
122
103
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
write_to_stream(lines) if lines
|
133
|
-
end
|
104
|
+
# Close the underlying stream and release any associated resources. This method
|
105
|
+
# ensures all buffered data is flushed before closing the stream, providing
|
106
|
+
# clean shutdown behavior for file handles and network connections.
|
107
|
+
#
|
108
|
+
# @return [void]
|
109
|
+
def close
|
110
|
+
flush
|
111
|
+
stream.close
|
112
|
+
end
|
134
113
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
114
|
+
# Flush the underlying stream to ensure all buffered data is written to the
|
115
|
+
# destination. This method is safe to call on streams that don't support
|
116
|
+
# flushing, making it suitable for various IO types.
|
117
|
+
#
|
118
|
+
# @return [void]
|
119
|
+
def flush
|
120
|
+
stream.flush if stream.respond_to?(:flush)
|
121
|
+
end
|
141
122
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
123
|
+
# Get the current datetime format from the template if supported. Returns the
|
124
|
+
# format string used for timestamp formatting in log entries.
|
125
|
+
#
|
126
|
+
# @return [String, nil] The datetime format string if the template supports it,
|
127
|
+
# or nil if the template doesn't provide datetime formatting
|
128
|
+
def datetime_format
|
129
|
+
@template.datetime_format if @template.respond_to?(:datetime_format)
|
130
|
+
end
|
151
131
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
132
|
+
# Set the datetime format on the template if supported. This allows dynamic
|
133
|
+
# reconfiguration of timestamp formatting without recreating the device.
|
134
|
+
#
|
135
|
+
# @param format [String] The datetime format string (strftime pattern) to
|
136
|
+
# apply to the template for timestamp formatting
|
137
|
+
# @return [void]
|
138
|
+
def datetime_format=(format)
|
139
|
+
if @template.respond_to?(:datetime_format=)
|
140
|
+
@template.datetime_format = format
|
157
141
|
end
|
142
|
+
end
|
158
143
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
144
|
+
# Access the underlying IO stream for direct manipulation or compatibility
|
145
|
+
# with code expecting Logger device interface. This method provides the
|
146
|
+
# raw stream object for advanced use cases.
|
147
|
+
#
|
148
|
+
# @return [IO] The underlying stream object used for output
|
149
|
+
# @api private
|
150
|
+
def dev
|
151
|
+
stream
|
152
|
+
end
|
163
153
|
|
164
|
-
|
165
|
-
|
154
|
+
# Get the file system path of the underlying stream if available. This method
|
155
|
+
# is useful for monitoring, log rotation, or any operations that need to
|
156
|
+
# work with the actual file path.
|
157
|
+
#
|
158
|
+
# @return [String, nil] The file system path if the stream is file-based,
|
159
|
+
# or nil for non-file streams (STDOUT, StringIO, network streams, etc.)
|
160
|
+
def path
|
161
|
+
stream.path if stream.respond_to?(:path)
|
162
|
+
end
|
166
163
|
|
167
|
-
|
164
|
+
# The underlying stream object that is being written to.
|
165
|
+
#
|
166
|
+
# @return [IO] The current stream object
|
167
|
+
attr_accessor :stream
|
168
168
|
|
169
|
-
|
170
|
-
return if lines.empty?
|
171
|
-
lines = lines.first if lines.is_a?(Array) && lines.size == 1
|
172
|
-
out = if lines.is_a?(Array)
|
173
|
-
"#{lines.join(Lumberjack::LINE_SEPARATOR)}#{Lumberjack::LINE_SEPARATOR}"
|
174
|
-
else
|
175
|
-
"#{lines}#{Lumberjack::LINE_SEPARATOR}"
|
176
|
-
end
|
169
|
+
private
|
177
170
|
|
171
|
+
# Write a formatted line to the stream with robust error handling. This method
|
172
|
+
# ensures proper line termination, handles IO errors gracefully, and provides
|
173
|
+
# fallback error reporting to STDERR when the primary stream fails.
|
174
|
+
#
|
175
|
+
# @param line [String] The formatted log line to write
|
176
|
+
# @return [void]
|
177
|
+
def write_to_stream(line)
|
178
|
+
out = line.end_with?(Lumberjack::LINE_SEPARATOR) ? line : "#{line}#{Lumberjack::LINE_SEPARATOR}"
|
179
|
+
begin
|
178
180
|
begin
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
# want to lock the thread on every stream write call.
|
185
|
-
@lock.synchronize do
|
186
|
-
if stream.closed?
|
187
|
-
raise e
|
188
|
-
else
|
189
|
-
stream.write(out)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
begin
|
194
|
-
stream.flush
|
195
|
-
rescue
|
196
|
-
nil
|
197
|
-
end
|
198
|
-
rescue => e
|
199
|
-
$stderr.write("#{e.class.name}: #{e.message}#{" at " + e.backtrace.first if e.backtrace}")
|
200
|
-
$stderr.write(out)
|
201
|
-
$stderr.flush
|
181
|
+
stream.write(out)
|
182
|
+
rescue IOError => e
|
183
|
+
raise e if stream.closed?
|
184
|
+
|
185
|
+
stream.write(out)
|
202
186
|
end
|
187
|
+
rescue => e
|
188
|
+
$stderr.write(error_message(e))
|
189
|
+
$stderr.write(out)
|
203
190
|
end
|
204
191
|
end
|
192
|
+
|
193
|
+
# Generate a detailed error message for logging failures. This method creates
|
194
|
+
# informative error messages that include exception details and backtrace
|
195
|
+
# information for debugging stream write failures.
|
196
|
+
#
|
197
|
+
# @param e [Exception] The exception that occurred during stream operations
|
198
|
+
# @return [String] A formatted error message with exception details
|
199
|
+
def error_message(e)
|
200
|
+
"#{e.class.name}: #{e.message}#{" at " + e.backtrace.first if e.backtrace}#{Lumberjack::LINE_SEPARATOR}"
|
201
|
+
end
|
202
|
+
|
203
|
+
# Create a test log template.
|
204
|
+
def test_log_template(options)
|
205
|
+
kwargs = {
|
206
|
+
exclude_attributes: options[:exclude_attributes],
|
207
|
+
exclude_progname: options[:exclude_progname],
|
208
|
+
exclude_pid: options[:exclude_pid],
|
209
|
+
exclude_time: options[:exclude_time]
|
210
|
+
}
|
211
|
+
TestLogTemplate.new(**kwargs.compact)
|
212
|
+
end
|
205
213
|
end
|
206
214
|
end
|
data/lib/lumberjack/device.rb
CHANGED
@@ -1,57 +1,176 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "device_registry"
|
4
|
+
|
3
5
|
module Lumberjack
|
4
|
-
#
|
5
|
-
#
|
6
|
+
# Abstract base class defining the interface for logging output devices.
|
7
|
+
# Devices are responsible for the final output of log entries to various
|
8
|
+
# destinations such as files, streams, databases, or external services.
|
9
|
+
#
|
10
|
+
# This class establishes the contract that all concrete device implementations
|
11
|
+
# must follow, with the +write+ method being the only required implementation.
|
12
|
+
# Additional lifecycle methods (+close+, +flush+, +reopen+) and configuration
|
13
|
+
# methods (+datetime_format+) are optional but provide standardized interfaces
|
14
|
+
# for device management.
|
15
|
+
#
|
16
|
+
# The device architecture allows for flexible log output handling while
|
17
|
+
# maintaining consistent behavior across different output destinations.
|
18
|
+
# Devices receive formatted LogEntry objects and are responsible for their
|
19
|
+
# final serialization and delivery.
|
20
|
+
#
|
21
|
+
# @abstract Subclass and implement {#write} to create a concrete device
|
22
|
+
# @see Lumberjack::Device::Writer File-based output device
|
23
|
+
# @see Lumberjack::Device::LoggerWrapper Ruby Logger compatibility device
|
24
|
+
# @see Lumberjack::Device::Multi Multiple device routing
|
25
|
+
# @see Lumberjack::Device::Null Silent device for testing
|
26
|
+
# @see Lumberjack::Device::Test In-memory device for testing
|
6
27
|
class Device
|
7
28
|
require_relative "device/writer"
|
8
29
|
require_relative "device/log_file"
|
9
|
-
require_relative "device/
|
10
|
-
require_relative "device/date_rolling_log_file"
|
11
|
-
require_relative "device/size_rolling_log_file"
|
30
|
+
require_relative "device/logger_wrapper"
|
12
31
|
require_relative "device/multi"
|
13
32
|
require_relative "device/null"
|
33
|
+
require_relative "device/test"
|
34
|
+
require_relative "device/buffer"
|
35
|
+
require_relative "device/size_rolling_log_file"
|
36
|
+
require_relative "device/date_rolling_log_file"
|
37
|
+
|
38
|
+
class << self
|
39
|
+
# Open a logging device with the given options.
|
40
|
+
#
|
41
|
+
# @param device [nil, Symbol, String, File, IO, Array, Lumberjack::Device, ContextLogger] The device to open.
|
42
|
+
# The device can be:
|
43
|
+
# - +nil+: returns a +Device::Null+ instance that discards all log entries.
|
44
|
+
# - +Symbol+: looks up the device in the +DeviceRegistry+ and creates a new instance with the provided options.
|
45
|
+
# - +String+ or +Pathname+: treated as a file path and opens a +Device::LogFile+.
|
46
|
+
# - +File+: opens a +Device::LogFile+ for the given file stream.
|
47
|
+
# - +IO+: opens a +Device::Writer+ wrapping the given IO stream.
|
48
|
+
# - +Lumberjack::Device+: returns the device instance as-is.
|
49
|
+
# - +ContextLogger+: wraps the logger in a +Device::LoggerWrapper+.
|
50
|
+
# - +Array+: each element is treated as a device specification and opened recursively,
|
51
|
+
# returning a +Device::Multi+ that routes log entries to all specified devices. Each
|
52
|
+
# device can have its own options hash if passed as a two-element array +[device, options]+.
|
53
|
+
# @param options [Hash] Options to pass to the device constructor.
|
54
|
+
# @return [Lumberjack::Device] The opened device instance.
|
55
|
+
#
|
56
|
+
# @example Open a file-based device
|
57
|
+
# device = Lumberjack::Device.open_device("/var/log/myapp.log", shift_age: "daily")
|
58
|
+
#
|
59
|
+
# @example Open a stream-based device
|
60
|
+
# device = Lumberjack::Device.open_device($stdout)
|
61
|
+
#
|
62
|
+
# @example Open a device from the registry
|
63
|
+
# device = Lumberjack::Device.open_device(:syslog)
|
64
|
+
#
|
65
|
+
# @example Open multiple devices
|
66
|
+
# device = Lumberjack::Device.open_device([["/var/log/app.log", {shift_age: "daily"}], $stdout])
|
67
|
+
#
|
68
|
+
# @example Wrap another logger
|
69
|
+
# device = Lumberjack::Device.open_device(Lumberjack::Logger.new($stdout))
|
70
|
+
def open_device(device, options = {})
|
71
|
+
device = device.to_s if device.is_a?(Pathname)
|
72
|
+
|
73
|
+
if device.nil?
|
74
|
+
Device::Null.new
|
75
|
+
elsif device.is_a?(Device)
|
76
|
+
device
|
77
|
+
elsif device.is_a?(Symbol)
|
78
|
+
DeviceRegistry.new_device(device, options)
|
79
|
+
elsif device.is_a?(ContextLogger) || device.is_a?(::Logger)
|
80
|
+
Device::LoggerWrapper.new(device)
|
81
|
+
elsif device.is_a?(Array)
|
82
|
+
devices = device.collect do |dev, dev_options|
|
83
|
+
dev_options = dev_options.is_a?(Hash) ? options.merge(dev_options) : options
|
84
|
+
open_device(dev, dev_options)
|
85
|
+
end
|
86
|
+
Device::Multi.new(devices)
|
87
|
+
elsif io_but_not_file_stream?(device)
|
88
|
+
Device::Writer.new(device, options)
|
89
|
+
else
|
90
|
+
Device::LogFile.new(device, options)
|
91
|
+
end
|
92
|
+
end
|
14
93
|
|
15
|
-
|
94
|
+
private
|
95
|
+
|
96
|
+
def io_but_not_file_stream?(object)
|
97
|
+
return false if object.is_a?(File)
|
98
|
+
return false unless object.respond_to?(:write)
|
99
|
+
return true if object.respond_to?(:tty?) && object.tty?
|
100
|
+
return false if object.respond_to?(:path) && object.path
|
101
|
+
|
102
|
+
true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Write a log entry to the device. This is the core method that all device
|
107
|
+
# implementations must provide. The method receives a fully formatted
|
108
|
+
# LogEntry object and is responsible for outputting it to the target
|
109
|
+
# destination.
|
16
110
|
#
|
17
|
-
# @param [Lumberjack::LogEntry]
|
111
|
+
# @param entry [Lumberjack::LogEntry] The log entry to write to the device
|
18
112
|
# @return [void]
|
113
|
+
# @abstract Subclasses must implement this method
|
114
|
+
# @raise [NotImplementedError] If called on the abstract base class
|
19
115
|
def write(entry)
|
20
116
|
raise NotImplementedError
|
21
117
|
end
|
22
118
|
|
23
|
-
#
|
119
|
+
# Close the device and release any resources. The default implementation
|
120
|
+
# calls flush to ensure any buffered data is written before closing.
|
121
|
+
# Subclasses should override this method if they need to perform specific
|
122
|
+
# cleanup operations such as closing file handles or network connections.
|
24
123
|
#
|
25
124
|
# @return [void]
|
26
125
|
def close
|
27
126
|
flush
|
28
127
|
end
|
29
128
|
|
30
|
-
#
|
129
|
+
# Reopen the device, optionally with a new log destination. The default
|
130
|
+
# implementation calls flush to ensure data consistency. This method is
|
131
|
+
# typically used for log rotation scenarios or when changing output
|
132
|
+
# destinations dynamically.
|
31
133
|
#
|
32
|
-
# @param [Object]
|
134
|
+
# @param logdev [Object, nil] Optional new log device or destination
|
33
135
|
# @return [void]
|
34
136
|
def reopen(logdev = nil)
|
35
137
|
flush
|
36
138
|
end
|
37
139
|
|
38
|
-
#
|
140
|
+
# Flush any buffered data to the output destination. The default
|
141
|
+
# implementation is a no-op since not all devices use buffering.
|
142
|
+
# Subclasses that implement buffering should override this method
|
143
|
+
# to ensure data is written to the final destination.
|
39
144
|
#
|
40
145
|
# @return [void]
|
41
146
|
def flush
|
42
147
|
end
|
43
148
|
|
44
|
-
#
|
149
|
+
# Get the current datetime format string used for timestamp formatting.
|
150
|
+
# The default implementation returns nil, indicating no specific format
|
151
|
+
# is set. Subclasses may override this to provide device-specific
|
152
|
+
# timestamp formatting.
|
45
153
|
#
|
46
|
-
# @return [String] The format
|
154
|
+
# @return [String, nil] The datetime format string, or nil if not set
|
47
155
|
def datetime_format
|
48
156
|
end
|
49
157
|
|
50
|
-
#
|
158
|
+
# Set the datetime format string for timestamp formatting. The default
|
159
|
+
# implementation is a no-op. Subclasses that support configurable
|
160
|
+
# timestamp formatting should override this method to store and apply
|
161
|
+
# the specified format.
|
51
162
|
#
|
52
|
-
# @param [String]
|
163
|
+
# @param format [String, nil] The datetime format string to use for timestamps
|
53
164
|
# @return [void]
|
54
165
|
def datetime_format=(format)
|
55
166
|
end
|
167
|
+
|
168
|
+
# Expose the underlying stream if any.
|
169
|
+
#
|
170
|
+
# @return [IO, Lumberjacke::Device, nil]
|
171
|
+
# @api private
|
172
|
+
def dev
|
173
|
+
self
|
174
|
+
end
|
56
175
|
end
|
57
176
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
# The device registry is used for setting up names to represent Device classes. It is used
|
5
|
+
# in the constructor for Lumberjack::Logger and allows passing in a symbol to reference a
|
6
|
+
# device.
|
7
|
+
#
|
8
|
+
# Devices must have a constructor that accepts the options hash as its sole argument in order
|
9
|
+
# to use the device registry.
|
10
|
+
#
|
11
|
+
# The values :stdout and :stderr are registered by default and map to the standard output
|
12
|
+
# and standard error streams, respectively.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# Lumberjack::Device.register(:my_device, MyDevice)
|
17
|
+
# logger = Lumberjack::Logger.new(:my_device)
|
18
|
+
module DeviceRegistry
|
19
|
+
@registry = {stdout: :stdout, stderr: :stderr}
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Register a device name. Device names can be used to associate a symbol with a device
|
23
|
+
# class. The symbol can then be passed to Logger as the device argument.
|
24
|
+
#
|
25
|
+
# Registered devices must take only one argument and that is the options hash for the
|
26
|
+
# device options.
|
27
|
+
#
|
28
|
+
# @param name [Symbol] The name of the device
|
29
|
+
# @param klass [Class] The device class to register
|
30
|
+
# @return [void]
|
31
|
+
def add(name, klass)
|
32
|
+
raise ArgumentError.new("name must be a symbol") unless name.is_a?(Symbol)
|
33
|
+
|
34
|
+
@registry[name] = klass
|
35
|
+
end
|
36
|
+
|
37
|
+
# Remove a device from the registry.
|
38
|
+
#
|
39
|
+
# @param name [Symbol] The name of the device to remove
|
40
|
+
# @return [void]
|
41
|
+
def remove(name)
|
42
|
+
@registry.delete(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if a device is registered.
|
46
|
+
#
|
47
|
+
# @param name [Symbol] The name of the device
|
48
|
+
# @return [Boolean] True if the device is registered, false otherwise
|
49
|
+
def registered?(name)
|
50
|
+
@registry.include?(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Instantiate a new device with the specified options from the device registry.
|
54
|
+
#
|
55
|
+
# @param name [Symbol] The name of the device
|
56
|
+
# @param options [Hash] The device options
|
57
|
+
# @return [Lumberjack::Device]
|
58
|
+
def new_device(name, options)
|
59
|
+
klass = device_class(name)
|
60
|
+
unless klass
|
61
|
+
valid_names = @registry.keys.map(&:inspect).join(", ")
|
62
|
+
raise ArgumentError.new("#{name.inspect} is not registered as a device name; valid names are: #{valid_names}")
|
63
|
+
end
|
64
|
+
|
65
|
+
if klass == :stdout
|
66
|
+
Device::Writer.new($stdout, options)
|
67
|
+
elsif klass == :stderr
|
68
|
+
Device::Writer.new($stderr, options)
|
69
|
+
else
|
70
|
+
klass.new(options)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Retrieve the class registered with the given name or nil if the name is not defined.
|
75
|
+
#
|
76
|
+
# @param name [Symbol] The name of the device
|
77
|
+
# @return [Class, nil] The registered device class or nil if not found
|
78
|
+
def device_class(name)
|
79
|
+
@registry[name]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return the map of registered device class names.
|
83
|
+
#
|
84
|
+
# @return [Hash]
|
85
|
+
def registered_devices
|
86
|
+
@registry.dup
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|