Telemetry

View Source

Kura emits telemetry events for every database query. You can attach any handler — logging, metrics, OpenTelemetry — without Kura knowing about it.

Events

Kura emits a single event:

[kura, repo, query]

Emitted after every query execution.

Measurements:

KeyTypeDescription
durationinteger()Wall-clock time in native time units
duration_usinteger()Wall-clock time in microseconds

Metadata:

KeyTypeDescription
querybinary()The compiled SQL string
params[term()]Bind parameters
resultok | errorWhether the query succeeded
num_rowsinteger()Number of rows returned or affected
repomodule()The repo module that ran the query
sourcebinary() | undefinedThe table name extracted from the query

Attaching Handlers

Attach handlers at application startup using telemetry:attach/4:

%% In your application's start/2
start(_Type, _Args) ->
    telemetry:attach(
        ~"kura-logger",
        [kura, repo, query],
        fun ?MODULE:handle_query_event/4,
        #{}
    ),
    my_sup:start_link().

handle_query_event(_Event, Measurements, Metadata, _Config) ->
    logger:info("Kura ~s (~pus) [~p rows]", [
        maps:get(query, Metadata),
        maps:get(duration_us, Measurements),
        maps:get(num_rows, Metadata)
    ]).

Slow Query Logging

telemetry:attach(
    ~"kura-slow-queries",
    [kura, repo, query],
    fun(_, #{duration_us := D}, #{query := Q}, _) ->
        case D > 5000 of
            true -> logger:warning("Slow query (~pus): ~s", [D, Q]);
            false -> ok
        end
    end,
    #{}
).

Metrics with telemetry_metrics

If you use telemetry_metrics, define metrics against the Kura event:

[
    Telemetry.Metrics.distribution("kura.repo.query.duration",
        unit: {:native, :millisecond},
        tags: [:source, :result]
    ),
    Telemetry.Metrics.counter("kura.repo.query.count",
        tags: [:source, :result]
    )
]

OpenTelemetry

Use opentelemetry_kura to automatically create OpenTelemetry spans for every query:

%% In your application's start/2
opentelemetry_kura:setup().

See the opentelemetry_kura documentation for details.

Legacy Log Configuration

For backward compatibility, the {kura, [{log, ...}]} application environment is still supported. When set, it runs in addition to telemetry events.

%% sys.config
{kura, [
    {log, true}              %% Built-in logger (logger:info)
    %% {log, {M, F}}         %% Call M:F(Event) for each query
    %% {log, fun(E) -> ... end}  %% Runtime fun
]}.

The legacy log event map:

#{query => ~"SELECT ...",
  params => [1],
  result => ok,
  num_rows => 1,
  duration_us => 1500,
  repo => my_repo}

Recommendation: Prefer attaching telemetry handlers over the legacy log config. The telemetry approach is composable (multiple handlers), standard (same pattern as Ecto, Phoenix, etc.), and supports richer measurements.

Testing

build_telemetry_metadata/4 and extract_source/1 are exported for testing:

Meta = kura_repo_worker:build_telemetry_metadata(
    my_repo, ~"SELECT * FROM \"users\"", [], #{rows => []}
),
~"users" = maps:get(source, Meta).