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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 004afd0d90983e7e0acbe3f53e498e9c8583f67cb9d1f0eac650f9d801a7bb6d
4
- data.tar.gz: 96da00012f2fb29cf86bd99530d9cdaf7be375135612543b29f7acf60e73569e
3
+ metadata.gz: c77108c1ddfab7776d31052f6633e7ec132aafb415f682099f571e5c4758db89
4
+ data.tar.gz: 262506de5a8bc5a4bbfa74341bdb79113fd54f4d0fe6d247c51cb35db395b006
5
5
  SHA512:
6
- metadata.gz: d909cd98bcb6a018a8e957765c3e4ae5bfcf4d583536c3819aa639ec63a7029e81f9cda60e80f2ba7961490a0e6e19d8c32d976dc9f7ee8cc4ef09e035bb5810
7
- data.tar.gz: 0a6b2433ec72221942b287a584def552ebd59b5857db83d58c41051f75c2a3b23f9aa5380b74db1a08f28d201a5791dfee1ee0254b8d140e8cf55f3c299d1c87
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 = :_rails_otel_ctx_ar
17
- SCOPE_THREAD_KEY = :_rails_otel_ctx_scope
18
- TIMING_ID_KEY = :_rails_otel_ctx_timing_id
19
- TIMING_START_KEY = :_rails_otel_ctx_timing_start
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 initialize
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, id, _payload)
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
- source = mod.source_location_for_app
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.apply_source_to_span(span, source)
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
- source = mod.source_location_for_app
50
- return super(command, redis_config, &block) unless source
37
+ site = mod.call_site_for_app
38
+ return super(command, redis_config, &block) unless site
51
39
 
52
- OpenTelemetry::Instrumentation::Redis.with_attributes(
53
- 'code.filepath' => source[0],
54
- 'code.lineno' => source[1]
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
- source = mod.source_location_for_app
62
- return super(commands, redis_config, &block) unless source
49
+ site = mod.call_site_for_app
50
+ return super(commands, redis_config, &block) unless site
63
51
 
64
- OpenTelemetry::Instrumentation::Redis.with_attributes(
65
- 'code.filepath' => source[0],
66
- 'code.lineno' => source[1]
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 = config.request_context_enabled
43
- @custom_span_attributes = config.custom_span_attributes
44
- @span_name_formatter = config.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(_span); end
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsOtelContext
4
- VERSION = '0.8.3'
4
+ VERSION = '0.8.5'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-otel-context
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.8.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Last9