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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b76516c0e871b68e2834d0517dd219eefd0985fd55894c48e8e8c4fa9e71be8
4
- data.tar.gz: 2516f75995e1ca98a7d0daabf5ea71b1a3a2e4593966a955bac9bc66dea5827e
3
+ metadata.gz: 140e49b15017aa81ca85a2e42a9de896b09938b5f9dd437a8f2917f821831d3d
4
+ data.tar.gz: fe95870efcfb98d6831833e4125a74817f93c718969febcdba81e0b3b6e149a1
5
5
  SHA512:
6
- metadata.gz: a6df7a99b4b8bba7a694cdcb45053185b7f2d40bb18af18af354e0188eba00b2f04156536e0844c0ddc74e38e3661821e60b357d4eef8928a3de6bd2bf0af1da
7
- data.tar.gz: ad698517a3a54969c582d6cb839c218894a89b301c137bf2bdc08889777d50343ca4ccaf26d4a40ae96bfd3601790481267729e15eadc4d8f2cc7abbc687fd06
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.2
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 "fiber_locals"
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(FiberLocals) unless base.include?(FiberLocals)
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
- fiber_locals do |locals|
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
- fiber_locals do |locals|
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
- fiber_local&.context
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 fiber_local&.cleared
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
- init_fiber_locals!
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
@@ -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
- init_fiber_locals!
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 fiber_local&.logging
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
- fiber_locals do |locals|
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/fiber_locals"
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
- fiber_id = Fiber.current.object_id
108
- ctx = @global_contexts[fiber_id]
109
- begin
110
- @global_contexts_mutex.synchronize do
111
- @global_contexts[fiber_id] = (context || Context.new)
112
- end
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
- !!@global_contexts[Fiber.current.object_id]
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
- @global_contexts[Fiber.current.object_id]
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.2
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