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
data/ARCHITECTURE.md
CHANGED
@@ -1,244 +1,592 @@
|
|
1
|
-
# Lumberjack
|
1
|
+
# Lumberjack Logging Framework Architecture
|
2
2
|
|
3
|
-
|
3
|
+
This document provides a comprehensive overview of the Lumberjack logging framework architecture, illustrating how the various components work together to provide a flexible, high-performance logging solution for Ruby applications.
|
4
4
|
|
5
5
|
## Overview
|
6
6
|
|
7
|
-
|
7
|
+
Lumberjack is a structured logging framework that extends Ruby's standard Logger with advanced features including:
|
8
8
|
|
9
|
-
- **
|
10
|
-
- **
|
11
|
-
- **
|
12
|
-
- **
|
13
|
-
- **
|
14
|
-
- **
|
15
|
-
- **Context**: Thread-local context for managing tags across log entries
|
16
|
-
- **Severity**: Log level management and filtering
|
9
|
+
- **Structured logging** with attributes (key-value pairs)
|
10
|
+
- **Context isolation** for scoping logging behavior
|
11
|
+
- **Flexible output devices** supporting files, streams, and custom destinations
|
12
|
+
- **Customizable formatters** for messages and attributes
|
13
|
+
- **Thread and fiber safety** for concurrent applications
|
14
|
+
- **Hierarchical logger forking** for component isolation
|
17
15
|
|
18
16
|
## Core Architecture
|
19
17
|
|
18
|
+
The framework follows a layered architecture with clear separation of concerns:
|
19
|
+
|
20
20
|
```mermaid
|
21
21
|
classDiagram
|
22
|
+
%% Core Logger Classes
|
22
23
|
class Logger {
|
23
24
|
+Device device
|
24
|
-
+
|
25
|
-
+
|
25
|
+
+Context context
|
26
|
+
+EntryFormatter formatter
|
27
|
+
+initialize(device, options)
|
28
|
+
+info(message, attributes)
|
29
|
+
+debug(message, attributes)
|
30
|
+
+error(message, attributes)
|
31
|
+
+add_entry(severity, message, progname, attributes)
|
32
|
+
+tag(attributes, &block)
|
33
|
+
+context(&block)
|
34
|
+
+fork(options) ForkedLogger
|
35
|
+
}
|
36
|
+
|
37
|
+
class ContextLogger {
|
38
|
+
<<interface>>
|
39
|
+
+level() Integer
|
40
|
+
+level=(value) void
|
41
|
+
+progname() String
|
42
|
+
+progname=(value) void
|
43
|
+
+add_entry(severity, message, progname, attributes)
|
44
|
+
+tag(attributes, &block)
|
45
|
+
+context(&block)
|
46
|
+
+attributes() Hash
|
47
|
+
}
|
48
|
+
|
49
|
+
class ForkedLogger {
|
50
|
+
+Logger parent_logger
|
51
|
+
+Context context
|
52
|
+
+initialize(parent_logger)
|
53
|
+
+add_entry(severity, message, progname, attributes)
|
54
|
+
}
|
55
|
+
|
56
|
+
%% Context and Attribute Management
|
57
|
+
class Context {
|
58
|
+
+Hash attributes
|
26
59
|
+Integer level
|
27
60
|
+String progname
|
28
|
-
+
|
29
|
-
+initialize(
|
30
|
-
+
|
31
|
-
+
|
32
|
-
+warn(message, progname, tags)
|
33
|
-
+error(message, progname, tags)
|
34
|
-
+fatal(message, progname, tags)
|
35
|
-
+add_entry(severity, message, progname, tags)
|
36
|
-
+tag(tags_hash)
|
37
|
-
+flush()
|
38
|
-
+close()
|
61
|
+
+Integer default_severity
|
62
|
+
+initialize(parent_context)
|
63
|
+
+assign_attributes(attributes)
|
64
|
+
+clear_attributes()
|
39
65
|
}
|
40
66
|
|
67
|
+
class AttributesHelper {
|
68
|
+
+Hash attributes
|
69
|
+
+initialize(attributes)
|
70
|
+
+update(attributes)
|
71
|
+
+delete(*names)
|
72
|
+
+[](key) Object
|
73
|
+
+[]=(key, value)
|
74
|
+
}
|
75
|
+
|
76
|
+
%% Entry and Formatting
|
41
77
|
class LogEntry {
|
42
78
|
+Time time
|
43
79
|
+Integer severity
|
44
|
-
+
|
80
|
+
+String message
|
45
81
|
+String progname
|
46
82
|
+Integer pid
|
47
|
-
+Hash
|
48
|
-
+initialize(time, severity, message, progname, pid,
|
49
|
-
+severity_label()
|
50
|
-
+
|
51
|
-
+to_s()
|
83
|
+
+Hash attributes
|
84
|
+
+initialize(time, severity, message, progname, pid, attributes)
|
85
|
+
+severity_label() String
|
86
|
+
+to_s() String
|
52
87
|
}
|
53
88
|
|
54
|
-
class
|
55
|
-
|
56
|
-
+
|
57
|
-
+
|
58
|
-
+
|
59
|
-
+
|
60
|
-
+
|
61
|
-
+
|
89
|
+
class EntryFormatter {
|
90
|
+
+Formatter message_formatter
|
91
|
+
+AttributeFormatter attribute_formatter
|
92
|
+
+format(message, attributes) Array
|
93
|
+
+format_class(classes, formatter, *args) self
|
94
|
+
+format_message(classes, formatter, *args) self
|
95
|
+
+format_attributes(classes, formatter, *args) self
|
96
|
+
+format_attribute_name(names, formatter, *args) self
|
97
|
+
+call(severity, timestamp, progname, msg) String
|
62
98
|
}
|
63
99
|
|
64
100
|
class Formatter {
|
65
101
|
+Hash class_formatters
|
66
|
-
+
|
67
|
-
+
|
68
|
-
+
|
69
|
-
+format(message)
|
70
|
-
+clear()
|
102
|
+
+add(klass, formatter, *args, &block)
|
103
|
+
+remove(klass)
|
104
|
+
+format(message) Object
|
71
105
|
}
|
72
106
|
|
73
|
-
class
|
74
|
-
+Hash
|
75
|
-
+
|
76
|
-
+
|
77
|
-
+add(
|
78
|
-
+
|
79
|
-
+
|
80
|
-
+
|
107
|
+
class AttributeFormatter {
|
108
|
+
+Hash attribute_formatters
|
109
|
+
+Formatter class_formatter
|
110
|
+
+Formatter default_formatter
|
111
|
+
+add(names_or_classes, formatter, *args, &block) self
|
112
|
+
+add_class(classes, formatter, *args, &block) self
|
113
|
+
+add_attribute(names, formatter, *args, &block) self
|
114
|
+
+default(formatter, *args, &block) self
|
115
|
+
+remove_class(classes) self
|
116
|
+
+remove_attribute(names) self
|
117
|
+
+format(attributes) Hash
|
118
|
+
+include_class?(class_or_name) Boolean
|
81
119
|
}
|
82
120
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
+
|
87
|
-
+
|
88
|
-
+
|
89
|
-
+
|
121
|
+
%% Device Architecture
|
122
|
+
class Device {
|
123
|
+
<<abstract>>
|
124
|
+
+write(entry) void
|
125
|
+
+flush() void
|
126
|
+
+close() void
|
127
|
+
+reopen(logdev) void
|
128
|
+
+datetime_format() String
|
129
|
+
+datetime_format=(format) void
|
90
130
|
}
|
91
131
|
|
92
|
-
class
|
93
|
-
+
|
94
|
-
+
|
95
|
-
+
|
96
|
-
|
97
|
-
|
98
|
-
+
|
132
|
+
class DeviceWriter["Device::Writer"] {
|
133
|
+
+IO stream
|
134
|
+
+Template template
|
135
|
+
+Buffer buffer
|
136
|
+
+initialize(stream, options)
|
137
|
+
+write(entry) void
|
138
|
+
+flush() void
|
139
|
+
+close() void
|
140
|
+
+reopen(logdev) void
|
141
|
+
+datetime_format() String
|
142
|
+
+datetime_format=(format) void
|
99
143
|
}
|
100
144
|
|
101
|
-
class
|
102
|
-
|
103
|
-
+
|
104
|
-
+
|
105
|
-
+coerce(value)
|
145
|
+
class DeviceLogFile["Device::LogFile"] {
|
146
|
+
+String path
|
147
|
+
+initialize(path, options)
|
148
|
+
+reopen(logdev) void
|
106
149
|
}
|
107
150
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
Formatter --> LogEntry : formats message
|
115
|
-
TagFormatter --> LogEntry : formats tags
|
116
|
-
Logger <-- Context : provides tags
|
117
|
-
```
|
118
|
-
|
119
|
-
## Device Hierarchy
|
151
|
+
class DeviceDateRollingLogFile["Device::DateRollingLogFile"] {
|
152
|
+
+String path
|
153
|
+
+String frequency
|
154
|
+
+initialize(path, options)
|
155
|
+
+roll_file?() Boolean
|
156
|
+
}
|
120
157
|
|
121
|
-
|
158
|
+
class DeviceSizeRollingLogFile["Device::SizeRollingLogFile"] {
|
159
|
+
+String path
|
160
|
+
+Integer max_size
|
161
|
+
+Integer keep
|
162
|
+
+initialize(path, options)
|
163
|
+
+roll_file?() Boolean
|
164
|
+
}
|
122
165
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
+
|
128
|
-
+
|
129
|
-
+
|
130
|
-
+
|
166
|
+
class DeviceMulti["Device::Multi"] {
|
167
|
+
+Array devices
|
168
|
+
+initialize(*devices)
|
169
|
+
+write(entry) void
|
170
|
+
+flush() void
|
171
|
+
+close() void
|
172
|
+
+reopen(logdev) void
|
173
|
+
+datetime_format() String
|
174
|
+
+datetime_format=(format) void
|
131
175
|
}
|
132
176
|
|
133
|
-
class
|
134
|
-
+
|
135
|
-
+
|
136
|
-
+
|
137
|
-
+
|
138
|
-
+
|
139
|
-
+
|
140
|
-
+
|
177
|
+
class DeviceTest["Device::Test"] {
|
178
|
+
+Array entries
|
179
|
+
+Integer max_entries
|
180
|
+
+initialize(options)
|
181
|
+
+write(entry) void
|
182
|
+
+include?(options) Boolean
|
183
|
+
+match(**options) LogEntry
|
184
|
+
+clear() void
|
141
185
|
}
|
142
186
|
|
143
|
-
class
|
144
|
-
+
|
145
|
-
+write(entry)
|
146
|
-
+reopen(file_path)
|
147
|
-
+close()
|
187
|
+
class DeviceNull["Device::Null"] {
|
188
|
+
+initialize()
|
189
|
+
+write(entry) void
|
148
190
|
}
|
149
191
|
|
150
|
-
class
|
151
|
-
|
152
|
-
+
|
153
|
-
+
|
154
|
-
+archive_file_suffix()
|
192
|
+
class DeviceLoggerWrapper["Device::LoggerWrapper"] {
|
193
|
+
+Logger logger
|
194
|
+
+initialize(logger)
|
195
|
+
+write(entry) void
|
155
196
|
}
|
156
197
|
|
157
|
-
class
|
158
|
-
+
|
159
|
-
+
|
160
|
-
+
|
198
|
+
class DeviceBuffer["Device::Buffer"] {
|
199
|
+
+Array values
|
200
|
+
+Integer size
|
201
|
+
+initialize()
|
202
|
+
+<<(string) void
|
203
|
+
+empty?() Boolean
|
204
|
+
+pop!() Array
|
205
|
+
+clear() void
|
161
206
|
}
|
162
207
|
|
163
|
-
|
164
|
-
|
165
|
-
+
|
166
|
-
+
|
167
|
-
+
|
208
|
+
%% Template System
|
209
|
+
class Template {
|
210
|
+
+String template
|
211
|
+
+String time_format
|
212
|
+
+String attribute_format
|
213
|
+
+initialize(template, options)
|
214
|
+
+call(entry) String
|
168
215
|
}
|
169
216
|
|
170
|
-
|
171
|
-
|
172
|
-
+
|
173
|
-
+
|
174
|
-
+
|
217
|
+
%% Utility Classes
|
218
|
+
class Utils {
|
219
|
+
+deprecated(method, message, &block) Object
|
220
|
+
+hostname() String
|
221
|
+
+current_line() String
|
222
|
+
+flatten_attributes(hash) Hash
|
223
|
+
+expand_attributes(hash) Hash
|
224
|
+
+global_pid() String
|
225
|
+
+thread_name() String
|
226
|
+
+global_thread_id() String
|
175
227
|
}
|
176
228
|
|
177
|
-
class
|
178
|
-
+
|
229
|
+
class Severity {
|
230
|
+
+level_to_label(level) String
|
231
|
+
+label_to_level(label) Integer
|
232
|
+
+coerce(value) Integer
|
179
233
|
}
|
180
234
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
235
|
+
%% Relationships
|
236
|
+
Logger --|> ContextLogger : implements
|
237
|
+
ForkedLogger --|> Logger : inherits
|
238
|
+
Logger --* Device : uses
|
239
|
+
Logger --* Context : has
|
240
|
+
Logger --* EntryFormatter : uses
|
241
|
+
ForkedLogger --* Logger : forwards to
|
242
|
+
|
243
|
+
Context --* AttributesHelper : uses
|
244
|
+
EntryFormatter --* Formatter : uses
|
245
|
+
EntryFormatter --* AttributeFormatter : uses
|
246
|
+
AttributeFormatter --* Formatter : uses
|
247
|
+
|
248
|
+
Device <|-- DeviceWriter : implements
|
249
|
+
Device <|-- DeviceMulti : implements
|
250
|
+
Device <|-- DeviceTest : implements
|
251
|
+
Device <|-- DeviceNull : implements
|
252
|
+
Device <|-- DeviceLoggerWrapper : implements
|
253
|
+
DeviceWriter <|-- DeviceLogFile : inherits
|
254
|
+
DeviceLogFile <|-- DeviceDateRollingLogFile : inherits
|
255
|
+
DeviceLogFile <|-- DeviceSizeRollingLogFile : inherits
|
256
|
+
|
257
|
+
DeviceWriter --* Template : uses
|
258
|
+
DeviceWriter --* DeviceBuffer : uses
|
259
|
+
Logger --> LogEntry : creates
|
260
|
+
EntryFormatter --> LogEntry : processes
|
261
|
+
Device --> LogEntry : receives
|
262
|
+
|
263
|
+
DeviceMulti --* Device : aggregates
|
264
|
+
DeviceLoggerWrapper --* Logger : forwards to
|
265
|
+
DeviceTest --* LogEntry : stores
|
189
266
|
```
|
190
267
|
|
191
|
-
##
|
268
|
+
## Component Responsibilities
|
269
|
+
|
270
|
+
### Core Logger Components
|
271
|
+
|
272
|
+
**Logger**
|
273
|
+
- Main entry point for logging operations
|
274
|
+
- Manages device, context, and formatting coordination
|
275
|
+
- Provides standard logging methods (info, debug, error, etc.)
|
276
|
+
- Handles context creation and attribute management
|
277
|
+
|
278
|
+
**ContextLogger**
|
279
|
+
- Mixin providing context-aware logging capabilities
|
280
|
+
- Manages level, progname, and attribute scoping
|
281
|
+
- Supports hierarchical contexts and attribute inheritance
|
282
|
+
- Thread and fiber-safe context isolation
|
283
|
+
|
284
|
+
**ForkedLogger**
|
285
|
+
- Creates isolated logger instances that forward to parent loggers
|
286
|
+
- Enables component-specific logging configuration
|
287
|
+
- Maintains independent context while sharing output infrastructure
|
288
|
+
|
289
|
+
### Context and Attribute Management
|
290
|
+
|
291
|
+
**Context**
|
292
|
+
- Stores scoped logging configuration (level, progname, attributes)
|
293
|
+
- Supports hierarchical inheritance from parent contexts
|
294
|
+
- Provides isolation for block-scoped logging behavior
|
295
|
+
|
296
|
+
**AttributesHelper**
|
297
|
+
- Manages attribute hash manipulation and access
|
298
|
+
- Supports dot notation for nested attribute access
|
299
|
+
- Handles attribute merging and deletion operations
|
300
|
+
|
301
|
+
### Entry Processing Pipeline
|
302
|
+
|
303
|
+
**LogEntry**
|
304
|
+
- Immutable data structure representing a single log event
|
305
|
+
- Contains all metadata: timestamp, severity, message, attributes
|
306
|
+
- Provides formatted string representation for output
|
307
|
+
|
308
|
+
**EntryFormatter**
|
309
|
+
- Coordinates message and attribute formatting
|
310
|
+
- Delegates to specialized formatters for different data types
|
311
|
+
- Handles complex formatting scenarios with embedded attributes
|
312
|
+
|
313
|
+
**Formatter & AttributeFormatter**
|
314
|
+
- Class-based and name-based formatting rules
|
315
|
+
- Recursive formatting for nested data structures
|
316
|
+
- Extensible formatting system with built-in formatters
|
317
|
+
|
318
|
+
### Device Architecture
|
319
|
+
|
320
|
+
**Device (Abstract Base)**
|
321
|
+
- Defines interface for log output destinations
|
322
|
+
- Supports lifecycle methods (flush, close, reopen)
|
323
|
+
- Enables pluggable output architecture
|
324
|
+
|
325
|
+
**WriterDevice**
|
326
|
+
- Writes formatted entries to IO streams
|
327
|
+
- Supports templated output formatting
|
328
|
+
- Handles encoding and error recovery
|
329
|
+
|
330
|
+
**MultiDevice**
|
331
|
+
- Broadcasts entries to multiple target devices
|
332
|
+
- Enables redundant logging and output splitting
|
333
|
+
- Maintains consistent state across all targets
|
334
|
+
|
335
|
+
**Specialized Devices**
|
336
|
+
- **LogFileDevice**: File-based logging with rotation
|
337
|
+
- **TestDevice**: In-memory capture for testing
|
338
|
+
- **NullDevice**: Silent operation for performance testing
|
339
|
+
- **LoggerDevice**: Forwards to other Logger instances
|
340
|
+
|
341
|
+
## Logging Flow Sequence
|
192
342
|
|
193
|
-
The
|
343
|
+
The following sequence diagram illustrates the complete flow of a log entry through the Lumberjack framework:
|
194
344
|
|
195
345
|
```mermaid
|
196
346
|
sequenceDiagram
|
197
|
-
participant
|
198
|
-
participant Logger
|
199
|
-
participant
|
200
|
-
participant
|
201
|
-
participant
|
202
|
-
participant
|
203
|
-
participant
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
Logger->>
|
210
|
-
|
211
|
-
Logger
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
347
|
+
participant App as Application
|
348
|
+
participant Logger as Logger
|
349
|
+
participant Context as Context
|
350
|
+
participant EntryFormatter as EntryFormatter
|
351
|
+
participant MsgFormatter as Message Formatter
|
352
|
+
participant AttrFormatter as Attribute Formatter
|
353
|
+
participant Device as Device
|
354
|
+
participant Template as Template
|
355
|
+
participant Output as Output Stream
|
356
|
+
|
357
|
+
%% Context Setup
|
358
|
+
App->>Logger: context do |ctx|
|
359
|
+
Logger->>Context: new(parent_context)
|
360
|
+
Context-->>Logger: context instance
|
361
|
+
App->>Logger: tag(user_id: 123)
|
362
|
+
Logger->>Context: assign_attributes({user_id: 123})
|
363
|
+
|
364
|
+
%% Logging Call
|
365
|
+
App->>Logger: info("User login", ip: "192.168.1.1")
|
366
|
+
Logger->>Logger: check level >= INFO
|
367
|
+
|
368
|
+
%% Entry Creation
|
369
|
+
Logger->>Logger: merge_all_attributes()
|
370
|
+
Note over Logger: Combines global, context, and local attributes
|
371
|
+
Logger->>LogEntry: new(time, severity, message, progname, pid, attributes)
|
372
|
+
LogEntry-->>Logger: entry instance
|
373
|
+
|
374
|
+
%% Entry Formatting
|
375
|
+
Logger->>EntryFormatter: format(message, attributes)
|
376
|
+
EntryFormatter->>MsgFormatter: format("User login")
|
377
|
+
MsgFormatter-->>EntryFormatter: formatted message
|
378
|
+
EntryFormatter->>AttrFormatter: format({user_id: 123, ip: "192.168.1.1"})
|
379
|
+
AttrFormatter-->>EntryFormatter: formatted attributes
|
380
|
+
EntryFormatter-->>Logger: [formatted_message, formatted_attributes]
|
381
|
+
|
382
|
+
%% Device Writing
|
383
|
+
Logger->>Device: write(entry)
|
384
|
+
|
385
|
+
alt WriterDevice
|
386
|
+
Device->>Template: call(entry)
|
387
|
+
Template-->>Device: formatted string
|
388
|
+
Device->>Output: write(formatted_string)
|
389
|
+
Output-->>Device: success
|
390
|
+
else MultiDevice
|
391
|
+
Device->>Device: devices.each
|
392
|
+
loop Each Target Device
|
393
|
+
Device->>Device: target.write(entry)
|
394
|
+
end
|
395
|
+
else TestDevice
|
396
|
+
Device->>Device: entries << entry
|
397
|
+
end
|
398
|
+
|
399
|
+
Device-->>Logger: success
|
400
|
+
Logger-->>App: true
|
401
|
+
|
402
|
+
%% Context Cleanup
|
403
|
+
Note over Logger,Context: Context automatically cleaned up when block exits
|
217
404
|
```
|
218
405
|
|
219
|
-
## Key
|
406
|
+
## Key Design Patterns
|
407
|
+
|
408
|
+
### 1. **Layered Architecture**
|
409
|
+
- Clear separation between logging interface, processing, and output
|
410
|
+
- Each layer has well-defined responsibilities and interfaces
|
411
|
+
- Enables independent testing and component replacement
|
412
|
+
|
413
|
+
### 2. **Strategy Pattern**
|
414
|
+
- Devices implement pluggable output strategies
|
415
|
+
- Formatters provide pluggable formatting strategies
|
416
|
+
- Templates enable customizable output formatting
|
417
|
+
|
418
|
+
### 3. **Composite Pattern**
|
419
|
+
- MultiDevice composes multiple output devices
|
420
|
+
- AttributeFormatter composes class and attribute formatters
|
421
|
+
- Context inherits from parent contexts
|
422
|
+
|
423
|
+
### 4. **Chain of Responsibility**
|
424
|
+
- Formatting pipeline processes entries through multiple stages
|
425
|
+
- Context resolution follows inheritance chain
|
426
|
+
- Attribute merging follows precedence rules
|
427
|
+
|
428
|
+
### 5. **Facade Pattern**
|
429
|
+
- Logger provides simplified interface to complex subsystem
|
430
|
+
- ContextLogger abstracts context management complexity
|
431
|
+
- Utils module provides common functionality access
|
432
|
+
|
433
|
+
## Performance Characteristics
|
434
|
+
|
435
|
+
### **Memory Management**
|
436
|
+
- Immutable LogEntry objects prevent accidental modification
|
437
|
+
- Context inheritance minimizes memory duplication
|
438
|
+
- Attribute compaction removes empty values automatically
|
439
|
+
|
440
|
+
### **Thread Safety**
|
441
|
+
- Fiber-local storage for context isolation (contexts are fiber-scoped)
|
442
|
+
- Mutex-protected device operations where needed
|
443
|
+
- Immutable data structures prevent race conditions
|
444
|
+
- FiberLocals module manages per-fiber state
|
445
|
+
|
446
|
+
### **Lazy Evaluation**
|
447
|
+
- Block-based message generation for expensive operations
|
448
|
+
- Conditional formatting based on log levels
|
449
|
+
- On-demand context resolution
|
450
|
+
|
451
|
+
### **Efficient Routing**
|
452
|
+
- Level checking before entry creation
|
453
|
+
- Direct device writing without intermediate buffers
|
454
|
+
- Optimized formatter selection for common types
|
455
|
+
|
456
|
+
## Extension Points
|
457
|
+
|
458
|
+
The framework provides several extension points for customization:
|
459
|
+
|
460
|
+
### **Custom Devices**
|
461
|
+
```ruby
|
462
|
+
class DatabaseDevice < Lumberjack::Device
|
463
|
+
def initialize(connection)
|
464
|
+
@connection = connection
|
465
|
+
end
|
466
|
+
|
467
|
+
def write(entry)
|
468
|
+
@connection.execute(
|
469
|
+
"INSERT INTO logs (timestamp, level, message, attributes) VALUES (?, ?, ?, ?)",
|
470
|
+
entry.time,
|
471
|
+
entry.severity_label,
|
472
|
+
entry.message,
|
473
|
+
JSON.generate(entry.attributes || {})
|
474
|
+
)
|
475
|
+
end
|
476
|
+
|
477
|
+
def flush
|
478
|
+
# Database connections typically auto-flush
|
479
|
+
end
|
480
|
+
|
481
|
+
def close
|
482
|
+
@connection.close
|
483
|
+
end
|
484
|
+
end
|
485
|
+
```
|
486
|
+
|
487
|
+
### **Custom Formatters**
|
488
|
+
```ruby
|
489
|
+
class JsonFormatter
|
490
|
+
def call(obj)
|
491
|
+
JSON.generate(obj)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
logger.formatter.format_class(Hash, JsonFormatter.new)
|
496
|
+
```
|
497
|
+
|
498
|
+
### **Custom Templates**
|
499
|
+
```ruby
|
500
|
+
json_template = ->(entry) do
|
501
|
+
JSON.generate(
|
502
|
+
timestamp: entry.time.iso8601,
|
503
|
+
level: entry.severity_label,
|
504
|
+
message: entry.message,
|
505
|
+
attributes: entry.attributes
|
506
|
+
)
|
507
|
+
end
|
508
|
+
|
509
|
+
device = Lumberjack::Device::Writer.new(STDOUT, template: json_template)
|
510
|
+
```
|
511
|
+
|
512
|
+
## Integration Patterns
|
513
|
+
|
514
|
+
### **Web Application Integration**
|
515
|
+
```ruby
|
516
|
+
# Rack middleware for request context
|
517
|
+
use Lumberjack::Rack::Context do |env|
|
518
|
+
{
|
519
|
+
request_id: env["HTTP_X_REQUEST_ID"],
|
520
|
+
user_id: env["warden"]&.user&.id
|
521
|
+
}
|
522
|
+
end
|
523
|
+
```
|
524
|
+
|
525
|
+
### **Component Isolation**
|
526
|
+
```ruby
|
527
|
+
# Component-specific loggers
|
528
|
+
class UserService
|
529
|
+
def initialize(logger)
|
530
|
+
@logger = logger.fork(progname: "UserService")
|
531
|
+
@logger.tag!(component: "user_service", version: "1.2.3")
|
532
|
+
end
|
533
|
+
end
|
534
|
+
```
|
220
535
|
|
221
|
-
###
|
222
|
-
|
223
|
-
|
224
|
-
|
536
|
+
### **Testing Integration**
|
537
|
+
```ruby
|
538
|
+
# Test environment setup
|
539
|
+
logger = Lumberjack::Logger.new(:test)
|
540
|
+
logger.info("Test message", user_id: 123)
|
541
|
+
expect(logger.device).to include(
|
542
|
+
severity: :info,
|
543
|
+
message: "Test message",
|
544
|
+
attributes: {user_id: 123}
|
545
|
+
)
|
546
|
+
```
|
547
|
+
|
548
|
+
## Configuration Best Practices
|
549
|
+
|
550
|
+
### **Production Configuration**
|
551
|
+
```ruby
|
552
|
+
logger = Lumberjack::Logger.new("/var/log/app.log", shift_age: 10, shift_size: "50M", level: :info) do |config|
|
553
|
+
# Structured attribute formatting
|
554
|
+
config.formatter.format_attribute_name("password") { |value| "[REDACTED]" }
|
555
|
+
config.formatter.format_class(Time, :iso8601)
|
556
|
+
end
|
557
|
+
```
|
558
|
+
|
559
|
+
### **Development Configuration**
|
560
|
+
```ruby
|
561
|
+
logger = Lumberjack::Logger.new(STDOUT, level: :debug, template: "[{{time}} {{severity}}] {{message}} {{attributes}}") do |config|
|
562
|
+
# Pretty-print complex objects
|
563
|
+
config.formatter.format_class(Hash, :pretty_print)
|
564
|
+
config.formatter.format_class(Array, :pretty_print)
|
565
|
+
end
|
566
|
+
```
|
567
|
+
|
568
|
+
### **Multi-Environment Setup**
|
569
|
+
```ruby
|
570
|
+
file_device = Lumberjack::Device::LogFile.new("/var/log/app.log")
|
571
|
+
console_device = Lumberjack::Device::Writer.new(STDOUT)
|
572
|
+
error_device = Lumberjack::Device::Writer.new(STDERR)
|
573
|
+
|
574
|
+
multi_device = Lumberjack::Device::Multi.new(
|
575
|
+
file_device,
|
576
|
+
Rails.env.development? ? console_device : nil
|
577
|
+
).compact
|
578
|
+
|
579
|
+
logger = Lumberjack::Logger.new(multi_device)
|
580
|
+
```
|
225
581
|
|
226
|
-
|
227
|
-
- LogEntry captures structured data beyond just the message
|
228
|
-
- Tags provide key-value metadata
|
229
|
-
- Formatters can handle complex object serialization
|
582
|
+
This architecture enables Lumberjack to provide a powerful, flexible logging solution that scales from simple applications to complex, multi-component systems while maintaining excellent performance and developer experience.
|
230
583
|
|
231
|
-
|
232
|
-
- Device abstraction allows custom output destinations
|
233
|
-
- Formatter system enables custom message transformation
|
234
|
-
- TagFormatter provides specialized tag handling
|
584
|
+
## Version Notes
|
235
585
|
|
236
|
-
|
237
|
-
- Buffered writing in Writer devices
|
238
|
-
- Lazy evaluation of expensive operations
|
239
|
-
- Configurable flush intervals
|
586
|
+
This documentation reflects the current Lumberjack architecture. Note that:
|
240
587
|
|
241
|
-
|
242
|
-
-
|
243
|
-
-
|
244
|
-
-
|
588
|
+
- Template syntax has evolved from `:placeholder` to `{{placeholder}}` format (old syntax is deprecated)
|
589
|
+
- Some method names have changed over versions (e.g., `tag_formatter` is now `attribute_formatter`)
|
590
|
+
- Configuration options have been streamlined to use keyword arguments
|
591
|
+
- Fiber-local storage is used for context isolation rather than thread-local storage
|
592
|
+
- The `EntryFormatter` class provides the primary formatting interface, with `format()` being the main method and `call()` for Logger compatibility
|