lumberjack 2.0.2 → 2.0.3
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 +0 -188
- data/CHANGELOG.md +6 -0
- data/README.md +13 -0
- data/VERSION +1 -1
- data/lib/lumberjack/context_locals.rb +108 -0
- data/lib/lumberjack/context_logger.rb +7 -6
- data/lib/lumberjack/forked_logger.rb +2 -1
- data/lib/lumberjack/logger.rb +5 -3
- data/lib/lumberjack.rb +27 -17
- metadata +2 -2
- data/lib/lumberjack/fiber_locals.rb +0 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 140e49b15017aa81ca85a2e42a9de896b09938b5f9dd437a8f2917f821831d3d
|
|
4
|
+
data.tar.gz: fe95870efcfb98d6831833e4125a74817f93c718969febcdba81e0b3b6e149a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba5adc8a3633df76fb78960ed06b266336c829d72ac48df5092dc5eb6fea4061ba4bb738c54aafbc2cedb23b6e638871992363120f0c323a916976e757d4bf0b
|
|
7
|
+
data.tar.gz: e23288af3a076eead23bad85f885f4928d5aae4592cef341e6fd4f27ee9e65c6c8464e20c09b6d7a1522512ca70057027a9f40c0201bfa8b4400a6af139b4c3a
|
data/ARCHITECTURE.md
CHANGED
|
@@ -402,191 +402,3 @@ sequenceDiagram
|
|
|
402
402
|
%% Context Cleanup
|
|
403
403
|
Note over Logger,Context: Context automatically cleaned up when block exits
|
|
404
404
|
```
|
|
405
|
-
|
|
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
|
-
```
|
|
535
|
-
|
|
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
|
-
```
|
|
581
|
-
|
|
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.
|
|
583
|
-
|
|
584
|
-
## Version Notes
|
|
585
|
-
|
|
586
|
-
This documentation reflects the current Lumberjack architecture. Note that:
|
|
587
|
-
|
|
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
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 2.0.3
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added `isolation_level` to loggers. By default this is set to `:fiber` which isolates logger contexts to the current fiber. That is each fiber will get its own context stack and starting a new fiber will start with a clean context. Setting `isolation_level` to `:thread` will isolate contexts to the current thread instead. This is useful if your application does not share fibers between threads and you want to maintain context across fibers in the same thread.
|
|
12
|
+
|
|
7
13
|
## 2.0.2
|
|
8
14
|
|
|
9
15
|
### Changed
|
data/README.md
CHANGED
|
@@ -22,6 +22,7 @@ The philosophy behind the library is to promote use of structured logging with t
|
|
|
22
22
|
- [Context Blocks](#context-blocks)
|
|
23
23
|
- [Nested Context Blocks](#nested-context-blocks)
|
|
24
24
|
- [Forking Loggers](#forking-loggers)
|
|
25
|
+
- [Isolation Level](#isolation-level)
|
|
25
26
|
- [Structured Logging With Attributes](#structured-logging-with-attributes)
|
|
26
27
|
- [Basic Attribute Logging](#basic-attribute-logging)
|
|
27
28
|
- [Adding attributes to the logger](#adding-attributes-to-the-logger)
|
|
@@ -113,6 +114,18 @@ main_logger.info("Main logger info") # Includes only version attribute
|
|
|
113
114
|
main_logger.debug("Main logger debug info") # Not logged since level is :info
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
#### Isolation Level
|
|
118
|
+
|
|
119
|
+
By default each fiber will have its own logging context. This is useful in asynchronous applications where multiple fibers may be running concurrently. You can change the isolation level to `:thread` if you want each thread to have its own logging context instead.
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
Lumberjack.isolation_level = :thread # Set isolation level globally
|
|
123
|
+
|
|
124
|
+
logger = Lumberjack::Logger.new(STDOUT, isolation_level: :thread) # Set isolation level per logger
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Fiber isolation is the safest behavior since it completely isolates local contexts. However, in applications where threads are the main unit of work and fibers are never shared across threads, thread isolation may be more appropriate. Otherwise you can end up with the logger losing the context block when switching fibers within the same thread.
|
|
128
|
+
|
|
116
129
|
### Structured Logging With Attributes
|
|
117
130
|
|
|
118
131
|
Lumberjack extends standard logging with **attributes** (structured key-value pairs) that add context and metadata to your log entries. This enables powerful filtering, searching, and analytics capabilities.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.3
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lumberjack
|
|
4
|
+
# Provides isolated fiber or thread local storage for thread-safe data access.
|
|
5
|
+
module ContextLocals
|
|
6
|
+
# Lightweight structure to hold context-local data.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Data
|
|
10
|
+
attr_accessor :context, :logging, :cleared
|
|
11
|
+
|
|
12
|
+
def initialize(copy = nil)
|
|
13
|
+
@context = copy&.context
|
|
14
|
+
@logging = copy&.logging
|
|
15
|
+
@cleared = copy&.cleared
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Set the isolation level for context locals.
|
|
20
|
+
#
|
|
21
|
+
# @param value [Symbol] The isolation level, either :fiber or :thread.
|
|
22
|
+
# @return [void]
|
|
23
|
+
def isolation_level=(value)
|
|
24
|
+
value = value&.to_sym
|
|
25
|
+
value = :fiber unless [:fiber, :thread].include?(value)
|
|
26
|
+
@isolation_level = value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get the isolation level for context locals.
|
|
30
|
+
#
|
|
31
|
+
# @return [Symbol] The isolation level, either :fiber or :thread.
|
|
32
|
+
def isolation_level
|
|
33
|
+
@isolation_level ||= :fiber
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def new_context_locals(&block)
|
|
39
|
+
init_context_locals! unless defined?(@context_locals)
|
|
40
|
+
|
|
41
|
+
if isolation_level == :fiber
|
|
42
|
+
set_context_locals(&block)
|
|
43
|
+
else
|
|
44
|
+
set_context_locals_thread_id do
|
|
45
|
+
set_context_locals(&block)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_context_locals(&block)
|
|
51
|
+
scope_id = context_locals_scope_id
|
|
52
|
+
current = @context_locals[scope_id] if scope_id
|
|
53
|
+
data = Data.new(current)
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
@context_locals_mutex.synchronize do
|
|
57
|
+
@context_locals[scope_id] = data
|
|
58
|
+
end
|
|
59
|
+
yield data
|
|
60
|
+
ensure
|
|
61
|
+
@context_locals_mutex.synchronize do
|
|
62
|
+
if current.nil?
|
|
63
|
+
@context_locals.delete(scope_id)
|
|
64
|
+
else
|
|
65
|
+
@context_locals[scope_id] = current
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def current_context_locals
|
|
72
|
+
return nil unless defined?(@context_locals)
|
|
73
|
+
|
|
74
|
+
@context_locals[context_locals_scope_id]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Initialize the context locals storage and mutex.
|
|
78
|
+
def init_context_locals!
|
|
79
|
+
@context_locals ||= {}
|
|
80
|
+
@context_locals_mutex ||= Mutex.new
|
|
81
|
+
@isolation_level ||= Lumberjack.isolation_level
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def context_locals_scope_id
|
|
85
|
+
if isolation_level == :fiber
|
|
86
|
+
Fiber.current.object_id
|
|
87
|
+
else
|
|
88
|
+
Thread.current.thread_variable_get(:lumberjack_context_locals_thread_id)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Create a consistent thread ID for context locals. We can't use Thread.current.object_id
|
|
93
|
+
# directly because it may change during execution (e.g., in JRuby when threads are
|
|
94
|
+
# migrated between native threads). Instead we store a unique ID in a thread variable.
|
|
95
|
+
def set_context_locals_thread_id
|
|
96
|
+
thread_id = Thread.current.thread_variable_get(:lumberjack_context_locals_thread_id)
|
|
97
|
+
return yield if thread_id
|
|
98
|
+
|
|
99
|
+
thread_id = Object.new.object_id
|
|
100
|
+
begin
|
|
101
|
+
Thread.current.thread_variable_set(:lumberjack_context_locals_thread_id, thread_id)
|
|
102
|
+
yield
|
|
103
|
+
ensure
|
|
104
|
+
Thread.current.thread_variable_set(:lumberjack_context_locals_thread_id, nil)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "context_locals"
|
|
4
4
|
require_relative "io_compatibility"
|
|
5
5
|
require_relative "severity"
|
|
6
6
|
|
|
@@ -26,7 +26,7 @@ module Lumberjack
|
|
|
26
26
|
|
|
27
27
|
class << self
|
|
28
28
|
def included(base)
|
|
29
|
-
base.include(
|
|
29
|
+
base.include(ContextLocals) unless base.include?(ContextLocals)
|
|
30
30
|
base.include(IOCompatibility) unless base.include?(IOCompatibility)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -408,7 +408,7 @@ module Lumberjack
|
|
|
408
408
|
|
|
409
409
|
new_context = Context.new(current_context)
|
|
410
410
|
new_context.parent = local_context
|
|
411
|
-
|
|
411
|
+
new_context_locals do |locals|
|
|
412
412
|
locals.context = new_context
|
|
413
413
|
block.call(new_context)
|
|
414
414
|
end
|
|
@@ -448,6 +448,7 @@ module Lumberjack
|
|
|
448
448
|
logger.level = level if level
|
|
449
449
|
logger.progname = progname if progname
|
|
450
450
|
logger.tag!(attributes) if attributes
|
|
451
|
+
logger.isolation_level = isolation_level
|
|
451
452
|
logger
|
|
452
453
|
end
|
|
453
454
|
|
|
@@ -494,7 +495,7 @@ module Lumberjack
|
|
|
494
495
|
#
|
|
495
496
|
# @return [void]
|
|
496
497
|
def clear_attributes(&block)
|
|
497
|
-
|
|
498
|
+
new_context_locals do |locals|
|
|
498
499
|
locals.cleared = true
|
|
499
500
|
context do |ctx|
|
|
500
501
|
ctx.clear_attributes
|
|
@@ -529,7 +530,7 @@ module Lumberjack
|
|
|
529
530
|
end
|
|
530
531
|
|
|
531
532
|
def local_context
|
|
532
|
-
|
|
533
|
+
current_context_locals&.context
|
|
533
534
|
end
|
|
534
535
|
|
|
535
536
|
def default_context
|
|
@@ -594,7 +595,7 @@ module Lumberjack
|
|
|
594
595
|
def merge_all_attributes
|
|
595
596
|
attributes = nil
|
|
596
597
|
|
|
597
|
-
unless
|
|
598
|
+
unless current_context_locals&.cleared
|
|
598
599
|
global_context_attributes = Lumberjack.context_attributes
|
|
599
600
|
if global_context_attributes && !global_context_attributes.empty?
|
|
600
601
|
attributes ||= {}
|
|
@@ -65,7 +65,8 @@ module Lumberjack
|
|
|
65
65
|
# @param logger [Lumberjack::ContextLogger, #add_entry] The parent logger to forward entries to.
|
|
66
66
|
# Must respond to either +add_entry+ (for Lumberjack loggers) or standard Logger methods.
|
|
67
67
|
def initialize(logger)
|
|
68
|
-
|
|
68
|
+
init_context_locals!
|
|
69
|
+
self.isolation_level = logger.isolation_level if logger.respond_to?(:isolation_level)
|
|
69
70
|
@parent_logger = logger
|
|
70
71
|
@context = Context.new
|
|
71
72
|
@context.level ||= logger.level
|
data/lib/lumberjack/logger.rb
CHANGED
|
@@ -94,7 +94,7 @@ module Lumberjack
|
|
|
94
94
|
def initialize(logdev, shift_age = 0, shift_size = 1048576,
|
|
95
95
|
level: DEBUG, progname: nil, formatter: nil, datetime_format: nil,
|
|
96
96
|
binmode: false, shift_period_suffix: "%Y%m%d", **kwargs)
|
|
97
|
-
|
|
97
|
+
init_context_locals!
|
|
98
98
|
|
|
99
99
|
if shift_age.is_a?(Hash)
|
|
100
100
|
Lumberjack::Utils.deprecated("Logger.new(options)", "Passing a Hash as the second argument to Logger.new is deprecated and will be removed in version 2.1; use keyword arguments instead.")
|
|
@@ -106,6 +106,8 @@ module Lumberjack
|
|
|
106
106
|
kwargs = options.merge(kwargs)
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
+
self.isolation_level = kwargs.delete(:isolation_level) || Lumberjack.isolation_level
|
|
110
|
+
|
|
109
111
|
# Include standard args that affect devices with the optional kwargs which may
|
|
110
112
|
# contain device specific options.
|
|
111
113
|
device_options = kwargs.merge(shift_age: shift_age, shift_size: size_with_units(shift_size), binmode: binmode, shift_period_suffix: shift_period_suffix)
|
|
@@ -398,14 +400,14 @@ module Lumberjack
|
|
|
398
400
|
return false unless device
|
|
399
401
|
|
|
400
402
|
# Prevent infinite recursion if logging is attempted from within a logging call.
|
|
401
|
-
if
|
|
403
|
+
if current_context_locals&.logging
|
|
402
404
|
log_to_stderr(severity, message)
|
|
403
405
|
return false
|
|
404
406
|
end
|
|
405
407
|
|
|
406
408
|
severity = Severity.label_to_level(severity) unless severity.is_a?(Integer)
|
|
407
409
|
|
|
408
|
-
|
|
410
|
+
new_context_locals do |locals|
|
|
409
411
|
locals.logging = true # protection from infinite loops
|
|
410
412
|
|
|
411
413
|
time = Time.now
|
data/lib/lumberjack.rb
CHANGED
|
@@ -42,7 +42,7 @@ module Lumberjack
|
|
|
42
42
|
require_relative "lumberjack/attributes_helper"
|
|
43
43
|
require_relative "lumberjack/context"
|
|
44
44
|
require_relative "lumberjack/context_logger"
|
|
45
|
-
require_relative "lumberjack/
|
|
45
|
+
require_relative "lumberjack/context_locals"
|
|
46
46
|
require_relative "lumberjack/io_compatibility"
|
|
47
47
|
require_relative "lumberjack/log_entry"
|
|
48
48
|
require_relative "lumberjack/log_entry_matcher"
|
|
@@ -68,6 +68,9 @@ module Lumberjack
|
|
|
68
68
|
@global_contexts_mutex = Mutex.new
|
|
69
69
|
@deprecation_mode = nil
|
|
70
70
|
@raise_logger_errors = false
|
|
71
|
+
@isolation_level = :fiber
|
|
72
|
+
|
|
73
|
+
extend ContextLocals
|
|
71
74
|
|
|
72
75
|
class << self
|
|
73
76
|
# Contexts can be used to store attributes that will be attached to all log entries in the block.
|
|
@@ -104,21 +107,15 @@ module Lumberjack
|
|
|
104
107
|
# @return [Object] The result of the block.
|
|
105
108
|
# @api private
|
|
106
109
|
def use_context(context, &block)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
unless block_given?
|
|
111
|
+
raise ArgumentError, "A block must be provided to the context method"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
new_context = Context.new(context)
|
|
115
|
+
new_context.parent = current_context
|
|
116
|
+
new_context_locals do |locals|
|
|
117
|
+
locals.context = new_context
|
|
113
118
|
yield
|
|
114
|
-
ensure
|
|
115
|
-
@global_contexts_mutex.synchronize do
|
|
116
|
-
if ctx.nil?
|
|
117
|
-
@global_contexts.delete(fiber_id)
|
|
118
|
-
else
|
|
119
|
-
@global_contexts[fiber_id] = ctx
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
119
|
end
|
|
123
120
|
end
|
|
124
121
|
|
|
@@ -126,7 +123,7 @@ module Lumberjack
|
|
|
126
123
|
#
|
|
127
124
|
# @return [Boolean]
|
|
128
125
|
def in_context?
|
|
129
|
-
|
|
126
|
+
!current_context.nil?
|
|
130
127
|
end
|
|
131
128
|
|
|
132
129
|
def context?
|
|
@@ -142,6 +139,19 @@ module Lumberjack
|
|
|
142
139
|
current_context&.attributes
|
|
143
140
|
end
|
|
144
141
|
|
|
142
|
+
# Set the isolation level for global contexts to be either per fiber or per thread. Default is :fiber.
|
|
143
|
+
#
|
|
144
|
+
# @param value [Symbol] The isolation level, either :fiber or :thread.
|
|
145
|
+
# @return [void]
|
|
146
|
+
def isolation_level=(value)
|
|
147
|
+
value = value&.to_sym
|
|
148
|
+
value = :fiber unless [:fiber, :thread].include?(value)
|
|
149
|
+
@isolation_level = value
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Symbol] The current isolation level.
|
|
153
|
+
attr_reader :isolation_level
|
|
154
|
+
|
|
145
155
|
# Alias for context_attributes to provide API compatibility with version 1.x.
|
|
146
156
|
# This method will eventually be removed.
|
|
147
157
|
#
|
|
@@ -220,7 +230,7 @@ module Lumberjack
|
|
|
220
230
|
private
|
|
221
231
|
|
|
222
232
|
def current_context
|
|
223
|
-
|
|
233
|
+
current_context_locals&.context
|
|
224
234
|
end
|
|
225
235
|
end
|
|
226
236
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lumberjack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -53,6 +53,7 @@ files:
|
|
|
53
53
|
- lib/lumberjack/attribute_formatter.rb
|
|
54
54
|
- lib/lumberjack/attributes_helper.rb
|
|
55
55
|
- lib/lumberjack/context.rb
|
|
56
|
+
- lib/lumberjack/context_locals.rb
|
|
56
57
|
- lib/lumberjack/context_logger.rb
|
|
57
58
|
- lib/lumberjack/device.rb
|
|
58
59
|
- lib/lumberjack/device/buffer.rb
|
|
@@ -66,7 +67,6 @@ files:
|
|
|
66
67
|
- lib/lumberjack/device/writer.rb
|
|
67
68
|
- lib/lumberjack/device_registry.rb
|
|
68
69
|
- lib/lumberjack/entry_formatter.rb
|
|
69
|
-
- lib/lumberjack/fiber_locals.rb
|
|
70
70
|
- lib/lumberjack/forked_logger.rb
|
|
71
71
|
- lib/lumberjack/formatter.rb
|
|
72
72
|
- lib/lumberjack/formatter/date_time_formatter.rb
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Lumberjack
|
|
4
|
-
# Provides isolated fiber-local storage for thread-safe data access.
|
|
5
|
-
module FiberLocals
|
|
6
|
-
# Lightweight structure to hold fiber-local data.
|
|
7
|
-
#
|
|
8
|
-
# @api private
|
|
9
|
-
class Data
|
|
10
|
-
attr_accessor :context, :logging, :cleared
|
|
11
|
-
|
|
12
|
-
def initialize(copy = nil)
|
|
13
|
-
@context = copy&.context
|
|
14
|
-
@logging = copy&.logging
|
|
15
|
-
@cleared = copy&.cleared
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def fiber_locals(&block)
|
|
22
|
-
init_fiber_locals! unless defined?(@fiber_locals)
|
|
23
|
-
|
|
24
|
-
fiber_id = Fiber.current.object_id
|
|
25
|
-
current = @fiber_locals[fiber_id]
|
|
26
|
-
data = Data.new(current)
|
|
27
|
-
begin
|
|
28
|
-
@fiber_locals_mutex.synchronize do
|
|
29
|
-
@fiber_locals[fiber_id] = data
|
|
30
|
-
end
|
|
31
|
-
yield data
|
|
32
|
-
ensure
|
|
33
|
-
@fiber_locals_mutex.synchronize do
|
|
34
|
-
if current.nil?
|
|
35
|
-
@fiber_locals.delete(fiber_id)
|
|
36
|
-
else
|
|
37
|
-
@fiber_locals[fiber_id] = current
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def fiber_local
|
|
44
|
-
return nil unless defined?(@fiber_locals)
|
|
45
|
-
|
|
46
|
-
@fiber_locals[Fiber.current.object_id]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Initialize the fiber locals storage and mutex.
|
|
50
|
-
def init_fiber_locals!
|
|
51
|
-
@fiber_locals ||= {}
|
|
52
|
-
@fiber_locals_mutex ||= Mutex.new
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|