ditliti

Official Elixir SDK for Ditliti - error tracking, tracing, session replay, and profiling ingestion.

This SDK talks to a self-hosted Ditliti instance (ingestion-api). It does not connect to any managed/hosted service - point it at your own deployment's ingestion URL. Dependency-free (no Jason/Poison/HTTPoison required).

Install

def deps do
[
{:ditliti, git: "https://github.com/kangartha/inspectora.git", ref: "sdk-v0.1.2", sparse: "sdk/elixir"}
]
end

Quick start

client = Ditliti.new("http://localhost:8305", "mp_xxx", "PROJECT_UUID", release: "1.2.3", environment: "production")
try do
risky_operation()
rescue
exception ->
Ditliti.capture_exception(client, exception, context: %{extra: "context"})
end

The client struct is immutable: functions that add context (add_breadcrumb/3, put_user/2, put_session/2) return an updated struct rather than mutating anything in place.

API

Distributed tracing

Generates real W3C traceparent (https://www.w3.org/TR/trace-context/) trace context, so a trace can be propagated across service/microservice boundaries instead of staying siloed per project. Since the client struct is immutable, start_trace/2 returns an updated client rather than mutating anything:

# Service A: start a trace and call Service B, forwarding the header.
client = Ditliti.start_trace(client)
HTTPoison.get(url, [{"traceparent", Ditliti.get_traceparent(client)}])
# Service B: continue the same trace from the inbound header (Ditliti.Plug does this automatically).
client = Ditliti.start_trace(client, incoming_traceparent_header)
{client, span} = Ditliti.start_span(client, "db.query", tags: %{"db" => "postgres"})
# ... do the work ...
Ditliti.finish_span(client, span)
# capture_exception/2 automatically tags trace_id when a trace is active,
# so errors show up correlated to the trace at GET /api/v1/traces/:trace_id (org-wide).

Database query instrumentation (Database Insights)

Ditliti.Db wraps a query with a db.query span carrying the OpenTelemetry-shaped attributes the backend uses for Database Insights (slow query / N+1 / error-rate / regression detection). The server parameterizes and hashes db.query.text itself - send the real SQL text, never a redacted one:

# Generic wrapper - works for any driver/Ecto adapter:
{client, result} =
Ditliti.Db.trace_query(client, [system: "postgresql", text: "SELECT * FROM orders WHERE id = $1"], fn ->
Postgrex.query(conn, "SELECT * FROM orders WHERE id = $1", [order_id])
end)
# Or auto-instrument every query via Ecto's own :telemetry event - no Repo
# wrapping required, works for the query builder/changesets/raw queries alike:
Ditliti.Db.attach_ecto_telemetry(client, [:my_app, :repo, :query], system: "postgresql")

attach_ecto_telemetry/3 correlates to the current trace via Ditliti.current_trace/0, which start_trace/2 populates automatically for the calling process (Ecto/Postgrex telemetry handlers run in the same process as the query call) - call start_trace/2 early in the request lifecycle (e.g. a Phoenix Plug) for DB spans to show up linked to that request's trace.

Neither ever sets db.query.summary or ditliti.query_hash (server-computed). On failure they record error.type and preserve the original error unchanged (trace_query/3 re-raises; the Ecto handler reads error.type from the telemetry event's :result metadata); on success they record db.response.returned_rows, inferred from common Ecto/Postgrex result shapes or via an explicit :row_count option.

License

MIT - see LICENSE.