contextual_logger 1.2.0.pre.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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