canardstack
canardstack is an experimental, single-binary observability backend for storing OpenTelemetry logs, traces, gauge metrics, and sum metrics in DuckLake.
It accepts OTLP/HTTP, normalizes telemetry into Arrow batches, stores immutable Parquet-backed DuckLake segments, and exposes bounded Prometheus, Loki, and Tempo compatibility APIs for Grafana-style clients.
What This Is
Section titled “What This Is”- A single Rust process with synchronous std-library HTTP serving.
- Local DuckLake storage by default, with remote DuckLake attach support.
- A focused compatibility surface for inspecting telemetry through existing Prometheus, Loki, Tempo, and DuckDB-compatible tools.
What This Is Not
Section titled “What This Is Not”- A full observability suite.
- A multi-tenant production service.
- A full Prometheus, Loki, Tempo, PromQL, LogQL, or TraceQL implementation.
Start Locally
Section titled “Start Locally”Install and start canardstack. With no options, it uses local DuckLake storage
under .canardstack and listens on 127.0.0.1:4318.
cargo install --locked canardstackcanardstackIn another terminal, send one OTLP/HTTP JSON log:
curl -sS -X POST http://127.0.0.1:4318/v1/logs \ -H 'Authorization: Bearer dev-canardstack-key' \ -H 'Content-Type: application/json' \ --data '{"resourceLogs":[{"scopeLogs":[{"logRecords":[{"timeUnixNano":"1779667200000000000","body":{"stringValue":"hello world"}}]}]}]}'Give the scheduler a moment to seal the row into DuckLake, then query it directly from the DuckDB CLI:
sleep 2duckdbINSTALL ducklake;LOAD ducklake;ATTACH 'ducklake:.canardstack/canardstack.ducklake' AS canardlake (DATA_PATH '.canardstack/storage');USE canardlake;
SELECT timestamp, bodyFROM logsWHERE body = 'hello world'ORDER BY ingested_at DESCLIMIT 1;Schema
Section titled “Schema”canardstack stores four DuckLake telemetry tables: logs, spans,
metric_gauge, and metric_sum. The physical columns come from
otlp2records, with two local storage
columns added by canardstack: ingested_at and source_format.
Arbitrary OpenTelemetry resource, scope, log, span, and metric attributes are
stored as JSON strings in attribute columns. Grafana-facing labels such as
deployment_environment, http_method, and http_route are derived from those
canonical columns in bounded query and metadata paths instead of being stored as
separate raw-table columns.
For the full OpenTelemetry demo workflow, use the Demo guide.
Start The Stack
Section titled “Start The Stack”Start canardstack and Grafana with local DuckLake storage:
docker compose up --buildThe default local stack exposes:
- canardstack:
http://localhost:4318 - Grafana:
http://localhost:3000
Send Sample Telemetry
Section titled “Send Sample Telemetry”In another terminal, run the smoke client:
docker compose run --rm smokeThe smoke workload sends logs, a multi-span trace, gauge samples, and cumulative sum samples through OTLP/HTTP. It also checks storage health and the Prometheus, Loki, and Tempo-compatible query paths.
Open Grafana
Section titled “Open Grafana”Open the provisioned dashboard:
http://localhost:3000/d/canardstack-overview/canardstack-overviewUse admin/admin if Grafana asks for credentials.
Deploy
Section titled “Deploy”Use the send telemetry guide to configure OTLP/HTTP producers. Use the deployment docs for MotherDuck, GCP Cloud Run, and AWS ECS/Fargate options.
Query Data
Section titled “Query Data”Use the Grafana datasource guide to connect metrics, logs, and traces. Use the DuckDB SQL guide to query the DuckLake tables directly.
Build From Source
Section titled “Build From Source”For host development:
cargo checkcargo testcargo runThe host server defaults to 127.0.0.1:4318.