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
data/ARCHITECTURE.md CHANGED
@@ -1,244 +1,592 @@
1
- # Lumberjack Gem Architecture
1
+ # Lumberjack Logging Framework Architecture
2
2
 
3
- Lumberjack is a simple, powerful, and fast logging implementation in Ruby. It uses nearly the same API as the Logger class in the Ruby standard library and as ActiveSupport::BufferedLogger in Rails. The gem is designed with structured logging in mind, but can be used for simple text logging as well.
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
- The Lumberjack architecture follows a clean separation of concerns with the following main components:
7
+ Lumberjack is a structured logging framework that extends Ruby's standard Logger with advanced features including:
8
8
 
9
- - **Logger**: The main interface for creating log entries
10
- - **LogEntry**: Data structure that captures log messages and metadata
11
- - **Device**: Abstraction for different output destinations
12
- - **Formatter**: Handles message formatting and transformation
13
- - **TagFormatter**: Specialized formatting for tags
14
- - **Template**: Template engine for customizing log output format
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
- +Formatter formatter
25
- +TagFormatter tag_formatter
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
- +Hash tags
29
- +initialize(device, options)
30
- +debug(message, progname, tags)
31
- +info(message, progname, tags)
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
- +Object message
80
+ +String message
45
81
  +String progname
46
82
  +Integer pid
47
- +Hash tags
48
- +initialize(time, severity, message, progname, pid, tags)
49
- +severity_label()
50
- +tag(name)
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 Device {
55
- <<abstract>>
56
- +write(entry)*
57
- +flush()
58
- +close()
59
- +reopen()
60
- +datetime_format()
61
- +datetime_format=(format)
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
- +Hash module_formatters
67
- +add(classes, formatter)
68
- +remove(classes)
69
- +format(message)
70
- +clear()
102
+ +add(klass, formatter, *args, &block)
103
+ +remove(klass)
104
+ +format(message) Object
71
105
  }
72
106
 
73
- class TagFormatter {
74
- +Hash formatters
75
- +Object default_formatter
76
- +default(formatter)
77
- +add(names, formatter)
78
- +remove(names)
79
- +format(tags)
80
- +clear()
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
- class Template {
84
- +String first_line_template
85
- +String additional_line_template
86
- +String datetime_format
87
- +compile(template)
88
- +call(entry)
89
- +datetime_format=(format)
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 Context {
93
- +Hash tags
94
- +initialize(parent_context)
95
- +tag(tags)
96
- +\[](key)
97
- +\[]=(key, value)
98
- +reset()
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 Severity {
102
- <<module>>
103
- +level_to_label(severity)
104
- +label_to_level(label)
105
- +coerce(value)
145
+ class DeviceLogFile["Device::LogFile"] {
146
+ +String path
147
+ +initialize(path, options)
148
+ +reopen(logdev) void
106
149
  }
107
150
 
108
- Logger --> LogEntry : creates
109
- Logger --> Device : writes to
110
- Logger --> Formatter : uses
111
- Logger --> TagFormatter : uses
112
- Logger --> Severity : includes
113
- Device --> Template : may use
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
- The Device system provides a pluggable architecture for different output destinations:
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
- ```mermaid
124
- classDiagram
125
- class Device {
126
- <<abstract>>
127
- +write(entry)*
128
- +flush()
129
- +close()
130
- +reopen()
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 Writer {
134
- +IO stream
135
- +Template template
136
- +Buffer buffer
137
- +Integer buffer_size
138
- +write(entry)
139
- +flush()
140
- +before_flush()
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 LogFile {
144
- +String file_path
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 RollingLogFile {
151
- <<abstract>>
152
- +roll_file?()
153
- +roll_file!()
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 DateRollingLogFile {
158
- +String roll
159
- +roll_file?()
160
- +archive_file_suffix()
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
- class SizeRollingLogFile {
164
- +Integer max_size
165
- +Integer keep
166
- +roll_file?()
167
- +archive_file_suffix()
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
- class Multi {
171
- +Array~Device~ devices
172
- +write(entry)
173
- +flush()
174
- +close()
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 Null {
178
- +write(entry)
229
+ class Severity {
230
+ +level_to_label(level) String
231
+ +label_to_level(label) Integer
232
+ +coerce(value) Integer
179
233
  }
180
234
 
181
- Device <|-- Writer
182
- Device <|-- Multi
183
- Device <|-- Null
184
- Writer <|-- LogFile
185
- LogFile <|-- RollingLogFile
186
- RollingLogFile <|-- DateRollingLogFile
187
- RollingLogFile <|-- SizeRollingLogFile
188
- Multi --> Device : contains multiple
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
- ## Data Flow
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 logging process follows this flow:
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 Client
198
- participant Logger
199
- participant Formatter
200
- participant TagFormatter
201
- participant LogEntry
202
- participant Device
203
- participant Template
204
-
205
- Client->>Logger: info("message", tags: {key: value})
206
- Logger->>Logger: Check severity level
207
- Logger->>Formatter: format(message)
208
- Formatter-->>Logger: formatted_message
209
- Logger->>TagFormatter: format(tags)
210
- TagFormatter-->>Logger: formatted_tags
211
- Logger->>LogEntry: new(time, severity, message, progname, pid, tags)
212
- LogEntry-->>Logger: log_entry
213
- Logger->>Device: write(log_entry)
214
- Device->>Template: call(log_entry) [if Writer device]
215
- Template-->>Device: formatted_string
216
- Device->>Device: Write to output destination
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 Features
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
- ### Thread Safety
222
- - Logger operations are thread-safe
223
- - Context provides thread-local tag storage
224
- - Devices handle concurrent writes appropriately
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
- ### Structured Logging
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
- ### Pluggable Architecture
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
- ### Performance Optimization
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
- ### ActiveSupport Compatibility
242
- - TaggedLoggerSupport module provides Rails compatibility
243
- - Compatible API with standard library Logger
244
- - Supports ActiveSupport::TaggedLogging interface
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