xtrace 0.0.15

An AI observability service for collecting, storing, and querying traces, spans, and metrics across LLM and agent workflows.
Documentation
# XTrace Rust Backend Development Guide

This document guides implementation and development of a "Langfuse-like observability backend" in this repository. External API contracts follow `docs/api.md`.

## Goals and Scope (MVP)

Phase 1 implements:

- Trace list query: `GET /api/public/traces`
- Trace detail query: `GET /api/public/traces/{traceId}`
- Daily aggregated metrics: `GET /api/public/metrics/daily`

Note: For the system to be usable, the backend also needs internal ingest endpoints to write traces/observations to the database. These are not exposed to end users but are required.

## Recommended Tech Stack

- Web framework: Axum (on Tokio)
- Serialization: Serde
- Database: PostgreSQL
- ORM/query: SQLx (compile-time checks recommended)
- Logging and tracing: tracing + tower-http (request logs, trace id)
- Config: Environment variables (optional dotenv)

## Recommended Directory Structure

Backend code can live under `crates/` or at the repo root in a Rust workspace. Suggested layout:

- `crates/xtrace-api/`: HTTP API (routes, DTOs, auth, middleware)
- `crates/xtrace-service/`: Business logic (query, write, aggregation)
- `crates/xtrace-storage/`: SQLx access layer (repo, migrations, model mapping)
- `migrations/`: Database migrations

Alternatively, a monolith can split into `router/`, `service/`, `storage/` within one crate.

## Local Development Dependencies

- Rust stable (>= 1.75 recommended)
- PostgreSQL 14+
- sqlx-cli (optional but strongly recommended for migrations)

Install sqlx-cli (requires postgres dev libs/openssl; usually fine on macOS):

```bash
cargo install sqlx-cli --no-default-features --features native-tls,postgres
```

## Configuration (Environment Variables)

Service startup requires at least:

- `DATABASE_URL`: e.g. `postgres://user:pass@127.0.0.1:5432/xtrace`
- `API_BEARER_TOKEN`: Bearer token for public API (MVP: single token)
- `BIND_ADDR`: Listen address, default `127.0.0.1:8742`

Langfuse public API BasicAuth compatibility (optional):

- `XTRACE_PUBLIC_KEY`
- `XTRACE_SECRET_KEY`

Compatibility:
Also supports legacy names `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY`.

Conventions:

- Public endpoints (`/api/public/*`) must validate `Authorization: Bearer <token>`
- Internal ingest endpoints (e.g. `/v1/l/*`) may use a separate token or the same one (simpler initially)

## Database Migrations

Initialize and run migrations (example):

```bash
sqlx database create
sqlx migrate run
```

MVP migrations should include at least:

- `traces` table
- `observations` table
- (Optional) `daily_metrics` table (pre-aggregated)

## Running and Debugging

Local run (example):

```bash
export DATABASE_URL=postgres://user:pass@127.0.0.1:5432/xtrace
export API_BEARER_TOKEN=dev-token
export BIND_ADDR=127.0.0.1:8742
cargo run -p xtrace-api
```

Health check:

- `GET /healthz`: No auth; used for liveness

## API Implementation Conventions (aligned with docs/api.md)

### Unified Response Structure

Public endpoints follow Langfuse OpenAPI:

`GET /api/public/traces` returns paginated object: `{ data: [...], meta: { page, limit, totalItems, totalPages } }`

`GET /api/public/traces/{traceId}` returns the trace object directly (no outer `data/meta/message` wrapper).

### Pagination

`GET /api/public/traces` returns `data.meta`:

- `page`
- `limit`
- `totalItems`
- `totalPages`

### Time Filtering

`from_timestamp` / `to_timestamp` use ISO8601; backend returns 400 on parse failure.

### Tags Filtering

`tags` is an array parameter; semantics are "contains all tags (all-of)".

### Sorting

`order_by` whitelist:

- `timestamp.asc | timestamp.desc`
- `latency.asc | latency.desc`
- `totalCost.asc | totalCost.desc`

Invalid values return 400.

## Minimal Verification (curl)

Examples assume `API_BEARER_TOKEN=dev-token` and service listening on `127.0.0.1:8742`.

```bash
export API_BEARER_TOKEN=dev-token
export BASE_URL=http://127.0.0.1:8742

# For BasicAuth (xinference/langfuse compatibility)
# export XTRACE_PUBLIC_KEY=pk-xxx
# export XTRACE_SECRET_KEY=sk-yyy

# 1) traces list (default: core + io + scores + observations + metrics)
curl -sS "$BASE_URL/api/public/traces?page=1&limit=2" \
  -H "Authorization: Bearer $API_BEARER_TOKEN"

# 2) traces list (fields=core: omits input/output/metadata; latency/totalCost = -1; scores/observations empty)
curl -sS "$BASE_URL/api/public/traces?page=1&limit=2&fields=core" \
  -H "Authorization: Bearer $API_BEARER_TOKEN"

# 3) traces list (tags all-of + environment multi-value filter)
curl -sS "$BASE_URL/api/public/traces?page=1&limit=2&tags=foo&tags=bar&environment=default" \
  -H "Authorization: Bearer $API_BEARER_TOKEN"

# 4) trace detail (replace <TRACE_ID> with real UUID)
curl -sS "$BASE_URL/api/public/traces/<TRACE_ID>" \
  -H "Authorization: Bearer $API_BEARER_TOKEN"

# 5) metrics/daily (default last 30 days; supports fromTimestamp/toTimestamp, tags, traceName, userId)
curl -sS "$BASE_URL/api/public/metrics/daily?page=1&limit=50" \
  -H "Authorization: Bearer $API_BEARER_TOKEN"

# 6) traces list (BasicAuth)
curl -sS -u "$XTRACE_PUBLIC_KEY:$XTRACE_SECRET_KEY" \
  "$BASE_URL/api/public/traces?page=1&limit=2"
```

## Ingest Suggestions (Internal Endpoints)

For a complete loop, add internal ingest endpoints (not part of `docs/api.md` public API):

- `POST /v1/l/traces`: Create or upsert trace
- `POST /v1/l/observations`: Create or upsert observation

Key points:

- Idempotent (use `id` or `externalId` as upsert key)
- Allow observation-first or trace-first (at least one ordering must work)

## Testing Suggestions

- Route layer: Use `tower::ServiceExt` for request-level tests
- Storage layer: Use testcontainers or local postgres for integration tests

## Milestones

- M1: Run + auth + DB migrations + ingest writes
- M2: Implement `GET /api/public/traces` and `GET /api/public/traces/{trace_id}`
- M3: Implement `GET /api/public/metrics/daily` (real-time aggregation first; pre-aggregation later)