Module: Tracelit::Instrumentation

Defined in:
lib/tracelit/instrumentation.rb

Class Method Summary collapse

Class Method Details

.detect_frameworkObject

Detects the web framework in use for the telemetry.sdk.name attribute. This value appears as the ‘framework` column in the services table.



83
84
85
86
87
88
# File 'lib/tracelit/instrumentation.rb', line 83

def self.detect_framework
  return "rails"   if defined?(::Rails)
  return "sinatra" if defined?(::Sinatra)
  return "rack"    if defined?(::Rack)
  "ruby"
end

.error_always_on_sampler(rate) ⇒ Object

Returns an ErrorAlwaysOnSampler wrapped in ParentBased so child spans honour the parent’s sampling decision. ErrorAlwaysOnSampler upgrades DROP → RECORD_ONLY so that ErrorSpanProcessor.on_finish fires for all spans, allowing error spans to be exported even outside the sampling ratio.



94
95
96
97
98
# File 'lib/tracelit/instrumentation.rb', line 94

def self.error_always_on_sampler(rate)
  OpenTelemetry::SDK::Trace::Samplers.parent_based(
    root: Tracelit::ErrorAlwaysOnSampler.new(rate)
  )
end

.reset!Object



75
76
77
# File 'lib/tracelit/instrumentation.rb', line 75

def self.reset!
  @configured = false
end

.setup(config) ⇒ Object

Sets up the OpenTelemetry SDK with the Tracelit OTLP exporter. Called once at application boot. Idempotent — safe to call multiple times.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/tracelit/instrumentation.rb', line 17

def self.setup(config)
  return if @configured
  return unless config.enabled

  config.validate!

  OpenTelemetry::SDK.configure do |otel|
    # Resource attributes identify this service in Tracelit.
    # These populate the `resource` Map column on every telemetry row.
    otel.resource = OpenTelemetry::SDK::Resources::Resource.create(
      {
        OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME    => config.resolved_service_name,
        OpenTelemetry::SemanticConventions::Resource::DEPLOYMENT_ENVIRONMENT => config.environment,
        "telemetry.sdk.language" => "ruby",
        "telemetry.sdk.name"     => detect_framework,
        "telemetry.sdk.version"  => Tracelit::VERSION,
      }.merge(config.resource_attributes)
    )

    # Build the OTLP exporter once — shared by both processors
    exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
      endpoint: "#{config.endpoint}/v1/traces",
      headers: {
        "Authorization"  => "Bearer #{config.api_key}",
        "X-Service-Name" => config.resolved_service_name,
        "X-Environment"  => config.environment,
      }
    )

    # Primary processor: batches and exports sampled spans
    otel.add_span_processor(
      OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)
    )

    # Error processor: always exports error spans regardless of
    # sampling decision — fires on_finish after status is known
    otel.add_span_processor(
      Tracelit::ErrorSpanProcessor.new(exporter)
    )

    # Auto-instrumentation: instruments Rails, Rack, ActiveRecord,
    # Action View, Net::HTTP, Faraday, Redis, Sidekiq, and more.
    # use_all() enables every installed instrumentation gem.
    otel.use_all
  end

  # Set sampler after configure — Configurator does not expose
  # sampler= in OTel SDK 1.x, must be set on the provider directly.
  # Skip at 1.0: the default AlwaysOn sampler is correct and we do not touch it.
  if config.sample_rate < 1.0
    OpenTelemetry.tracer_provider.sampler = error_always_on_sampler(config.sample_rate)
  end

  @configured = true
  setup_logs(config)
  Tracelit::Metrics.setup(config)
end

.setup_logs(config) ⇒ Object

Sets up the OTel Logs SDK: creates a LoggerProvider, attaches a BatchLogRecordProcessor with an OTLP/HTTP exporter, registers it globally, and installs the Rails.logger bridge.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/tracelit/instrumentation.rb', line 103

def self.setup_logs(config)
  logs_exporter = OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new(
    endpoint: "#{config.endpoint}/v1/logs",
    headers: {
      "Authorization"  => "Bearer #{config.api_key}",
      "X-Service-Name" => config.resolved_service_name,
      "X-Environment"  => config.environment,
    }
  )

  logger_provider = OpenTelemetry::SDK::Logs::LoggerProvider.new(
    resource: OpenTelemetry.tracer_provider.resource
  )

  logger_provider.add_log_record_processor(
    OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(logs_exporter)
  )

  OpenTelemetry.logger_provider = logger_provider

  # Install the Rails.logger → OTel bridge after the provider is ready.
  # Called here (after Rails boot) so Rails.logger is already initialised.
  RailsLoggerBridge.install(logger_provider)
rescue StandardError => e
  OpenTelemetry.logger.warn("Tracelit: failed to set up logs: #{e.message}")
end