Module: Axn::Internal::Tracing
- Defined in:
- lib/axn/internal/tracing.rb
Class Method Summary collapse
-
.autodetected_tracer ⇒ Object
The OpenTelemetry tracer, when OpenTelemetry is loaded.
-
.reset! ⇒ Object
Drops the auto-detection and capability memos, for specs that swap the OpenTelemetry constant or the tracer out from under them.
-
.supports_record_exception_option?(tracer) ⇒ Boolean
Whether THIS tracer's #in_span accepts the
record_exception:option (added in opentelemetry-api 1.7.0).
Class Method Details
.autodetected_tracer ⇒ Object
The OpenTelemetry tracer, when OpenTelemetry is loaded. Mechanism only: whether axn traces at all, and with which tracer, is Axn.config.tracer's decision. The presence check runs on every call because OpenTelemetry can be loaded lazily, and the provider is re-consulted because it can be replaced (a host app configuring the SDK after boot, or a test swapping in a mock).
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/axn/internal/tracing.rb', line 23 def autodetected_tracer return nil unless defined?(OpenTelemetry) current_provider = OpenTelemetry.tracer_provider cached = @tracer_entry # Identity, not `==`: the provider is an object the host app supplies, and validating a cache # must not run its code. A provider whose `==` answers true for a DIFFERENT provider would # pin spans to the replaced one forever, and one whose `==` raises a class axn never swallows # would abort the call from inside an optional lookup — `autodetected_tracer` resolves # outside the probe's rescue. Same seam and same reason as the capability memo below. return cached.last if cached && Identity.same?(cached.first, current_provider) # ONE immutable entry, assigned in a single reference store. Two ivars cannot express this # safely: a failed acquisition could leave them mismatched (a pair the check above reads as a # hit, pinning spans to the previous provider), and two threads racing on a provider swap # could interleave their writes into a pair belonging to neither. A reader sees the whole old # entry or the whole new one, so at worst it re-derives. tracer = current_provider.tracer("axn", Axn::VERSION) @tracer_entry = [current_provider, tracer].freeze tracer end |
.reset! ⇒ Object
Drops the auto-detection and capability memos, for specs that swap the OpenTelemetry constant or the tracer out from under them.
96 97 98 99 100 |
# File 'lib/axn/internal/tracing.rb', line 96 def reset! %i[@tracer_entry @probe_entry].each do |ivar| remove_instance_variable(ivar) if instance_variable_defined?(ivar) end end |
.supports_record_exception_option?(tracer) ⇒ Boolean
Whether THIS tracer's #in_span accepts the record_exception: option (added in
opentelemetry-api 1.7.0). Asks the object actually being called rather than
OpenTelemetry::Trace::Tracer, whose signature says nothing about an injected tracer and can
be wrong in both directions.
Only a parameter actually DECLARED AS A KEYWORD (:key/:keyreq) counts — sharing the name
record_exception is not enough on its own. Two parameter shapes read as unsupported
despite matching the name:
- `**opts` (:keyrest): axn omits the option, so an OTel >= 1.7 tracer underneath records
the exception on top of axn's own `span.record_exception` — a duplicate event, not a
crash.
- a same-named POSITIONAL parameter (`def in_span(name, record_exception)`, :req):
without this check, `tracer.in_span(name, **kwargs)` would fold the whole kwargs hash
onto that one positional slot instead of binding the boolean axn intends.
Both are the safe direction to be wrong in: over-detecting instead sends the option to a
tracer whose #in_span has no slot for it at all, and in_span is called outside
best_effort, so the resulting ArgumentError would take down the call.
Single-slot identity memo: there is one tracer per process, so a map would model tracers that never exist, and the slot holds no reference Axn.config isn't already holding.
Both checks below go through Internal::Identity rather than dispatching nil?/equal? to
the tracer: a proxy that overrides or forwards either would run on every call after the
first, outside the probe's rescue, and take down an action over an optional lookup.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/axn/internal/tracing.rb', line 69 def supports_record_exception_option?(tracer) return false if Identity.nil_value?(tracer) cached = @probe_entry return cached.last if cached && Identity.same?(cached.first, tracer) supported = begin tracer.method(:in_span).parameters.any? { |type, name| name == :record_exception && %i[key keyreq].include?(type) } rescue StandardError, *Axn::Extensions::SWALLOWABLE_BEYOND_STANDARD_ERROR # Every class axn permits itself to absorb, not just StandardError: a proxy raising # SystemStackError from an overridden #method would otherwise escape an optional signature # lookup and abort the call. The answer for any failed probe is the same — assume the # option is unsupported and omit it. false end # One immutable entry, for the same reasons as the tracer memo above: a probe that unwinds # non-locally must not leave a tracer keyed to the PREVIOUS tracer's answer, and two threads # probing different tracers must not interleave into a pair belonging to neither — which # would hand one tracer the other's answer and send `record_exception:` to something with no # slot for it. @probe_entry = [tracer, supported].freeze supported end |