tracelit 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 58eb691b0dfacebc7072a0ee10ebc4fb88ddc5074c659211a689532bf2e86f3b
4
+ data.tar.gz: 9a73dad4b506173c24f6e64a17c227812a0ab90651ef3a08e493ab9a68c602e1
5
+ SHA512:
6
+ metadata.gz: 7ca34cc0206da44b31ceb865848ec08ef39f015764ae7f1f20dcf6d2b9af78820de537f6994ea48a224415350878ea9c206a54f21dd434cb37146451a04a06ba
7
+ data.tar.gz: 999c495a4c363af34e4bdd1671d66611c42124040f2e20172040590dbb5e6a715f9123e4550a29c2378c77d5275cede91fdc8dd46ca1830c1eabfa2b6b92eac3
data/README.md ADDED
@@ -0,0 +1,361 @@
1
+ # Tracelit Ruby SDK
2
+
3
+ Official Ruby SDK for [Tracelit](https://tracelit.io) — drop-in OpenTelemetry instrumentation for Rails, Sinatra, and Rack apps. Sends traces, metrics, and logs to the Tracelit ingest API via OTLP/HTTP.
4
+
5
+ **Requirements:** Ruby >= 3.0
6
+
7
+ ---
8
+
9
+ ## Set up with AI
10
+
11
+ Copy the prompt below and paste it into Cursor, Claude, ChatGPT, or any AI assistant. It contains everything the AI needs to fully integrate Tracelit into your app — gem, initializer, manual spans, custom metrics, and test guard.
12
+
13
+ ````
14
+ You are integrating the Tracelit Ruby SDK into this codebase.
15
+
16
+ ## What Tracelit does
17
+ Tracelit is a drop-in OpenTelemetry SDK that sends traces, metrics, and logs
18
+ to https://tracelit.io via OTLP/HTTP. It auto-instruments Rails, Sidekiq,
19
+ ActiveRecord, Redis, Faraday, Net::HTTP, and more with zero code changes.
20
+
21
+ ## Steps
22
+
23
+ ### 1. Add the gem
24
+ In Gemfile add:
25
+ gem "tracelit"
26
+ Then run: bundle install
27
+
28
+ ### 2. Create the initializer
29
+ Create config/initializers/tracelit.rb with this content
30
+ (replace MY_SERVICE_NAME with the actual name of this service):
31
+
32
+ Tracelit.configure do |config|
33
+ config.api_key = ENV["TRACELIT_API_KEY"]
34
+ config.service_name = "MY_SERVICE_NAME"
35
+ config.environment = ENV.fetch("RAILS_ENV", "production")
36
+ config.sample_rate = 1.0
37
+ config.enabled = ENV["TRACELIT_ENABLED"] != "false"
38
+ end
39
+
40
+ For Sinatra/Rack apps also add Tracelit.start! after the configure block.
41
+ For Rails, the Railtie calls start! automatically — no extra line needed.
42
+
43
+ ### 3. Disable in tests
44
+ In config/environments/test.rb (or .env.test) add:
45
+ TRACELIT_ENABLED=false
46
+
47
+ ### 4. Add manual spans (optional)
48
+ Wrap any important operation with a custom span:
49
+
50
+ Tracelit.tracer.in_span("describe_the_operation") do |span|
51
+ span.set_attribute("relevant.attribute", value.to_s)
52
+ do_the_work
53
+ end
54
+
55
+ ### 5. Add custom metrics (optional)
56
+ Use counters, histograms, or gauges for business-level signals:
57
+
58
+ # Counter — increment by 1 each time an event occurs
59
+ counter = Tracelit.metrics.counter("my.event.count",
60
+ description: "...", unit: "{events}")
61
+ counter.add(1, attributes: { "key" => "value" })
62
+
63
+ # Histogram — record a measured value (latency, size, etc.)
64
+ histogram = Tracelit.metrics.histogram("my.operation.duration",
65
+ description: "...", unit: "ms")
66
+ histogram.record(elapsed_ms, attributes: { "key" => "value" })
67
+
68
+ # Gauge — record a current level (queue depth, pool size, etc.)
69
+ gauge = Tracelit.metrics.gauge("my.queue.depth",
70
+ description: "...", unit: "{items}")
71
+ gauge.record(current_depth, attributes: { "queue" => "default" })
72
+
73
+ ## What you get automatically (no code required)
74
+ - Traces for every HTTP request, SQL query, Redis call, Sidekiq job, etc.
75
+ - Metrics: http.server.request.count/duration, http.server.error.count,
76
+ db.query.duration, sidekiq.job.count/duration/error.count,
77
+ db.connection_pool.size/busy/idle/waiting, process.memory.rss
78
+ - Rails.logger lines forwarded to Tracelit logs, correlated to the active trace
79
+ - Error spans always exported even when the trace is outside the sample ratio
80
+
81
+ ## Configuration reference
82
+ | Option | Env var | Default |
83
+ |--------------------|--------------------------|--------------------------------|
84
+ | api_key | TRACELIT_API_KEY | nil (required) |
85
+ | service_name | TRACELIT_SERVICE_NAME | Rails app name (required) |
86
+ | environment | TRACELIT_ENVIRONMENT | "production" |
87
+ | endpoint | TRACELIT_ENDPOINT | https://ingest.tracelit.app |
88
+ | sample_rate | TRACELIT_SAMPLE_RATE | 1.0 |
89
+ | enabled | TRACELIT_ENABLED | true |
90
+ | resource_attributes| — | {} (extra span/metric tags) |
91
+ ````
92
+
93
+ ---
94
+
95
+ ## Installation
96
+
97
+ Add to your `Gemfile` and run `bundle install`:
98
+
99
+ ```ruby
100
+ gem "tracelit"
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Setup
106
+
107
+ ### Rails
108
+
109
+ Create `config/initializers/tracelit.rb`:
110
+
111
+ ```ruby
112
+ Tracelit.configure do |config|
113
+ config.api_key = ENV["TRACELIT_API_KEY"] # required
114
+ config.service_name = "payments-api" # required
115
+ config.environment = ENV["RAILS_ENV"]
116
+ config.sample_rate = 1.0
117
+ end
118
+ ```
119
+
120
+ That is all. The Railtie picks up the configuration automatically and calls `Tracelit.start!` at boot — no further changes needed.
121
+
122
+ ### Sinatra / Rack
123
+
124
+ ```ruby
125
+ require "tracelit"
126
+
127
+ Tracelit.configure do |config|
128
+ config.api_key = ENV["TRACELIT_API_KEY"]
129
+ config.service_name = "my-sinatra-app"
130
+ config.environment = ENV["RACK_ENV"]
131
+ end
132
+
133
+ Tracelit.start! # must be called explicitly outside Rails
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Configuration reference
139
+
140
+ All options can be set in the `configure` block or via environment variables.
141
+
142
+ | Option | Env variable | Default | Description |
143
+ |---|---|---|---|
144
+ | `api_key` | `TRACELIT_API_KEY` | `nil` | **Required.** Your Tracelit ingest API key. |
145
+ | `service_name` | `TRACELIT_SERVICE_NAME` | Rails app name | **Required.** Name of this service as it appears in Tracelit. Falls back to the Rails application module name when inside Rails, or `"unknown-service"` otherwise. |
146
+ | `environment` | `TRACELIT_ENVIRONMENT` | `"production"` | Deployment environment tag — e.g. `production`, `staging`, `development`. |
147
+ | `endpoint` | `TRACELIT_ENDPOINT` | `https://ingest.tracelit.app` | Base URL of the Tracelit ingest API. Override only when self-hosting. |
148
+ | `sample_rate` | `TRACELIT_SAMPLE_RATE` | `1.0` | Head-based trace sampling ratio between `0.0` and `1.0`. `1.0` keeps every trace; `0.1` keeps 10%. **Errors are always exported regardless of this setting.** |
149
+ | `enabled` | `TRACELIT_ENABLED` | `true` | Set to `false` (or `TRACELIT_ENABLED=false`) to disable all telemetry without removing the gem — useful in test environments. |
150
+ | `resource_attributes` | — | `{}` | Extra key/value pairs appended to every span, metric, and log record as resource attributes. |
151
+
152
+ ### Adding custom resource attributes
153
+
154
+ ```ruby
155
+ Tracelit.configure do |config|
156
+ config.api_key = ENV["TRACELIT_API_KEY"]
157
+ config.service_name = "orders-api"
158
+ config.resource_attributes = {
159
+ "deployment.region" => "us-east-1",
160
+ "team" => "platform",
161
+ }
162
+ end
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Manual trace instrumentation
168
+
169
+ Use `Tracelit.tracer` to create custom spans around any block of work:
170
+
171
+ ```ruby
172
+ Tracelit.tracer.in_span("process_payment") do |span|
173
+ span.set_attribute("payment.id", payment.id.to_s)
174
+ span.set_attribute("payment.amount", amount)
175
+ span.set_attribute("payment.currency", currency)
176
+
177
+ result = process(payment)
178
+
179
+ span.set_attribute("payment.status", result.status)
180
+ result
181
+ end
182
+ ```
183
+
184
+ The tracer is an `OpenTelemetry::Trace::Tracer` and supports the full [OpenTelemetry Ruby API](https://opentelemetry.io/docs/languages/ruby/api/).
185
+
186
+ ---
187
+
188
+ ## Manual metrics instrumentation
189
+
190
+ Access the metrics interface via `Tracelit.metrics` (an alias for `Tracelit::Metrics`):
191
+
192
+ ### Counter
193
+
194
+ Counts discrete events. Use for request counts, job completions, errors, etc.
195
+
196
+ ```ruby
197
+ counter = Tracelit.metrics.counter(
198
+ "orders.placed",
199
+ description: "Total orders placed",
200
+ unit: "{orders}"
201
+ )
202
+
203
+ counter.add(1, attributes: { "currency" => "USD", "channel" => "web" })
204
+ ```
205
+
206
+ ### Histogram
207
+
208
+ Records distributions of values. Use for durations, payload sizes, queue depths, etc.
209
+
210
+ ```ruby
211
+ histogram = Tracelit.metrics.histogram(
212
+ "external.api.duration",
213
+ description: "External API call duration",
214
+ unit: "ms"
215
+ )
216
+
217
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
218
+ call_external_api
219
+ elapsed_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0
220
+
221
+ histogram.record(elapsed_ms, attributes: { "service" => "stripe" })
222
+ ```
223
+
224
+ ### Gauge
225
+
226
+ Records a point-in-time value. Use for pool sizes, queue lengths, cache hit rates, etc.
227
+
228
+ ```ruby
229
+ gauge = Tracelit.metrics.gauge(
230
+ "job_queue.depth",
231
+ description: "Number of pending background jobs",
232
+ unit: "{jobs}"
233
+ )
234
+
235
+ gauge.record(JobQueue.pending_count, attributes: { "queue" => "default" })
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Automatic instrumentation
241
+
242
+ The SDK enables every instrumentation gem bundled in `opentelemetry-instrumentation-all`, including:
243
+
244
+ | Library | What is captured |
245
+ |---|---|
246
+ | Rails / Action Pack | HTTP request traces, controller and action attributes |
247
+ | Active Record | SQL query traces with sanitised statement text |
248
+ | Action View | Template render times |
249
+ | Rack | Low-level HTTP middleware spans |
250
+ | Net::HTTP | Outbound HTTP call traces |
251
+ | Faraday | Outbound HTTP call traces |
252
+ | Redis | Cache command traces |
253
+ | Sidekiq | Job enqueue and execute traces |
254
+ | Bunny | AMQP publish/subscribe traces |
255
+ | gRPC | Client and server RPC traces |
256
+
257
+ Additional libraries (Mongo, pg, mysql2, Kafka, etc.) are also instrumented when their gems are present.
258
+
259
+ ---
260
+
261
+ ## Automatic metrics collection
262
+
263
+ Once `Tracelit.start!` has been called, the following metrics are collected with no additional configuration:
264
+
265
+ ### HTTP server metrics (Rails)
266
+
267
+ Emitted per request via `ActiveSupport::Notifications`:
268
+
269
+ | Metric | Type | Description |
270
+ |---|---|---|
271
+ | `http.server.request.count` | Counter | Total HTTP requests processed |
272
+ | `http.server.request.duration` | Histogram | Request duration in milliseconds |
273
+ | `http.server.error.count` | Counter | Total 5xx responses |
274
+ | `db.query.duration` | Histogram | ActiveRecord time per request in milliseconds |
275
+
276
+ Attributes on all HTTP metrics: `http.method`, `http.route`, `http.status_code`, `controller`, `action`.
277
+
278
+ ### Sidekiq job metrics
279
+
280
+ Emitted per job execution via server middleware:
281
+
282
+ | Metric | Type | Description |
283
+ |---|---|---|
284
+ | `sidekiq.job.count` | Counter | Total jobs processed |
285
+ | `sidekiq.job.duration` | Histogram | Job execution duration in milliseconds |
286
+ | `sidekiq.job.error.count` | Counter | Total jobs that raised an error |
287
+
288
+ Attributes: `sidekiq.job.class`, `sidekiq.queue`, `sidekiq.status` (`success` or `error`).
289
+
290
+ ### Database connection pool metrics (ActiveRecord)
291
+
292
+ Polled every 30 seconds on a background thread:
293
+
294
+ | Metric | Type | Description |
295
+ |---|---|---|
296
+ | `db.connection_pool.size` | Gauge | Maximum connections in the pool |
297
+ | `db.connection_pool.busy` | Gauge | Connections currently checked out |
298
+ | `db.connection_pool.idle` | Gauge | Connections available for checkout |
299
+ | `db.connection_pool.waiting` | Gauge | Threads waiting for a connection |
300
+
301
+ ### Process memory
302
+
303
+ Polled every 60 seconds:
304
+
305
+ | Metric | Type | Description |
306
+ |---|---|---|
307
+ | `process.memory.rss` | Gauge | Process RSS memory in megabytes |
308
+
309
+ ---
310
+
311
+ ## Log forwarding (Rails)
312
+
313
+ When Rails is present, `Tracelit.start!` installs a broadcast target on `Rails.logger`. Every `Rails.logger` call is forwarded to the OTel LoggerProvider and exported to the Tracelit logs table via OTLP. The original logger output is preserved — nothing changes for your existing log pipeline.
314
+
315
+ Log records are automatically correlated with the active trace via `trace_id` and `span_id`.
316
+
317
+ ---
318
+
319
+ ## Sampling and error guarantee
320
+
321
+ Set `config.sample_rate` below `1.0` to reduce trace volume in high-traffic environments:
322
+
323
+ ```ruby
324
+ config.sample_rate = 0.1 # keep 10% of traces
325
+ ```
326
+
327
+ **Error spans are always exported**, even when the parent trace is outside the sample ratio. The SDK uses a custom `ErrorAlwaysOnSampler` + `ErrorSpanProcessor` pair to guarantee this — no configuration required.
328
+
329
+ ---
330
+
331
+ ## Disabling in tests
332
+
333
+ ```ruby
334
+ # config/initializers/tracelit.rb
335
+ Tracelit.configure do |config|
336
+ config.api_key = ENV["TRACELIT_API_KEY"]
337
+ config.service_name = "my-app"
338
+ config.enabled = ENV["TRACELIT_ENABLED"] != "false"
339
+ end
340
+ ```
341
+
342
+ Then in your test environment:
343
+
344
+ ```bash
345
+ TRACELIT_ENABLED=false bundle exec rspec
346
+ ```
347
+
348
+ Or set it permanently in `config/environments/test.rb` / `.env.test`:
349
+
350
+ ```
351
+ TRACELIT_ENABLED=false
352
+ ```
353
+
354
+ ---
355
+
356
+ ## Running the SDK's own tests
357
+
358
+ ```bash
359
+ bundle install
360
+ bundle exec rspec
361
+ ```
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracelit
4
+ class Configuration
5
+ # Required
6
+ attr_accessor :api_key
7
+
8
+ # The name of this service as it will appear in Tracelit.
9
+ # Defaults to the Rails app name if Rails is present.
10
+ attr_accessor :service_name
11
+
12
+ # Environment tag — production, staging, development, etc.
13
+ attr_accessor :environment
14
+
15
+ # Full URL of the Tracelit ingest endpoint.
16
+ # Override only if self-hosting.
17
+ attr_accessor :endpoint
18
+
19
+ # Head-based sampling rate (0.0–1.0). Default: 1.0 (keep all traces).
20
+ # Set to 0.1 to keep 10% of traces. Errors are always kept regardless.
21
+ attr_accessor :sample_rate
22
+
23
+ # Set false to disable all telemetry without removing the gem.
24
+ # Useful for test environments.
25
+ attr_accessor :enabled
26
+
27
+ # Additional resource attributes appended to every span and log.
28
+ # Hash of string keys and string values.
29
+ attr_accessor :resource_attributes
30
+
31
+ def initialize
32
+ @api_key = ENV["TRACELIT_API_KEY"]
33
+ @service_name = ENV["TRACELIT_SERVICE_NAME"]
34
+ @environment = ENV["TRACELIT_ENVIRONMENT"] || "production"
35
+ @endpoint = ENV["TRACELIT_ENDPOINT"] || "https://ingest.tracelit.app"
36
+ @sample_rate = (ENV["TRACELIT_SAMPLE_RATE"] || "1.0").to_f
37
+ @enabled = ENV["TRACELIT_ENABLED"] != "false"
38
+ @resource_attributes = {}
39
+ end
40
+
41
+ def validate!
42
+ raise ArgumentError, "Tracelit.config.api_key is required" if api_key.nil? || api_key.empty?
43
+ raise ArgumentError, "Tracelit.config.service_name is required" if service_name.nil? || service_name.empty?
44
+ raise ArgumentError, "sample_rate must be between 0.0 and 1.0" unless sample_rate.between?(0.0, 1.0)
45
+ end
46
+
47
+ # Infer service name from Rails application if not explicitly set.
48
+ def resolved_service_name
49
+ return service_name if service_name && !service_name.empty?
50
+ return ::Rails.application.class.module_parent_name.underscore if defined?(::Rails)
51
+ "unknown-service"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracelit
4
+ # ErrorAlwaysOnSampler wraps a ratio-based sampler but upgrades DROP
5
+ # decisions to RECORD_ONLY. This ensures span processors (including
6
+ # ErrorSpanProcessor) fire on_end for ALL spans, even those outside
7
+ # the sampling ratio.
8
+ #
9
+ # Without this, TraceIdRatioBased(0.0) returns DROP, which causes the
10
+ # SDK to create NonRecordingSpans that bypass the processor pipeline
11
+ # entirely — so ErrorSpanProcessor.on_end is never called.
12
+ #
13
+ # With RECORD_ONLY:
14
+ # - Real spans are created and all processors fire
15
+ # - BatchSpanProcessor ignores them (checks trace_flags.sampled? == false)
16
+ # - ErrorSpanProcessor sees them and exports any that end in ERROR
17
+ class ErrorAlwaysOnSampler
18
+ Decision = OpenTelemetry::SDK::Trace::Samplers::Decision
19
+ Result = OpenTelemetry::SDK::Trace::Samplers::Result
20
+
21
+ def initialize(rate)
22
+ @inner = OpenTelemetry::SDK::Trace::Samplers.trace_id_ratio_based(rate)
23
+ end
24
+
25
+ def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
26
+ result = @inner.should_sample?(
27
+ trace_id: trace_id,
28
+ parent_context: parent_context,
29
+ links: links,
30
+ name: name,
31
+ kind: kind,
32
+ attributes: attributes
33
+ )
34
+
35
+ if result.recording?
36
+ result
37
+ else
38
+ # Upgrade DROP → RECORD_ONLY so processor pipeline fires
39
+ Result.new(decision: Decision::RECORD_ONLY, tracestate: result.tracestate)
40
+ end
41
+ end
42
+
43
+ def description
44
+ "ErrorAlwaysOnSampler{#{@inner.description}}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracelit
4
+ # ErrorSpanProcessor ensures error spans are always exported
5
+ # regardless of the sampling decision made at span creation time.
6
+ #
7
+ # How it works:
8
+ # - ErrorAlwaysOnSampler returns RECORD_ONLY (not DROP) for unsampled spans,
9
+ # which ensures this processor's on_finish is called for every span
10
+ # - On span finish, if the span has status ERROR, this processor forces it
11
+ # through the exporter directly, bypassing the BatchSpanProcessor
12
+ # - BatchSpanProcessor ignores RECORD_ONLY spans (trace_flags.sampled? false)
13
+ # so there is no double-export for sampled error spans
14
+ #
15
+ # NOTE: opentelemetry-sdk 1.x uses on_finish (not on_end) as the hook name.
16
+ class ErrorSpanProcessor
17
+ def initialize(exporter)
18
+ @exporter = exporter
19
+ end
20
+
21
+ def on_start(span, parent_context)
22
+ # nothing to do at start
23
+ end
24
+
25
+ def on_finish(span)
26
+ # Skip spans that are not in error — only intervene for errors
27
+ return if span.status.ok?
28
+
29
+ # Skip spans that were fully sampled — BatchSpanProcessor handles those.
30
+ # This prevents double-export of error spans on traces that were sampled.
31
+ return if span.context.trace_flags.sampled?
32
+
33
+ # Force-export this error span regardless of sampling decision
34
+ @exporter.export([span.to_span_data])
35
+ rescue StandardError
36
+ # Never let processor errors propagate to the application
37
+ end
38
+
39
+ def force_flush(timeout: nil)
40
+ @exporter.force_flush(timeout: timeout)
41
+ end
42
+
43
+ def shutdown(timeout: nil)
44
+ # Do not shut down the shared exporter here —
45
+ # the BatchSpanProcessor owns its lifecycle
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opentelemetry/sdk"
4
+ require "opentelemetry/exporter/otlp"
5
+ require "opentelemetry/instrumentation/all"
6
+ require "opentelemetry-logs-sdk"
7
+ require "opentelemetry/exporter/otlp_logs"
8
+ require_relative "error_span_processor"
9
+ require_relative "error_always_on_sampler"
10
+ require_relative "rails_logger_bridge"
11
+ require_relative "metrics"
12
+
13
+ module Tracelit
14
+ module Instrumentation
15
+ # Sets up the OpenTelemetry SDK with the Tracelit OTLP exporter.
16
+ # Called once at application boot. Idempotent — safe to call multiple times.
17
+ def self.setup(config)
18
+ return if @configured
19
+ return unless config.enabled
20
+
21
+ config.validate!
22
+
23
+ OpenTelemetry::SDK.configure do |otel|
24
+ # Resource attributes identify this service in Tracelit.
25
+ # These populate the `resource` Map column on every telemetry row.
26
+ otel.resource = OpenTelemetry::SDK::Resources::Resource.create(
27
+ {
28
+ OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => config.resolved_service_name,
29
+ OpenTelemetry::SemanticConventions::Resource::DEPLOYMENT_ENVIRONMENT => config.environment,
30
+ "telemetry.sdk.language" => "ruby",
31
+ "telemetry.sdk.name" => detect_framework,
32
+ "telemetry.sdk.version" => Tracelit::VERSION,
33
+ }.merge(config.resource_attributes)
34
+ )
35
+
36
+ # Build the OTLP exporter once — shared by both processors
37
+ exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
38
+ endpoint: "#{config.endpoint}/v1/traces",
39
+ headers: {
40
+ "Authorization" => "Bearer #{config.api_key}",
41
+ "X-Service-Name" => config.resolved_service_name,
42
+ "X-Environment" => config.environment,
43
+ }
44
+ )
45
+
46
+ # Primary processor: batches and exports sampled spans
47
+ otel.add_span_processor(
48
+ OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)
49
+ )
50
+
51
+ # Error processor: always exports error spans regardless of
52
+ # sampling decision — fires on_finish after status is known
53
+ otel.add_span_processor(
54
+ Tracelit::ErrorSpanProcessor.new(exporter)
55
+ )
56
+
57
+ # Auto-instrumentation: instruments Rails, Rack, ActiveRecord,
58
+ # Action View, Net::HTTP, Faraday, Redis, Sidekiq, and more.
59
+ # use_all() enables every installed instrumentation gem.
60
+ otel.use_all
61
+ end
62
+
63
+ # Set sampler after configure — Configurator does not expose
64
+ # sampler= in OTel SDK 1.x, must be set on the provider directly.
65
+ # Skip at 1.0: the default AlwaysOn sampler is correct and we do not touch it.
66
+ if config.sample_rate < 1.0
67
+ OpenTelemetry.tracer_provider.sampler = error_always_on_sampler(config.sample_rate)
68
+ end
69
+
70
+ @configured = true
71
+ setup_logs(config)
72
+ Tracelit::Metrics.setup(config)
73
+ end
74
+
75
+ def self.reset!
76
+ @configured = false
77
+ end
78
+
79
+ private
80
+
81
+ # Detects the web framework in use for the telemetry.sdk.name attribute.
82
+ # This value appears as the `framework` column in the services table.
83
+ def self.detect_framework
84
+ return "rails" if defined?(::Rails)
85
+ return "sinatra" if defined?(::Sinatra)
86
+ return "rack" if defined?(::Rack)
87
+ "ruby"
88
+ end
89
+
90
+ # Returns an ErrorAlwaysOnSampler wrapped in ParentBased so child spans
91
+ # honour the parent's sampling decision. ErrorAlwaysOnSampler upgrades
92
+ # DROP → RECORD_ONLY so that ErrorSpanProcessor.on_finish fires for all spans,
93
+ # allowing error spans to be exported even outside the sampling ratio.
94
+ def self.error_always_on_sampler(rate)
95
+ OpenTelemetry::SDK::Trace::Samplers.parent_based(
96
+ root: Tracelit::ErrorAlwaysOnSampler.new(rate)
97
+ )
98
+ end
99
+
100
+ # Sets up the OTel Logs SDK: creates a LoggerProvider, attaches a
101
+ # BatchLogRecordProcessor with an OTLP/HTTP exporter, registers it
102
+ # globally, and installs the Rails.logger bridge.
103
+ def self.setup_logs(config)
104
+ logs_exporter = OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new(
105
+ endpoint: "#{config.endpoint}/v1/logs",
106
+ headers: {
107
+ "Authorization" => "Bearer #{config.api_key}",
108
+ "X-Service-Name" => config.resolved_service_name,
109
+ "X-Environment" => config.environment,
110
+ }
111
+ )
112
+
113
+ logger_provider = OpenTelemetry::SDK::Logs::LoggerProvider.new(
114
+ resource: OpenTelemetry.tracer_provider.resource
115
+ )
116
+
117
+ logger_provider.add_log_record_processor(
118
+ OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(logs_exporter)
119
+ )
120
+
121
+ OpenTelemetry.logger_provider = logger_provider
122
+
123
+ # Install the Rails.logger → OTel bridge after the provider is ready.
124
+ # Called here (after Rails boot) so Rails.logger is already initialised.
125
+ RailsLoggerBridge.install(logger_provider)
126
+ rescue StandardError => e
127
+ OpenTelemetry.logger.warn("Tracelit: failed to set up logs: #{e.message}")
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opentelemetry/metrics"
4
+ require "opentelemetry-metrics-sdk"
5
+ require "opentelemetry/exporter/otlp_metrics"
6
+
7
+ module Tracelit
8
+ module Metrics
9
+ # Sets up the OpenTelemetry MeterProvider with OTLP exporter.
10
+ # Called once from Instrumentation.setup after trace setup.
11
+ def self.setup(config)
12
+ # Force delta temporality for all instruments. The SDK aggregation classes
13
+ # (Sum, ExplicitBucketHistogram) read this env var at construction time;
14
+ # there is no constructor keyword on MetricsExporter for this in v0.8.0.
15
+ ENV["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"] = "delta"
16
+
17
+ exporter = OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new(
18
+ endpoint: "#{config.endpoint}/v1/metrics",
19
+ headers: {
20
+ "Authorization" => "Bearer #{config.api_key}",
21
+ "X-Service-Name" => config.resolved_service_name,
22
+ "X-Environment" => config.environment,
23
+ }
24
+ )
25
+
26
+ reader = OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
27
+ exporter: exporter,
28
+ export_interval_millis: 60_000,
29
+ export_timeout_millis: 10_000
30
+ )
31
+
32
+ provider = OpenTelemetry::SDK::Metrics::MeterProvider.new(
33
+ resource: OpenTelemetry.tracer_provider.resource
34
+ )
35
+ provider.add_metric_reader(reader)
36
+
37
+ OpenTelemetry.meter_provider = provider
38
+
39
+ @meter = provider.meter(
40
+ config.resolved_service_name,
41
+ version: Tracelit::VERSION
42
+ )
43
+
44
+ install_rails_subscriber if defined?(::Rails)
45
+ install_sidekiq_middleware if defined?(::Sidekiq)
46
+ install_connection_pool_poller if defined?(::ActiveRecord)
47
+ install_memory_poller
48
+ rescue StandardError => e
49
+ OpenTelemetry.logger.warn("Tracelit: failed to set up metrics: #{e.message}")
50
+ end
51
+
52
+ def self.meter
53
+ @meter
54
+ end
55
+
56
+ # Exposes a counter for manual instrumentation in user code:
57
+ # Tracelit::Metrics.counter("orders.placed").add(1)
58
+ def self.counter(name, description: "", unit: "")
59
+ @meter&.create_counter(name,
60
+ description: description,
61
+ unit: unit
62
+ )
63
+ end
64
+
65
+ def self.histogram(name, description: "", unit: "")
66
+ @meter&.create_histogram(name,
67
+ description: description,
68
+ unit: unit
69
+ )
70
+ end
71
+
72
+ def self.gauge(name, description: "", unit: "")
73
+ @meter&.create_gauge(name,
74
+ description: description,
75
+ unit: unit
76
+ )
77
+ end
78
+
79
+ # Subscribes to Rails process_action.action_controller to emit:
80
+ # http.server.request.count — counter per request
81
+ # http.server.request.duration — histogram in milliseconds
82
+ # http.server.error.count — counter for 5xx responses
83
+ # db.query.duration — histogram for ActiveRecord time per request
84
+ def self.install_rails_subscriber
85
+ request_counter = @meter.create_counter(
86
+ "http.server.request.count",
87
+ description: "Total HTTP requests processed",
88
+ unit: "{requests}"
89
+ )
90
+
91
+ duration_histogram = @meter.create_histogram(
92
+ "http.server.request.duration",
93
+ description: "HTTP request duration",
94
+ unit: "ms"
95
+ )
96
+
97
+ error_counter = @meter.create_counter(
98
+ "http.server.error.count",
99
+ description: "Total HTTP 5xx responses",
100
+ unit: "{errors}"
101
+ )
102
+
103
+ db_duration_histogram = @meter.create_histogram(
104
+ "db.query.duration",
105
+ description: "Database query duration",
106
+ unit: "ms"
107
+ )
108
+
109
+ ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
110
+ event = ActiveSupport::Notifications::Event.new(*args)
111
+ payload = event.payload
112
+
113
+ attrs = {
114
+ "http.method" => payload[:method].to_s,
115
+ "http.route" => payload[:path].to_s,
116
+ "http.status_code" => payload[:status].to_s,
117
+ "controller" => payload[:controller].to_s,
118
+ "action" => payload[:action].to_s,
119
+ }
120
+
121
+ request_counter.add(1, attributes: attrs)
122
+ duration_histogram.record(event.duration, attributes: attrs)
123
+
124
+ error_counter.add(1, attributes: attrs) if payload[:status].to_i >= 500
125
+
126
+ if payload[:db_runtime]
127
+ db_duration_histogram.record(
128
+ payload[:db_runtime].to_f,
129
+ attributes: { "controller" => payload[:controller].to_s }
130
+ )
131
+ end
132
+ rescue StandardError
133
+ # Never let metric errors surface to the application
134
+ end
135
+ end
136
+
137
+ # Installs a Sidekiq server middleware that emits per-job metrics.
138
+ # Uses a dynamically defined class so the instrument references are
139
+ # captured in the closure without global state.
140
+ def self.install_sidekiq_middleware
141
+ job_counter = @meter.create_counter(
142
+ "sidekiq.job.count",
143
+ description: "Total Sidekiq jobs processed",
144
+ unit: "{jobs}"
145
+ )
146
+
147
+ job_duration = @meter.create_histogram(
148
+ "sidekiq.job.duration",
149
+ description: "Sidekiq job execution duration",
150
+ unit: "ms"
151
+ )
152
+
153
+ job_error_counter = @meter.create_counter(
154
+ "sidekiq.job.error.count",
155
+ description: "Total Sidekiq jobs that raised an error",
156
+ unit: "{jobs}"
157
+ )
158
+
159
+ _job_counter = job_counter
160
+ _job_duration = job_duration
161
+ _job_error_counter = job_error_counter
162
+
163
+ middleware_class = Class.new do
164
+ define_method(:call) do |_worker, msg, queue, &block|
165
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
166
+ error_raised = false
167
+
168
+ begin
169
+ block.call
170
+ rescue StandardError
171
+ error_raised = true
172
+ raise
173
+ ensure
174
+ elapsed_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0
175
+
176
+ attrs = {
177
+ "sidekiq.job.class" => msg["class"].to_s,
178
+ "sidekiq.queue" => queue.to_s,
179
+ "sidekiq.status" => error_raised ? "error" : "success",
180
+ }
181
+
182
+ _job_counter.add(1, attributes: attrs)
183
+ _job_duration.record(elapsed_ms, attributes: attrs)
184
+ _job_error_counter.add(1, attributes: attrs) if error_raised
185
+ end
186
+ end
187
+ end
188
+
189
+ Sidekiq.configure_server do |config|
190
+ config.server_middleware do |chain|
191
+ chain.add middleware_class
192
+ end
193
+ end
194
+ rescue StandardError => e
195
+ warn "Tracelit: failed to install Sidekiq middleware: #{e.message}"
196
+ end
197
+
198
+ # Polls ActiveRecord connection pool stats every 30 seconds on a daemon
199
+ # thread and records them as gauges. Does not require a live connection
200
+ # at install time — errors during polling are silently retried next cycle.
201
+ def self.install_connection_pool_poller
202
+ pool_size = @meter.create_gauge(
203
+ "db.connection_pool.size",
204
+ description: "Maximum connections in the pool",
205
+ unit: "{connections}"
206
+ )
207
+
208
+ pool_busy = @meter.create_gauge(
209
+ "db.connection_pool.busy",
210
+ description: "Connections currently checked out",
211
+ unit: "{connections}"
212
+ )
213
+
214
+ pool_idle = @meter.create_gauge(
215
+ "db.connection_pool.idle",
216
+ description: "Connections available for checkout",
217
+ unit: "{connections}"
218
+ )
219
+
220
+ pool_waiting = @meter.create_gauge(
221
+ "db.connection_pool.waiting",
222
+ description: "Threads waiting for a connection",
223
+ unit: "{threads}"
224
+ )
225
+
226
+ thread = Thread.new do
227
+ Thread.current[:tracelit_pool_poller] = true
228
+ loop do
229
+ sleep 30
230
+ begin
231
+ pool = ActiveRecord::Base.connection_pool
232
+ stat = pool.stat
233
+ attrs = { "db.system" => pool.pool_config.db_config.adapter.to_s }
234
+ pool_size.record(stat[:size], attributes: attrs)
235
+ pool_busy.record(stat[:busy], attributes: attrs)
236
+ pool_idle.record(stat[:idle], attributes: attrs)
237
+ pool_waiting.record(stat[:waiting], attributes: attrs)
238
+ rescue StandardError
239
+ # Pool may not be connected yet — retry next cycle
240
+ end
241
+ end
242
+ end
243
+ thread.abort_on_exception = false
244
+ thread
245
+ rescue StandardError => e
246
+ warn "Tracelit: failed to install connection pool poller: #{e.message}"
247
+ end
248
+
249
+ # Polls process RSS memory every 60 seconds on a daemon thread using ps,
250
+ # which works on both macOS (arm64-darwin) and Linux without /proc.
251
+ def self.install_memory_poller
252
+ memory_gauge = @meter.create_gauge(
253
+ "process.memory.rss",
254
+ description: "Process resident set size (RSS)",
255
+ unit: "MB"
256
+ )
257
+
258
+ pid = Process.pid
259
+
260
+ thread = Thread.new do
261
+ Thread.current[:tracelit_memory_poller] = true
262
+ loop do
263
+ sleep 60
264
+ begin
265
+ rss_kb = `ps -o rss= -p #{pid} 2>/dev/null`.strip.to_i
266
+ next if rss_kb == 0
267
+
268
+ rss_mb = rss_kb / 1024.0
269
+ memory_gauge.record(rss_mb, attributes: {
270
+ "process.pid" => pid.to_s,
271
+ "process.runtime" => "ruby",
272
+ })
273
+ rescue StandardError
274
+ # Ignore — ps may not be available in all environments
275
+ end
276
+ end
277
+ end
278
+ thread.abort_on_exception = false
279
+ thread
280
+ rescue StandardError => e
281
+ warn "Tracelit: failed to install memory poller: #{e.message}"
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracelit
4
+ # RailsLoggerBridge adds an OpenTelemetry log emitter to Rails.logger
5
+ # so that every Rails.logger call is also forwarded to the OTel
6
+ # LoggerProvider and exported via OTLP to the Tracelit logs table.
7
+ #
8
+ # It works by broadcasting a lightweight Logger subclass (OTelLogger)
9
+ # alongside the existing logger. In Rails 7.1+, Rails.logger is already
10
+ # an ActiveSupport::BroadcastLogger, so we call broadcast_to directly.
11
+ # In older setups we wrap it in a new BroadcastLogger.
12
+ #
13
+ # Severity mapping (OTel SeverityNumber spec):
14
+ # Rails DEBUG (0) → OTel 5 (SEVERITY_NUMBER_DEBUG)
15
+ # Rails INFO (1) → OTel 9 (SEVERITY_NUMBER_INFO)
16
+ # Rails WARN (2) → OTel 13 (SEVERITY_NUMBER_WARN)
17
+ # Rails ERROR (3) → OTel 17 (SEVERITY_NUMBER_ERROR)
18
+ # Rails FATAL (4) → OTel 21 (SEVERITY_NUMBER_FATAL)
19
+ # Rails UNKNOWN → OTel 1 (SEVERITY_NUMBER_TRACE)
20
+ module RailsLoggerBridge
21
+ SEVERITY_MAP = [5, 9, 13, 17, 21, 1].freeze
22
+
23
+ def self.install(logger_provider)
24
+ return unless defined?(::Rails) && ::Rails.logger
25
+
26
+ otel_logger = logger_provider.logger(
27
+ name: "rails",
28
+ version: Tracelit::VERSION
29
+ )
30
+
31
+ otel_sink = OTelLogger.new(otel_logger)
32
+
33
+ if ::Rails.logger.is_a?(ActiveSupport::BroadcastLogger)
34
+ ::Rails.logger.broadcast_to(otel_sink)
35
+ else
36
+ ::Rails.logger = ActiveSupport::BroadcastLogger.new(
37
+ ::Rails.logger,
38
+ otel_sink
39
+ )
40
+ end
41
+ rescue StandardError => e
42
+ warn "Tracelit: failed to install Rails logger bridge: #{e.message}"
43
+ end
44
+
45
+ # OTelLogger is a Logger subclass whose add method emits an OTel LogRecord
46
+ # instead of writing to an IO device. It is added as a broadcast target so
47
+ # the original Rails logger output is preserved.
48
+ #
49
+ # The SDK Logger#on_emit defaults context: to OpenTelemetry::Context.current,
50
+ # which automatically correlates the log record to the current active span
51
+ # (trace_id + span_id) without any extra work here.
52
+ class OTelLogger < ::Logger
53
+ def initialize(otel_logger)
54
+ # Discard output — this logger only emits OTel records
55
+ super(File::NULL)
56
+ @otel_logger = otel_logger
57
+ # Accept all severities so we don't filter below the original logger
58
+ self.level = ::Logger::DEBUG
59
+ end
60
+
61
+ def add(severity, message = nil, progname = nil)
62
+ severity_number = SEVERITY_MAP[severity.to_i] || 9
63
+ severity_text = ::Logger::SEV_LABEL[severity.to_i] || "ANY"
64
+
65
+ body = if message.nil?
66
+ block_given? ? yield : progname
67
+ else
68
+ message
69
+ end
70
+
71
+ @otel_logger.on_emit(
72
+ timestamp: Time.now,
73
+ severity_number: severity_number,
74
+ severity_text: severity_text,
75
+ body: body.to_s
76
+ )
77
+ rescue StandardError
78
+ # Never let OTel errors surface to the application
79
+ end
80
+ alias_method :log, :add
81
+
82
+ def close; end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracelit
4
+ # Railtie hooks Tracelit into the Rails boot sequence automatically.
5
+ # No explicit initializer call needed — adding the gem to the Gemfile
6
+ # is sufficient for Rails apps.
7
+ class Railtie < ::Rails::Railtie
8
+ # Run after the app is initialized so config/initializers/ have
9
+ # already been evaluated and Tracelit.configure { } blocks applied.
10
+ initializer "tracelit.configure", after: :load_config_initializers do
11
+ Tracelit::Instrumentation.setup(Tracelit.config)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracelit
4
+ VERSION = "0.1.0"
5
+ end
data/lib/tracelit.rb ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tracelit/version"
4
+ require_relative "tracelit/configuration"
5
+ require_relative "tracelit/instrumentation"
6
+
7
+ module Tracelit
8
+ class << self
9
+ # Global configuration instance. Thread-safe after boot — configure
10
+ # once in an initializer, read-only thereafter.
11
+ def config
12
+ @config ||= Configuration.new
13
+ end
14
+
15
+ # Yields the configuration object for block-style setup:
16
+ #
17
+ # Tracelit.configure do |config|
18
+ # config.api_key = "tl_live_abc123"
19
+ # config.service_name = "payments-api"
20
+ # config.environment = "production"
21
+ # config.sample_rate = 0.2
22
+ # end
23
+ #
24
+ def configure
25
+ yield config
26
+ end
27
+
28
+ # Manually trigger SDK setup. Not needed for Rails — the Railtie
29
+ # handles this automatically. Call explicitly for Sinatra/Rack:
30
+ #
31
+ # Tracelit.start!
32
+ #
33
+ def start!
34
+ Instrumentation.setup(config)
35
+ end
36
+
37
+ # Returns the OpenTelemetry tracer for this service.
38
+ # Use for manual instrumentation of custom operations:
39
+ #
40
+ # Tracelit.tracer.in_span("my_operation") do |span|
41
+ # span.set_attribute("order.id", order.id)
42
+ # do_work
43
+ # end
44
+ #
45
+ def tracer
46
+ OpenTelemetry.tracer_provider.tracer(
47
+ config.resolved_service_name,
48
+ VERSION
49
+ )
50
+ end
51
+
52
+ # Returns the Tracelit metrics interface for manual instrumentation:
53
+ #
54
+ # Tracelit.metrics.counter("payments.processed").add(1,
55
+ # attributes: { "currency" => "USD" }
56
+ # )
57
+ #
58
+ def metrics
59
+ Tracelit::Metrics
60
+ end
61
+ end
62
+ end
63
+
64
+ # Auto-require Railtie when Rails is present.
65
+ require_relative "tracelit/railtie" if defined?(::Rails::Railtie)
data/tracelit.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/tracelit/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "tracelit"
7
+ spec.version = Tracelit::VERSION
8
+ spec.authors = ["Tracelit"]
9
+ spec.email = ["hey@tracelit.io"]
10
+ spec.summary = "Official Ruby SDK for Tracelit backend observability"
11
+ spec.description = "Drop-in OpenTelemetry instrumentation for Rails, Sinatra, " \
12
+ "and Rack apps. Sends traces, metrics, and logs to the " \
13
+ "Tracelit ingest API via OTLP/HTTP."
14
+ spec.homepage = "https://tracelit.io"
15
+ spec.license = "MIT"
16
+
17
+ spec.required_ruby_version = ">= 3.0"
18
+
19
+ spec.files = Dir[
20
+ "lib/**/*.rb",
21
+ "tracelit.gemspec",
22
+ "README.md"
23
+ ]
24
+
25
+ spec.require_paths = ["lib"]
26
+
27
+ # Core OTel runtime dependencies
28
+ spec.add_dependency "opentelemetry-sdk", "~> 1.4"
29
+ spec.add_dependency "opentelemetry-exporter-otlp", "~> 0.26"
30
+ spec.add_dependency "opentelemetry-instrumentation-all", "~> 0.62"
31
+ spec.add_dependency "opentelemetry-logs-sdk", "~> 0.5"
32
+ spec.add_dependency "opentelemetry-exporter-otlp-logs", "~> 0.4"
33
+ spec.add_dependency "opentelemetry-metrics-sdk", "~> 0.13"
34
+ spec.add_dependency "opentelemetry-exporter-otlp-metrics", "~> 0.8"
35
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tracelit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tracelit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opentelemetry-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: opentelemetry-exporter-otlp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.26'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.26'
41
+ - !ruby/object:Gem::Dependency
42
+ name: opentelemetry-instrumentation-all
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.62'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.62'
55
+ - !ruby/object:Gem::Dependency
56
+ name: opentelemetry-logs-sdk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: opentelemetry-exporter-otlp-logs
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: opentelemetry-metrics-sdk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.13'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.13'
97
+ - !ruby/object:Gem::Dependency
98
+ name: opentelemetry-exporter-otlp-metrics
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.8'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.8'
111
+ description: Drop-in OpenTelemetry instrumentation for Rails, Sinatra, and Rack apps.
112
+ Sends traces, metrics, and logs to the Tracelit ingest API via OTLP/HTTP.
113
+ email:
114
+ - hey@tracelit.io
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - README.md
120
+ - lib/tracelit.rb
121
+ - lib/tracelit/configuration.rb
122
+ - lib/tracelit/error_always_on_sampler.rb
123
+ - lib/tracelit/error_span_processor.rb
124
+ - lib/tracelit/instrumentation.rb
125
+ - lib/tracelit/metrics.rb
126
+ - lib/tracelit/rails_logger_bridge.rb
127
+ - lib/tracelit/railtie.rb
128
+ - lib/tracelit/version.rb
129
+ - tracelit.gemspec
130
+ homepage: https://tracelit.io
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '3.0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.0.3.1
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Official Ruby SDK for Tracelit backend observability
153
+ test_files: []