rails-otel-context 0.8.3 → 0.8.5
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/rails_otel_context/activerecord_context.rb +6 -29
- data/lib/rails_otel_context/adapters/clickhouse.rb +2 -2
- data/lib/rails_otel_context/adapters/redis.rb +14 -26
- data/lib/rails_otel_context/call_context_processor.rb +23 -4
- data/lib/rails_otel_context/railtie.rb +7 -0
- data/lib/rails_otel_context/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c77108c1ddfab7776d31052f6633e7ec132aafb415f682099f571e5c4758db89
|
|
4
|
+
data.tar.gz: 262506de5a8bc5a4bbfa74341bdb79113fd54f4d0fe6d247c51cb35db395b006
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6dd47b5fcaea795a44e9e2db032fd77ccfddb97bef173be6ff1c19499e52e5ffbe26ff3b8a9ce8a5d4bf145f2f2aa58cd115aac04ac7ebf166d6dc98d0401ae6
|
|
7
|
+
data.tar.gz: 5ac0a1d07185865a92f79bab9895b04f5405b7f4a4978f6ded96878ad1b4bb0ed76c093bcd1a7ab912f5c1935f6989fb692fc4dd9d9000a2f211b294c777123d
|
|
@@ -13,12 +13,10 @@ module RailsOtelContext
|
|
|
13
13
|
# Transaction.recent_completed.to_a where the scope method returns before
|
|
14
14
|
# SQL fires.
|
|
15
15
|
module ActiveRecordContext
|
|
16
|
-
THREAD_KEY
|
|
17
|
-
SCOPE_THREAD_KEY
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
DB_SLOW_ATTR = 'db.slow'
|
|
21
|
-
private_constant :THREAD_KEY, :SCOPE_THREAD_KEY, :TIMING_ID_KEY, :TIMING_START_KEY
|
|
16
|
+
THREAD_KEY = :_rails_otel_ctx_ar
|
|
17
|
+
SCOPE_THREAD_KEY = :_rails_otel_ctx_scope
|
|
18
|
+
DB_SLOW_ATTR = 'db.slow'
|
|
19
|
+
private_constant :THREAD_KEY, :SCOPE_THREAD_KEY
|
|
22
20
|
|
|
23
21
|
# Frozen regex — only the verb regex remains; table extraction uses index+slice.
|
|
24
22
|
SQL_VERB_RE = /\A(\w+)/i
|
|
@@ -81,21 +79,10 @@ module RailsOtelContext
|
|
|
81
79
|
|
|
82
80
|
# Subscriber for sql.active_record notifications.
|
|
83
81
|
class Subscriber
|
|
84
|
-
def
|
|
85
|
-
@threshold = RailsOtelContext.configuration.slow_query_threshold_ms
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def start(_name, id, payload)
|
|
82
|
+
def start(_name, _id, payload)
|
|
89
83
|
ar_name = payload[:name]
|
|
90
84
|
return if ar_name == 'SCHEMA' || ar_name&.start_with?('CACHE')
|
|
91
85
|
|
|
92
|
-
# Set up slow-query timing regardless of whether we can map a model name,
|
|
93
|
-
# so that raw SQL (connection.execute, SELECT SLEEP, etc.) still sets db.slow.
|
|
94
|
-
if @threshold
|
|
95
|
-
Thread.current[TIMING_ID_KEY] = id
|
|
96
|
-
Thread.current[TIMING_START_KEY] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
86
|
ctx = if ar_name.nil? || ar_name == 'SQL'
|
|
100
87
|
ActiveRecordContext.parse_sql_context(payload[:sql])
|
|
101
88
|
else
|
|
@@ -124,15 +111,7 @@ module RailsOtelContext
|
|
|
124
111
|
ActiveRecordContext.apply_to_span(OpenTelemetry::Trace.current_span, ctx)
|
|
125
112
|
end
|
|
126
113
|
|
|
127
|
-
def finish(_name,
|
|
128
|
-
if @threshold && Thread.current[TIMING_ID_KEY].equal?(id)
|
|
129
|
-
elapsed_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - Thread.current[TIMING_START_KEY]) * 1000
|
|
130
|
-
Thread.current[TIMING_ID_KEY] = nil
|
|
131
|
-
if elapsed_ms >= @threshold
|
|
132
|
-
span = OpenTelemetry::Trace.current_span
|
|
133
|
-
span.set_attribute(DB_SLOW_ATTR, true) if span.context.valid?
|
|
134
|
-
end
|
|
135
|
-
end
|
|
114
|
+
def finish(_name, _id, _payload)
|
|
136
115
|
ensure
|
|
137
116
|
Thread.current[THREAD_KEY] = nil
|
|
138
117
|
end
|
|
@@ -196,8 +175,6 @@ module RailsOtelContext
|
|
|
196
175
|
def clear!
|
|
197
176
|
Thread.current[THREAD_KEY] = nil
|
|
198
177
|
Thread.current[SCOPE_THREAD_KEY] = nil
|
|
199
|
-
Thread.current[TIMING_ID_KEY] = nil
|
|
200
|
-
Thread.current[TIMING_START_KEY] = nil
|
|
201
178
|
end
|
|
202
179
|
|
|
203
180
|
# Test helpers: set AR context directly for unit tests.
|
|
@@ -64,7 +64,7 @@ module RailsOtelContext
|
|
|
64
64
|
define_method(method_name) do |*args, &block|
|
|
65
65
|
return super(*args, &block) if Thread.current[reentrancy_key]
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
site = mod.call_site_for_app
|
|
68
68
|
statement = args.first.is_a?(String) ? args.first : nil
|
|
69
69
|
|
|
70
70
|
tracer = OpenTelemetry.tracer_provider.tracer('rails-otel-context-clickhouse')
|
|
@@ -76,7 +76,7 @@ module RailsOtelContext
|
|
|
76
76
|
span.set_attribute('db.statement', statement) if statement
|
|
77
77
|
|
|
78
78
|
result = super(*args, &block)
|
|
79
|
-
mod.
|
|
79
|
+
mod.apply_call_site_to_span(span, site)
|
|
80
80
|
result
|
|
81
81
|
end
|
|
82
82
|
ensure
|
|
@@ -24,47 +24,35 @@ module RailsOtelContext
|
|
|
24
24
|
def build_patch_module
|
|
25
25
|
mod = Module.new do
|
|
26
26
|
class << self
|
|
27
|
+
include RailsOtelContext::SourceLocation
|
|
28
|
+
|
|
27
29
|
attr_accessor :app_root
|
|
28
30
|
|
|
29
31
|
def configure(app_root:)
|
|
30
32
|
@app_root = app_root.to_s
|
|
31
33
|
end
|
|
32
|
-
|
|
33
|
-
def source_location_for_app
|
|
34
|
-
return unless Thread.respond_to?(:each_caller_location)
|
|
35
|
-
|
|
36
|
-
Thread.each_caller_location do |location|
|
|
37
|
-
path = location.absolute_path || location.path
|
|
38
|
-
next unless path&.start_with?(app_root)
|
|
39
|
-
next if path.include?('/gems/')
|
|
40
|
-
|
|
41
|
-
return [path.delete_prefix("#{app_root}/"), location.lineno]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
nil
|
|
45
|
-
end
|
|
46
34
|
end
|
|
47
35
|
|
|
48
36
|
define_method(:call) do |command, redis_config, &block|
|
|
49
|
-
|
|
50
|
-
return super(command, redis_config, &block) unless
|
|
37
|
+
site = mod.call_site_for_app
|
|
38
|
+
return super(command, redis_config, &block) unless site
|
|
51
39
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
) do
|
|
40
|
+
attrs = { 'code.namespace' => site[:class_name], 'code.filepath' => site[:filepath] }
|
|
41
|
+
attrs['code.function'] = site[:method_name] if site[:method_name]
|
|
42
|
+
attrs['code.lineno'] = site[:lineno] if site[:lineno]
|
|
43
|
+
OpenTelemetry::Instrumentation::Redis.with_attributes(attrs) do
|
|
56
44
|
super(command, redis_config, &block)
|
|
57
45
|
end
|
|
58
46
|
end
|
|
59
47
|
|
|
60
48
|
define_method(:call_pipelined) do |commands, redis_config, &block|
|
|
61
|
-
|
|
62
|
-
return super(commands, redis_config, &block) unless
|
|
49
|
+
site = mod.call_site_for_app
|
|
50
|
+
return super(commands, redis_config, &block) unless site
|
|
63
51
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
) do
|
|
52
|
+
attrs = { 'code.namespace' => site[:class_name], 'code.filepath' => site[:filepath] }
|
|
53
|
+
attrs['code.function'] = site[:method_name] if site[:method_name]
|
|
54
|
+
attrs['code.lineno'] = site[:lineno] if site[:lineno]
|
|
55
|
+
OpenTelemetry::Instrumentation::Redis.with_attributes(attrs) do
|
|
68
56
|
super(commands, redis_config, &block)
|
|
69
57
|
end
|
|
70
58
|
end
|
|
@@ -39,9 +39,10 @@ module RailsOtelContext
|
|
|
39
39
|
|
|
40
40
|
def initialize(app_root:, config: RailsOtelContext.configuration)
|
|
41
41
|
@app_root = app_root.to_s
|
|
42
|
-
@request_context_enabled
|
|
43
|
-
@custom_span_attributes
|
|
44
|
-
@span_name_formatter
|
|
42
|
+
@request_context_enabled = config.request_context_enabled
|
|
43
|
+
@custom_span_attributes = config.custom_span_attributes
|
|
44
|
+
@span_name_formatter = config.span_name_formatter
|
|
45
|
+
@slow_query_threshold_ms = config.slow_query_threshold_ms
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def on_start(span, _parent_context)
|
|
@@ -51,7 +52,25 @@ module RailsOtelContext
|
|
|
51
52
|
apply_custom_attributes(span) if @custom_span_attributes
|
|
52
53
|
end
|
|
53
54
|
|
|
54
|
-
def on_finish(
|
|
55
|
+
def on_finish(span)
|
|
56
|
+
return unless @slow_query_threshold_ms
|
|
57
|
+
return unless span.respond_to?(:attributes) && span.attributes&.key?('db.system')
|
|
58
|
+
|
|
59
|
+
start_ns = span.start_timestamp
|
|
60
|
+
end_ns = span.end_timestamp
|
|
61
|
+
return unless start_ns && end_ns
|
|
62
|
+
|
|
63
|
+
duration_ms = (end_ns - start_ns) / 1_000_000.0
|
|
64
|
+
return unless duration_ms >= @slow_query_threshold_ms
|
|
65
|
+
|
|
66
|
+
# span.recording? is false here — the span has finished and current_span
|
|
67
|
+
# has reverted to the HTTP parent. Write directly to the backing attributes
|
|
68
|
+
# hash so db.slow lands on the actual DB span, not the HTTP parent.
|
|
69
|
+
attrs = span.instance_variable_get(:@attributes)
|
|
70
|
+
attrs.store(ActiveRecordContext::DB_SLOW_ATTR, true) if attrs.respond_to?(:store)
|
|
71
|
+
rescue StandardError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
55
74
|
|
|
56
75
|
def force_flush(timeout: nil); end
|
|
57
76
|
|
|
@@ -41,10 +41,17 @@ module RailsOtelContext
|
|
|
41
41
|
initializer 'rails_otel_context.install_frame_context' do
|
|
42
42
|
ActiveSupport.on_load(:action_controller) do
|
|
43
43
|
around_action do |_controller, block|
|
|
44
|
+
# Reset N+1 query counter at the start of every request so counts
|
|
45
|
+
# never bleed across requests on Puma's reused threads. This runs
|
|
46
|
+
# regardless of request_context_enabled, which only gates whether
|
|
47
|
+
# RequestContext.set is called (and would reset it there too).
|
|
48
|
+
Thread.current[RailsOtelContext::RequestContext::QUERY_COUNT_KEY] = nil
|
|
44
49
|
RailsOtelContext::FrameContext.with_frame(
|
|
45
50
|
class_name: self.class.name,
|
|
46
51
|
method_name: action_name
|
|
47
52
|
) { block.call }
|
|
53
|
+
ensure
|
|
54
|
+
Thread.current[RailsOtelContext::RequestContext::QUERY_COUNT_KEY] = nil
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
end
|