Module: RailsErrorDashboard::Integrations::Tracer
- Defined in:
- lib/rails_error_dashboard/integrations/tracer.rb
Overview
OpenTelemetry tracer façade for the outbound direction — emits spans from the gem’s capture path so host operators can audit error tracking latency from their existing Datadog/Honeycomb/Jaeger pipeline.
Symmetric counterpart to LlmSpanProcessor (which is INBOUND — pulls OTel spans INTO RED breadcrumbs). This module pushes OUTBOUND: gem operations OUT to the host’s tracer provider.
Designed to be called from hot paths unconditionally. When OTel is absent or the feature is off, ‘in_span` runs the block with a no-op span object — call sites do NOT branch on availability.
HOST APP SAFETY (HOST_APP_SAFETY.md):
-
No-op when ‘enable_otel_export = false` OR OTel API not loaded
-
Per-span-kind opt-in/out via config.otel_spans
-
Tracer instance memoized per-process (rebuild on ‘reset!`)
-
Every public method hard-rescues — never raises into host code
-
Block return value is preserved even when tracer errors
-
Exceptions raised by the block re-raise after being recorded
Configuration:
config.enable_otel_export = true # master switch (default false)
config.otel_service_name = "my-app" # falls back to application_name
config.otel_spans = [:capture, :breadcrumbs, :health, :notifications]
Usage from capture-path code:
Tracer.in_span("capture_error", kind: :capture,
attributes: { error_type: exception.class.name }) do |span|
# ... do the work ...
span&.set_attribute("rails_error_dashboard.error_id", error.id)
end
The span object yielded may be the real OTel span or a NoopSpan. Always use safe-nav (‘span&.`) or guard with `span.respond_to?(:…)`.
Defined Under Namespace
Classes: NoopSpan
Constant Summary collapse
- INSTRUMENTATION_NAME =
"rails_error_dashboard"- ALL_SPAN_KINDS =
%i[capture breadcrumbs health notifications].freeze
- NOOP_SPAN =
NoopSpan.new.freeze
Class Method Summary collapse
-
.emit?(kind) ⇒ Boolean
Returns true when the OTel API is loaded AND the master switch is on AND the given span kind is in the enabled set.
-
.in_span(name, kind: :capture, attributes: {}) {|span| ... } ⇒ Object
Yields a span object to the block.
-
.otel_api_loaded? ⇒ Boolean
Returns true if the OTel API gem is loaded (NOT the SDK).
-
.reset! ⇒ Object
Reset memoized tracer + availability — for spec isolation only.
Class Method Details
.emit?(kind) ⇒ Boolean
Returns true when the OTel API is loaded AND the master switch is on AND the given span kind is in the enabled set. Cheap — called on every in_span invocation, including in the hot path.
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rails_error_dashboard/integrations/tracer.rb', line 97 def emit?(kind) config = RailsErrorDashboard.configuration return false unless config.enable_otel_export return false unless otel_api_loaded? enabled_kinds = config.otel_spans return false if enabled_kinds.nil? || enabled_kinds.empty? enabled_kinds.include?(kind) rescue StandardError false end |
.in_span(name, kind: :capture, attributes: {}) {|span| ... } ⇒ Object
Yields a span object to the block. Returns the block’s return value. Records exceptions raised by the block as span events and re-raises.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/rails_error_dashboard/integrations/tracer.rb', line 67 def in_span(name, kind: :capture, attributes: {}) return yield(NOOP_SPAN) unless emit?(kind) tr = tracer return yield(NOOP_SPAN) unless tr full_name = "#{INSTRUMENTATION_NAME}.#{name}" merged = base_attributes.merge(safe_stringify(attributes)) tr.in_span(full_name, attributes: merged) do |span| begin yield span rescue StandardError => e record_block_exception(span, e) raise end end rescue StandardError => e # Tracer internals failed (e.g. OTel SDK threw on add_span). Fall back # to running the block with a no-op so the host app never sees a crash # caused by the tracer. Logger.debug("[RailsErrorDashboard] Tracer.in_span(#{name.inspect}) failed: #{e.class}: #{e.}") yield NOOP_SPAN end |
.otel_api_loaded? ⇒ Boolean
Returns true if the OTel API gem is loaded (NOT the SDK). The API alone is enough — it ships a ProxyTracerProvider that’s a no-op when no SDK is configured, which is the behavior we want.
119 120 121 122 123 124 125 |
# File 'lib/rails_error_dashboard/integrations/tracer.rb', line 119 def otel_api_loaded? return @otel_api_loaded unless @otel_api_loaded.nil? @otel_api_loaded = !!(defined?(::OpenTelemetry) && ::OpenTelemetry.respond_to?(:tracer_provider)) rescue StandardError @otel_api_loaded = false end |
.reset! ⇒ Object
Reset memoized tracer + availability — for spec isolation only.
110 111 112 113 |
# File 'lib/rails_error_dashboard/integrations/tracer.rb', line 110 def reset! @tracer = nil @otel_api_loaded = nil end |