Module: Ditliti::DbTracing
- Defined in:
- lib/ditliti/db.rb
Overview
Database query auto-instrumentation.
Wraps a query execution with a db.query span carrying OpenTelemetry-
shaped database attributes. The backend (ingestion-api) parameterizes
and sanitizes db.query.text server-side and derives
db.query.summary / db.operation.name / db.collection.name /
ditliti.query_hash from it - callers here only need to supply the
real SQL text, exactly like every other span already sent by this SDK
travels over the same trust boundary to the user's own ingestion-api.
This never changes the wrapped call's exception flow: span-recording failures are swallowed, and the original exception (if any) is always re-raised unchanged.
Note: this environment has no Ruby runtime available, so this file has been carefully written (mirroring the already-tested Python/PHP/Elixir adapters' generic-wrapper + ORM-notification shape) but has not itself been executed - review before relying on it in production.
Class Method Summary collapse
- .extract_row_count(result) ⇒ Object
-
.record_completed_span(client, system:, text:, duration_ms:, collection: nil, operation: nil, error_type: nil) ⇒ Object
Records a span whose duration is already known (e.g. from ActiveRecord's notification, which reports a query's elapsed time only after it has already completed) - unlike
trace_query, there's no separate "finish" step since the operation is already over. - .safe_finish(span, extra_tags) ⇒ Object
-
.subscribe_active_record(client, system: "postgresql") ⇒ Object
Subscribes to ActiveRecord's
sql.active_recordnotification - no query wrapping required, works for the query builder, Active Record models, and rawconnection.executealike. - .trace_query(client, system:, text:, collection: nil, operation: nil) ⇒ Object
Class Method Details
.extract_row_count(result) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/ditliti/db.rb', line 92 def extract_row_count(result) return result if result.is_a?(Integer) && result >= 0 return result.length if result.respond_to?(:length) nil end |
.record_completed_span(client, system:, text:, duration_ms:, collection: nil, operation: nil, error_type: nil) ⇒ Object
Records a span whose duration is already known (e.g. from
ActiveRecord's notification, which reports a query's elapsed time
only after it has already completed) - unlike trace_query, there's
no separate "finish" step since the operation is already over.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ditliti/db.rb', line 66 def record_completed_span(client, system:, text:, duration_ms:, collection: nil, operation: nil, error_type: nil) client.start_trace if client.trace.nil? parent_span_id = client.trace.span_id ctx = client.trace.child = { "db.system.name" => system, "db.query.text" => text } ["db.operation.name"] = operation if operation ["db.collection.name"] = collection if collection ["error.type"] = error_type if error_type client.record_span( trace_id: ctx.trace_id, span_id: ctx.span_id, parent_span_id: parent_span_id, operation_name: "db.query", duration_ms: duration_ms, tags: ) end |
.safe_finish(span, extra_tags) ⇒ Object
86 87 88 89 90 |
# File 'lib/ditliti/db.rb', line 86 def safe_finish(span, ) span.finish(extra_tags: ) rescue StandardError # Instrumentation must never break the caller's query path. end |
.subscribe_active_record(client, system: "postgresql") ⇒ Object
Subscribes to ActiveRecord's sql.active_record notification - no
query wrapping required, works for the query builder, Active Record
models, and raw connection.execute alike. Requires ActiveSupport to
already be loaded by the host application (this SDK has no Rails
dependency of its own).
47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/ditliti/db.rb', line 47 def subscribe_active_record(client, system: "postgresql") ActiveSupport::Notifications.subscribe("sql.active_record") do |*args| event = ActiveSupport::Notifications::Event.new(*args) next if event.payload[:name] == "SCHEMA" # migrations/introspection queries, not app queries record_completed_span( client, system: system, text: event.payload[:sql], duration_ms: event.duration, collection: event.payload[:name] ) end end |
.trace_query(client, system:, text:, collection: nil, operation: nil) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/ditliti/db.rb', line 25 def trace_query(client, system:, text:, collection: nil, operation: nil) = { "db.system.name" => system, "db.query.text" => text } ["db.operation.name"] = operation if operation ["db.collection.name"] = collection if collection span = client.start_span("db.query", tags: ) begin result = yield count = extract_row_count(result) safe_finish(span, count ? { "db.response.returned_rows" => count.to_s } : {}) result rescue => error safe_finish(span, { "error.type" => error.class.name }) raise end end |