Skip to content

canardstack

OpenTelemetry metrics, logs, and traces stored in DuckLake.

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.

  • 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.
  • A full observability suite.
  • A multi-tenant production service.
  • A full Prometheus, Loki, Tempo, PromQL, LogQL, or TraceQL implementation.

Install and start canardstack. With no options, it uses local DuckLake storage under .canardstack and listens on 127.0.0.1:4318.

Terminal window
cargo install --locked canardstack
canardstack

In another terminal, send one OTLP/HTTP JSON log:

Terminal window
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:

Terminal window
sleep 2
duckdb
INSTALL ducklake;
LOAD ducklake;
ATTACH 'ducklake:.canardstack/canardstack.ducklake' AS canardlake
(DATA_PATH '.canardstack/storage');
USE canardlake;
SELECT timestamp, body
FROM logs
WHERE body = 'hello world'
ORDER BY ingested_at DESC
LIMIT 1;

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 canardstack and Grafana with local DuckLake storage:

Terminal window
docker compose up --build

The default local stack exposes:

  • canardstack: http://localhost:4318
  • Grafana: http://localhost:3000

In another terminal, run the smoke client:

Terminal window
docker compose run --rm smoke

The 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 the provisioned dashboard:

http://localhost:3000/d/canardstack-overview/canardstack-overview

Use admin/admin if Grafana asks for credentials.

Use the send telemetry guide to configure OTLP/HTTP producers. Use the deployment docs for MotherDuck, GCP Cloud Run, and AWS ECS/Fargate options.

Use the Grafana datasource guide to connect metrics, logs, and traces. Use the DuckDB SQL guide to query the DuckLake tables directly.

For host development:

Terminal window
cargo check
cargo test
cargo run

The host server defaults to 127.0.0.1:4318.