Telemetry events emitted by Lotus.
Lotus uses :telemetry to emit events for query execution, cache operations,
and schema introspection. You can attach handlers to these events for monitoring,
logging, or integration with tools like Phoenix LiveDashboard or AppSignal.
Run Events
A run is one call to Lotus.run_query/2, Lotus.run_statement/3 or
Lotus.Runner.run_statement/3: :before_query, the execute step (or the
result cache), :before_execute, :after_query. The run events bracket all
of it, on every path — a result served from the cache, a statement a plug
halted in any phase — and always carry the caller's :context. A consumer
that records who ran what, whether it was served from the cache and whether
it was refused attaches to these three events.
The query events below cover only the execute step: a result served from the cache emits no query event, and neither does a run a plug halted before execution.
[:lotus, :run, :start]
Measurements: :system_time.
Metadata:
:source- The data source name:statement- TheLotus.Query.Statementthe caller supplied, before any:before_queryrewrite:context- The caller-supplied context (ornil):vars- The bound query variables, by name
[:lotus, :run, :stop]
Emitted when a run completes and its result is returned to the caller.
Measurements: :duration (native units), :row_count.
Metadata: the start metadata, with :statement now the statement that
ran, plus:
:result- TheLotus.Resultreturned to the caller:relations- What preflight knew about the statement: a list of{schema, table},{:unrestricted, reason}or{:skipped, reason}:origin-:executedor:cached
[:lotus, :run, :exception]
Emitted when any phase of a run fails, including a middleware halt.
Measurements: :duration.
Metadata: the start metadata plus:
:phase-:before_query,:sanitize,:preflight,:before_execute,:executeor:after_query:reason- The error or halt reason the caller receives:kind-:error:statement,:relations,:origin- Present when the failure came after the execute step, describing what ran
Query Events
[:lotus, :query, :start]
Emitted when a query begins execution.
Measurements:
:system_time- The system time at the start of the query (in native units)
Metadata:
:source- The data source name (e.g."main","warehouse"):statement- TheLotus.Query.Statementbeing executed. Its:bodyis the adapter-native payload and:paramsthe bound values.:context- The caller-supplied context (ornil)
[:lotus, :query, :stop]
Emitted when a query completes successfully.
Measurements:
:duration- The query duration (in native time units):row_count- The number of rows returned
Metadata:
:source- The data source name:statement- TheLotus.Query.Statementthat was executed:context- The caller-supplied context (ornil):result- TheLotus.Resultstruct
[:lotus, :query, :exception]
Emitted when a query fails with an exception.
Measurements:
:duration- The time elapsed before the failure (in native time units)
Metadata:
:source- The data source name:statement- TheLotus.Query.Statementthat was executed:context- The caller-supplied context (ornil):kind- The kind of exception (:error,:exit, or:throw):reason- The exception or error reason:stacktrace- The stacktrace
Cache Events
[:lotus, :cache, :hit]
Emitted when a cache lookup finds an existing entry.
Measurements:
:count- Always1
Metadata:
:key- The cache key
[:lotus, :cache, :miss]
Emitted when a cache lookup does not find an entry.
Measurements:
:count- Always1
Metadata:
:key- The cache key
[:lotus, :cache, :put]
Emitted when a value is stored in the cache.
Measurements:
:count- Always1
Metadata:
:key- The cache key:ttl_ms- The TTL in milliseconds
Schema Introspection Events
[:lotus, :schema, :introspection, :start]
Emitted when a schema introspection operation begins.
Measurements:
:system_time- The system time at the start (in native units)
Metadata:
:operation- The introspection operation (e.g.,:list_schemas,:list_tables,:describe_table,:get_table_stats,:list_relations):source- The data source name
[:lotus, :schema, :introspection, :stop]
Emitted when a schema introspection operation completes.
Measurements:
:duration- The operation duration (in native time units)
Metadata:
:operation- The introspection operation:source- The data source name:result-:okor:error
Content Events
A content change is one create, update or delete of a query, visualization,
dashboard, dashboard card, dashboard filter or filter mapping, including
enabling or disabling public sharing and moving a card in a reorder. The
content events bracket each change on every path — a refusal, a failed
validation, an update that writes nothing — and always carry the caller's
:context. A reorder emits one span per card it moves.
[:lotus, :content, :change, :start]
Measurements: :system_time.
Metadata:
:op-:create,:update,:delete,:enable_sharingor:disable_sharing:resource-:query,:visualization,:dashboard,:dashboard_card,:dashboard_filteror:filter_mapping:record- The struct as stored, ornilon a create:context- The caller-supplied context (ornil)
[:lotus, :content, :change, :stop]
Emitted when the change completes. The metadata is the
:after_content_change middleware payload. It is also emitted for an update
that wrote nothing, which fires no :after_content_change.
Measurements: :duration (native units).
Metadata: the start metadata, with :record now the struct as written,
plus:
:changes- Each changed field with its written value;%{}on a delete and on an update that wrote nothing
[:lotus, :content, :change, :exception]
Emitted when the change fails: a :before_content_change plug refused it, the
changeset was invalid, or the write raised, threw or exited. In a reorder,
every card change of the call shares the outcome.
Measurements: :duration.
Metadata: the start metadata plus:
:kind-:error, or:throwor:exitwhen the call did not return:reason- The error the caller receives:{:halted, reason}for a refusal, theEcto.Changesetfor a failed validation:stacktrace- Present when the call raised, threw or exited
Example
Attach a handler in your application's start/2 callback:
:telemetry.attach_many(
"lotus-logger",
[
[:lotus, :query, :stop],
[:lotus, :query, :exception],
[:lotus, :cache, :hit],
[:lotus, :cache, :miss]
],
&MyApp.TelemetryHandler.handle_event/4,
nil
)A simple logging handler:
defmodule MyApp.TelemetryHandler do
require Logger
def handle_event([:lotus, :query, :stop], measurements, metadata, _config) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
Logger.info("Lotus query completed in #{duration_ms}ms, rows: #{measurements.row_count}")
end
def handle_event([:lotus, :query, :exception], measurements, metadata, _config) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
Logger.error("Lotus query failed after #{duration_ms}ms: #{inspect(metadata.reason)}")
end
def handle_event([:lotus, :cache, :hit], _measurements, metadata, _config) do
Logger.debug("Lotus cache hit: #{metadata.key}")
end
def handle_event([:lotus, :cache, :miss], _measurements, metadata, _config) do
Logger.debug("Lotus cache miss: #{metadata.key}")
end
end