module CQL::Telemetry

Overview

Observability for every statement CQL runs: subscribers receive a QueryEvent after each one (successful or not), and an optional Tracer wraps the statement in a span (OpenTelemetry, Dashforge, ...).

CQL::Telemetry.subscribe("metrics") { |event| Log.info { "sql.#{event.operation} #{event.duration.total_milliseconds}ms" } }
CQL::Telemetry.alert_on_slow(200.milliseconds) { |event| Log.warn { "slow query: #{event.sql}" } }

.subscribe_transactions additionally reports Schema#transaction begin/commit/rollback, and Schema#pool_stats exposes the connection pool.

Parameter values are not included in events unless .capture_params is on, since they often contain personal data. A subscriber that raises is logged and never affects the query.

Defined in:

telemetry.cr
telemetry/event_queue.cr

Constant Summary

Log = ::Log.for(self)

Class Method Summary

Class Method Detail

def self.alert_on_slow(threshold : Time::Span, name : String = "cql.slow_query", *, async : Bool = false, &block : QueryEvent -> ) : Nil #

Calls the block for statements that took at least threshold, off the query's path when async (see .subscribe).


[View source]
def self.async_queue_capacity : Int32 #

Events the async queue holds before it drops new ones. Set it before the first async subscriber: the queue is built then and kept until .reset!, which also restores the default.


[View source]
def self.async_queue_capacity=(value : Int32) #

[View source]
def self.begin_transaction(adapter : Adapter, nested : Bool) : TransactionObserver | Nil #

Publishes Begin and returns the observer that publishes the end of the transaction, or nil (without allocating or reading the clock) when nobody subscribed.


[View source]
def self.capture_params : Bool #

[View source]
def self.capture_params=(value : Bool) #

[View source]
def self.current_context : String | Nil #

The context set by .with_context on the current fiber, if any.


[View source]
def self.dropped_events : Int64 #

Events the async queue dropped because it was full.


[View source]
def self.flush(timeout : Time::Span | Nil = nil) : Bool #

Waits until async subscribers have handled every event published before it; false on timeout, true without a queue.


[View source]
def self.log_queries(severity : ::Log::Severity = ::Log::Severity::Debug, name : String = "cql.log", *, async : Bool = false) : Nil #

Logs one line per statement, off the query's path when async (see .subscribe).


[View source]
def self.next_transaction_id : Int64 #

Process-wide, strictly increasing transaction id.


[View source]
def self.observe(adapter : Adapter, operation : Symbol, sql : String, params : Array(DB::Any), & : -> T) : T forall T #

Runs the block as one observed statement: inside the tracer's span, then reports the event to the subscribers.


[View source]
def self.reset! : Nil #

[View source]
def self.subscribe(name : String, *, async : Bool = false, &block : QueryEvent -> ) : Nil #

Registers (or replaces) the subscriber called name, by default called inline after each statement.

An async subscriber runs on one background fiber after the statement returns, fed by a bounded queue that drops events when full (.dropped_events). On a single Crystal thread this moves its work out of the query's latency, not out of the CPU; under multi-threading a push to a parked consumer can cost a scheduler wake.


[View source]
def self.subscribe_transactions(name : String, *, async : Bool = false, &block : TransactionEvent -> ) : Nil #

Registers (or replaces) the transaction subscriber called name; async works as in .subscribe.


[View source]
def self.subscribed?(name : String) : Bool #

Whether a query or transaction subscriber is registered under name.


[View source]
def self.tracer : Tracer | Nil #

[View source]
def self.tracer=(value : Tracer | Nil) #

[View source]
def self.unsubscribe(name : String) : Nil #

[View source]
def self.unsubscribe_transactions(name : String) : Nil #

[View source]
def self.with_context(context : String, &) #

Tags events emitted by the current fiber while the block runs.


[View source]