Class: RailsOtelContext::ActiveRecordContext::Subscriber

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_otel_context/activerecord_context.rb

Overview

Subscriber for sql.active_record notifications.

Instance Method Summary collapse

Instance Method Details

#finish(_name, _id, _payload) ⇒ Object



125
126
127
128
129
# File 'lib/rails_otel_context/activerecord_context.rb', line 125

def finish(_name, _id, _payload)
ensure
  Thread.current[THREAD_KEY] = nil
  Thread.current[PENDING_PREPARE_KEY] = nil # clear any leftovers from skipped notifications
end

#start(_name, _id, payload) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/rails_otel_context/activerecord_context.rb', line 83

def start(_name, _id, payload)
  ar_name = payload[:name]
  return if ar_name == 'SCHEMA' || ar_name&.start_with?('CACHE')

  ctx = if ar_name.nil? || ar_name == 'SQL'
          ActiveRecordContext.parse_sql_context(payload[:sql])
        else
          ActiveRecordContext.parse_ar_name(ar_name)
        end
  return unless ctx

  # Include scope name if one was captured by RelationScopeCapture
  scope = Thread.current[SCOPE_THREAD_KEY]
  ctx[:scope_name] = scope if scope

  query_key = ctx[:query_key]
  counts = (Thread.current[RequestContext::QUERY_COUNT_KEY] ||= {})
  count = (counts[query_key] = (counts[query_key] || 0) + 1)
  ctx[:query_count] = count if count > 1

  ctx[:async] = true if payload[:async]
  Thread.current[THREAD_KEY] = ctx

  return unless defined?(OpenTelemetry::Trace)

  # Enrich the current span directly. When OTel instruments via driver-level
  # prepend (Trilogy, PG, Mysql2), the span is created BEFORE this notification
  # fires, so CallContextProcessor#on_start sees nil AR context. Applying here
  # fixes those spans after the fact.
  ActiveRecordContext.apply_to_span(OpenTelemetry::Trace.current_span, ctx)

  # Retroactively enrich any PREPARE spans that finished before this notification
  # fired. PG's prepared-statement flow sends PREPARE then EXECUTE as separate wire
  # operations; the PREPARE span finishes before sql.active_record starts, so it
  # never sees AR context. CallContextProcessor#on_finish stashes those spans here.
  pending = Thread.current[PENDING_PREPARE_KEY]
  return unless pending

  pending.each { |s| ActiveRecordContext.retroactively_apply_to_span(s, ctx) }
  Thread.current[PENDING_PREPARE_KEY] = nil
end