contextual_logger 1.2.0.pre.1 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5ee8cad9d62ca2d8aeec81f2626b56b4aa6b1db2c6b626518a4708d590ddb4c
4
- data.tar.gz: 71be08f271be68de04f942abfa301a2a5ec54be54d0399bb01db8bcf52a07037
3
+ metadata.gz: 1345e9a0914c195f20d4cb22012452faff2bde5c9a869ee41db6a5c80ac70b03
4
+ data.tar.gz: 9614e6145e032712d0e8ebf64b1266ed7872200be70a782f5b1a9ed54edeeb1d
5
5
  SHA512:
6
- metadata.gz: 2b455d41a46223ea9c6e2583a7d03d81940743215c4eb1dd5977d6ffc8f8ffcb618ce27a3b975fed211987b592e0cfdbc1e691ceef155641764c5d8938da0679
7
- data.tar.gz: 6600b357b1dc7b25ee8e746f17280a7e74ac184b38a03340f48e835659259135321a0c0ff0d7c387a7b06b9804b82ad63f8b76a911790a6fb5c2f99a61c0cfdc
6
+ metadata.gz: ec61e408108a4485223e9c4d07029957e345f39d689d797db26263c37bfe5264661f11909ac808e2e0102176b172a7f2867b1d3bec624e58f3a927c6f53996e3
7
+ data.tar.gz: 28976f222764d2323103690a8e76896b85914f59f594c0264554fd502832dfedc4fdeb2fac988baa98f6998dedadbb283e2da3ff4a647dd0341446d7b0cc99c4
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ContextualLogger
4
+ module Context
5
+ EMPTY_CONTEXT = {}.freeze
6
+
7
+ def thread_context_key_for_logger_instance
8
+ # We include the object_id here to make these thread/fiber locals unique per logger instance.
9
+ @thread_context_key_for_logger_instance ||= "ContextualLogger::Context.context_for_#{object_id}".to_sym
10
+ end
11
+
12
+ def current_context_override
13
+ Thread.current[thread_context_key_for_logger_instance]
14
+ end
15
+
16
+ def current_context_override=(context_override)
17
+ ContextualLogger.global_context_lock_message ||= "ContextualLogger::Context.current_context_override set for #{self.class.name} #{object_id}: #{context_override.inspect}"
18
+ Thread.current[thread_context_key_for_logger_instance] = context_override.freeze
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ContextualLogger
4
+ class ContextHandler
5
+ def initialize(instance, previous_context_override)
6
+ @instance = instance
7
+ @previous_context_override = previous_context_override
8
+ end
9
+
10
+ def reset!
11
+ @instance.current_context_override = @previous_context_override
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ContextualLogger
4
+ class << self
5
+ attr_accessor :global_context_lock_message # nil or a string indicating what locked the global context
6
+ end
7
+
8
+ class GlobalContextIsLocked < StandardError
9
+ end
10
+ end
@@ -6,6 +6,13 @@ module ContextualLogger
6
6
  # A logger that deep_merges additional context and then delegates to the given logger.
7
7
  # Keeps it own log level (called override_level) that may be set independently of the logger it delegates to.
8
8
  # If override_level is non-nil, it takes precedence; if it is nil (the default), then it delegates to the logger.
9
+ #
10
+ # Context Precedence:
11
+ # 1. inline **context passed to the logger method
12
+ # 2. `with_context` overrides on this LoggerWithContext object
13
+ # 3. context passed to this LoggerWithContext constructor
14
+ # 4. `with_context` overrides on the logger passed to this constructor
15
+ # 5. `global_context` set on the logger passed to this constructor
9
16
  class LoggerWithContext
10
17
  include LoggerMixin
11
18
 
@@ -16,7 +23,13 @@ module ContextualLogger
16
23
  @logger = logger
17
24
  self.level = level
18
25
  @context = normalize_context(context)
19
- @merged_context_cache = {} # so we don't have to merge every time
26
+ end
27
+
28
+ # TODO: It's a (small) bug that the global_context is memoized at this point. There's a chance that the @logger.current_context
29
+ # changes after this because of an enclosing @logger.with_context block. If that happens, we'll miss that extra context.
30
+ # The tradeoff is that we don't want to keep calling deep_merge.
31
+ def global_context
32
+ @global_context ||= @logger.current_context.deep_merge(@context) # this will include any with_context overrides on the `logger`
20
33
  end
21
34
 
22
35
  def level
@@ -29,10 +42,10 @@ module ContextualLogger
29
42
 
30
43
  def write_entry_to_log(severity, timestamp, progname, message, context:)
31
44
  merged_context =
32
- if @merged_context_cache.size >= 1000 # keep this cache memory use finite
33
- @merged_context_cache[context] || @context.deep_merge(context)
45
+ if context.any?
46
+ current_context.deep_merge(context)
34
47
  else
35
- @merged_context_cache[context] ||= @context.deep_merge(context)
48
+ current_context
36
49
  end
37
50
 
38
51
  @logger.write_entry_to_log(severity, timestamp, progname, message, context: merged_context)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ContextualLogger
4
- VERSION = '1.2.0.pre.1'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -4,7 +4,9 @@ require 'active_support'
4
4
  require 'active_support/core_ext/module/delegation'
5
5
  require 'json'
6
6
  require_relative './contextual_logger/redactor'
7
- require_relative './contextual_logger/context/handler'
7
+ require_relative './contextual_logger/context'
8
+ require_relative './contextual_logger/context_handler'
9
+ require_relative './contextual_logger/global_context_lock_message'
8
10
 
9
11
  module ContextualLogger
10
12
  LOG_LEVEL_NAMES_TO_SEVERITY =
@@ -42,16 +44,37 @@ module ContextualLogger
42
44
  end
43
45
  end
44
46
 
47
+ # Context Precedence when this is mixed into a logger:
48
+ # 1. inline **context passed to the logger method
49
+ # 2. `with_context` overrides on the logger object
50
+ # 3. `global_context` set on the logger passed to this constructor
45
51
  module LoggerMixin
52
+ include Context
53
+
46
54
  delegate :register_secret, :register_secret_regex, to: :redactor
47
55
 
56
+ def global_context
57
+ @global_context ||= Context::EMPTY_CONTEXT
58
+ end
59
+
48
60
  def global_context=(context)
49
- Context::Handler.new(context).set!
61
+ if (global_context_lock_message = ::ContextualLogger.global_context_lock_message)
62
+ raise ::ContextualLogger::GlobalContextIsLocked, global_context_lock_message
63
+ end
64
+ @global_context = context.freeze
50
65
  end
51
66
 
52
- def with_context(context)
53
- context_handler = Context::Handler.new(current_context_for_thread.deep_merge(context))
54
- context_handler.set!
67
+ def current_context
68
+ current_context_override || global_context
69
+ end
70
+
71
+ # TODO: Deprecate current_context_for_thread in v2.0.
72
+ alias current_context_for_thread current_context
73
+
74
+ def with_context(stacked_context)
75
+ context_handler = ContextHandler.new(self, current_context_override)
76
+ self.current_context_override = deep_merge_with_current_context(stacked_context)
77
+
55
78
  if block_given?
56
79
  begin
57
80
  yield
@@ -59,15 +82,11 @@ module ContextualLogger
59
82
  context_handler.reset!
60
83
  end
61
84
  else
62
- # If no block given, the context handler is returned to the caller so they can handle reset! themselves.
85
+ # If no block given, return context handler to the caller so they can call reset! themselves.
63
86
  context_handler
64
87
  end
65
88
  end
66
89
 
67
- def current_context_for_thread
68
- Context::Handler.current_context
69
- end
70
-
71
90
  # In the methods generated below, we assume that presence of context means new code that is
72
91
  # aware of ContextualLogger...and that that code never uses progname.
73
92
  # This is important because we only get 3 args total (not including &block) passed to `add`,
@@ -102,7 +121,7 @@ module ContextualLogger
102
121
 
103
122
  # Note that this interface needs to stay compatible with the underlying ::Logger#add interface,
104
123
  # which is: def add(severity, message = nil, progname = nil)
105
- def add(arg_severity, arg1 = nil, arg2 = nil, **context) # Ruby will prefer to match hashes up to last ** argument
124
+ def add(arg_severity, arg1 = nil, arg2 = nil, **context) # Ruby will prefer to match hashes to last argument because of **
106
125
  severity = arg_severity || UNKNOWN
107
126
  if log_level_enabled?(severity)
108
127
  if arg1.nil?
@@ -117,7 +136,7 @@ module ContextualLogger
117
136
  message = arg1
118
137
  progname = arg2 || @progname
119
138
  end
120
- write_entry_to_log(severity, Time.now, progname, message, context: current_context_for_thread.deep_merge(context))
139
+ write_entry_to_log(severity, Time.now, progname, message, context: deep_merge_with_current_context(context))
121
140
  end
122
141
 
123
142
  true
@@ -141,7 +160,7 @@ module ContextualLogger
141
160
  normalized_message = ContextualLogger.normalize_message(message)
142
161
  normalized_progname = ContextualLogger.normalize_message(progname) unless progname.nil?
143
162
  if @formatter
144
- @formatter.call(severity, timestamp, normalized_progname, { message: normalized_message }.merge!(context))
163
+ @formatter.call(severity, timestamp, normalized_progname, { message: normalized_message, **context })
145
164
  else
146
165
  "#{basic_json_log_entry(severity, timestamp, normalized_progname, normalized_message, context: context)}\n"
147
166
  end
@@ -151,12 +170,20 @@ module ContextualLogger
151
170
  message_hash = {
152
171
  message: normalized_progname ? "#{normalized_progname}: #{normalized_message}" : normalized_message,
153
172
  severity: severity,
154
- timestamp: timestamp
173
+ timestamp: timestamp,
174
+ **context
155
175
  }
156
176
  message_hash[:progname] = normalized_progname if normalized_progname
157
177
 
158
- # merge! is faster and OK here since message_hash is still local only to this method
159
- message_hash.merge!(context).to_json
178
+ message_hash.to_json
179
+ end
180
+
181
+ def deep_merge_with_current_context(stacked_context)
182
+ if stacked_context.any?
183
+ current_context.deep_merge(stacked_context)
184
+ else
185
+ current_context
186
+ end
160
187
  end
161
188
  end
162
189
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contextual_logger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0.pre.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Ebentier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-09 00:00:00.000000000 Z
11
+ date: 2023-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -45,7 +45,9 @@ extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
47
  - lib/contextual_logger.rb
48
- - lib/contextual_logger/context/handler.rb
48
+ - lib/contextual_logger/context.rb
49
+ - lib/contextual_logger/context_handler.rb
50
+ - lib/contextual_logger/global_context_lock_message.rb
49
51
  - lib/contextual_logger/logger_with_context.rb
50
52
  - lib/contextual_logger/overrides/active_support/tagged_logging/formatter.rb
51
53
  - lib/contextual_logger/redactor.rb
@@ -67,9 +69,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
69
  version: '0'
68
70
  required_rubygems_version: !ruby/object:Gem::Requirement
69
71
  requirements:
70
- - - ">"
72
+ - - ">="
71
73
  - !ruby/object:Gem::Version
72
- version: 1.3.1
74
+ version: '0'
73
75
  requirements: []
74
76
  rubygems_version: 3.1.6
75
77
  signing_key:
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ContextualLogger
4
- module Context
5
- class Handler
6
- THREAD_CONTEXT_NAMESPACE = 'ContextualLoggerCurrentLoggingContext'
7
-
8
- attr_reader :previous_context, :context
9
-
10
- def self.current_context
11
- Thread.current[THREAD_CONTEXT_NAMESPACE] || {}
12
- end
13
-
14
- def initialize(context, previous_context: nil)
15
- @previous_context = previous_context || self.class.current_context
16
- @context = context
17
- end
18
-
19
- def set!
20
- Thread.current[THREAD_CONTEXT_NAMESPACE] = context
21
- end
22
-
23
- def reset!
24
- Thread.current[THREAD_CONTEXT_NAMESPACE] = previous_context
25
- end
26
- end
27
- end
28
- end