contextual_logger 1.2.0.pre.1 → 1.3.0.dg.pre.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 +4 -4
- data/lib/contextual_logger/context.rb +21 -0
- data/lib/contextual_logger/context_handler.rb +14 -0
- data/lib/contextual_logger/global_context_lock_message.rb +10 -0
- data/lib/contextual_logger/logger_with_context.rb +17 -4
- data/lib/contextual_logger/version.rb +1 -1
- data/lib/contextual_logger.rb +75 -16
- metadata +5 -3
- data/lib/contextual_logger/context/handler.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96fa2fd042325883674d751a6b2443c23e9e6b195f9f15ade2826832c6557735
|
4
|
+
data.tar.gz: 180923158cdaac1c7cd63755ca93e4cda42aaf48bab442b21981264a255a5765
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66081a0a05dded370cf77b2e60b2c20884e8993a362bf6be4674b3c4b0c95d4811c208b8b6ab64ea99495fc7ca05b16ba9ef779cb39f4169620a06eac0c12fce
|
7
|
+
data.tar.gz: 04d1997d713eec2231a08e2085728db8ef438268835b4ac6e3eb866ec01c7d5df0447677de127c286236aa0485d4e233b36214234aa661cd0c30c6fe52eaaef8
|
@@ -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
|
@@ -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
|
-
|
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
|
33
|
-
|
45
|
+
if context.any?
|
46
|
+
current_context.deep_merge(context)
|
34
47
|
else
|
35
|
-
|
48
|
+
current_context
|
36
49
|
end
|
37
50
|
|
38
51
|
@logger.write_entry_to_log(severity, timestamp, progname, message, context: merged_context)
|
data/lib/contextual_logger.rb
CHANGED
@@ -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
|
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 =
|
@@ -17,6 +19,9 @@ module ContextualLogger
|
|
17
19
|
unknown: Logger::Severity::UNKNOWN
|
18
20
|
}.freeze
|
19
21
|
|
22
|
+
class LambdaAlreadyDefinedError < StandardError
|
23
|
+
end
|
24
|
+
|
20
25
|
class << self
|
21
26
|
def new(logger)
|
22
27
|
logger.extend(LoggerMixin)
|
@@ -42,16 +47,57 @@ module ContextualLogger
|
|
42
47
|
end
|
43
48
|
end
|
44
49
|
|
50
|
+
# Context Precedence when this is mixed into a logger:
|
51
|
+
# 1. inline **context passed to the logger method
|
52
|
+
# 2. `with_context` overrides on the logger object
|
53
|
+
# 3. `global_context` set on the logger passed to this constructor
|
45
54
|
module LoggerMixin
|
55
|
+
include Context
|
56
|
+
|
46
57
|
delegate :register_secret, :register_secret_regex, to: :redactor
|
47
58
|
|
59
|
+
def global_context
|
60
|
+
@global_context ||= Context::EMPTY_CONTEXT
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_global_context_lambda(field, lambda)
|
64
|
+
if field.blank?
|
65
|
+
raise ArgumentError, "The field cannot be empty"
|
66
|
+
end
|
67
|
+
|
68
|
+
unless lambda.respond_to?(:call)
|
69
|
+
raise ArgumentError, "A lambda must respond to the :call method"
|
70
|
+
end
|
71
|
+
|
72
|
+
if global_context_lambdas[field]
|
73
|
+
raise ::ContextualLogger::LambdaAlreadyDefinedError, "A lambda for `#{field}` is already defined"
|
74
|
+
end
|
75
|
+
|
76
|
+
@global_context_lambdas[field] = lambda
|
77
|
+
end
|
78
|
+
|
79
|
+
def global_context_lambdas
|
80
|
+
@global_context_lambdas ||= {}
|
81
|
+
end
|
82
|
+
|
48
83
|
def global_context=(context)
|
49
|
-
|
84
|
+
if (global_context_lock_message = ::ContextualLogger.global_context_lock_message)
|
85
|
+
raise ::ContextualLogger::GlobalContextIsLocked, global_context_lock_message
|
86
|
+
end
|
87
|
+
@global_context = context.freeze
|
88
|
+
end
|
89
|
+
|
90
|
+
def current_context
|
91
|
+
current_context_override || global_context
|
50
92
|
end
|
51
93
|
|
52
|
-
|
53
|
-
|
54
|
-
|
94
|
+
# TODO: Deprecate current_context_for_thread in v2.0.
|
95
|
+
alias current_context_for_thread current_context
|
96
|
+
|
97
|
+
def with_context(stacked_context)
|
98
|
+
context_handler = ContextHandler.new(self, current_context_override)
|
99
|
+
self.current_context_override = deep_merge_with_current_context(stacked_context)
|
100
|
+
|
55
101
|
if block_given?
|
56
102
|
begin
|
57
103
|
yield
|
@@ -59,15 +105,11 @@ module ContextualLogger
|
|
59
105
|
context_handler.reset!
|
60
106
|
end
|
61
107
|
else
|
62
|
-
# If no block given,
|
108
|
+
# If no block given, return context handler to the caller so they can call reset! themselves.
|
63
109
|
context_handler
|
64
110
|
end
|
65
111
|
end
|
66
112
|
|
67
|
-
def current_context_for_thread
|
68
|
-
Context::Handler.current_context
|
69
|
-
end
|
70
|
-
|
71
113
|
# In the methods generated below, we assume that presence of context means new code that is
|
72
114
|
# aware of ContextualLogger...and that that code never uses progname.
|
73
115
|
# This is important because we only get 3 args total (not including &block) passed to `add`,
|
@@ -102,7 +144,7 @@ module ContextualLogger
|
|
102
144
|
|
103
145
|
# Note that this interface needs to stay compatible with the underlying ::Logger#add interface,
|
104
146
|
# 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
|
147
|
+
def add(arg_severity, arg1 = nil, arg2 = nil, **context) # Ruby will prefer to match hashes to last argument because of **
|
106
148
|
severity = arg_severity || UNKNOWN
|
107
149
|
if log_level_enabled?(severity)
|
108
150
|
if arg1.nil?
|
@@ -117,7 +159,8 @@ module ContextualLogger
|
|
117
159
|
message = arg1
|
118
160
|
progname = arg2 || @progname
|
119
161
|
end
|
120
|
-
|
162
|
+
full_context = evaluate_global_context_lambdas(deep_merge_with_current_context(context))
|
163
|
+
write_entry_to_log(severity, Time.now, progname, message, context: full_context)
|
121
164
|
end
|
122
165
|
|
123
166
|
true
|
@@ -141,7 +184,7 @@ module ContextualLogger
|
|
141
184
|
normalized_message = ContextualLogger.normalize_message(message)
|
142
185
|
normalized_progname = ContextualLogger.normalize_message(progname) unless progname.nil?
|
143
186
|
if @formatter
|
144
|
-
@formatter.call(severity, timestamp, normalized_progname, { message: normalized_message }
|
187
|
+
@formatter.call(severity, timestamp, normalized_progname, { message: normalized_message, **context })
|
145
188
|
else
|
146
189
|
"#{basic_json_log_entry(severity, timestamp, normalized_progname, normalized_message, context: context)}\n"
|
147
190
|
end
|
@@ -151,12 +194,28 @@ module ContextualLogger
|
|
151
194
|
message_hash = {
|
152
195
|
message: normalized_progname ? "#{normalized_progname}: #{normalized_message}" : normalized_message,
|
153
196
|
severity: severity,
|
154
|
-
timestamp: timestamp
|
197
|
+
timestamp: timestamp,
|
198
|
+
**context
|
155
199
|
}
|
156
200
|
message_hash[:progname] = normalized_progname if normalized_progname
|
157
201
|
|
158
|
-
|
159
|
-
|
202
|
+
message_hash.to_json
|
203
|
+
end
|
204
|
+
|
205
|
+
def deep_merge_with_current_context(stacked_context)
|
206
|
+
if stacked_context.any?
|
207
|
+
current_context.deep_merge(stacked_context)
|
208
|
+
else
|
209
|
+
current_context
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def evaluate_global_context_lambdas(context)
|
214
|
+
if global_context_lambdas.empty?
|
215
|
+
context
|
216
|
+
else
|
217
|
+
global_context_lambdas.transform_values { |lambda| lambda.call }.merge(context)
|
218
|
+
end
|
160
219
|
end
|
161
220
|
end
|
162
221
|
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.
|
4
|
+
version: 1.3.0.dg.pre.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-
|
11
|
+
date: 2023-10-13 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
|
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
|
@@ -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
|