Skip to content

Commit 65b2e46

Browse files
authored
feat(otel): add OpenTelemetry ingest, query, and frontend traces UI (#18)
* feat(otel): add OpenTelemetry support with new permissions and plugin integration - Introduced `temps-otel` crate to the workspace and updated dependencies in `Cargo.toml`. - Added `OtelRead` and `OtelWrite` permissions to the `Permission` enum in `temps-auth`. - Registered `OtelPlugin` in the console API for OpenTelemetry metrics, traces, and logs collection. - Created migration for OpenTelemetry tables in the database. - Updated relevant files to integrate OpenTelemetry functionality across the application. * feat(otel): add OpenTelemetry ingest, query, and frontend traces UI - Complete temps-otel crate: OTLP/HTTP protobuf ingest (traces, metrics, logs), query handlers, TimescaleDB storage, rate limiting, quota checks, anomaly detection, health summaries, and sidecar config generation - Auth: support tk_ (API key) and dt_ (deployment token) authentication for OTel ingest with path-based and header-based routes - Frontend: Traces list page with filtering (time range, service, status), trace detail page with span waterfall visualization and span detail panel, setup section with OTLP endpoint and Next.js code snippets - Add deployment_id to deployment tokens for OTel context propagation - Fix protobuf Span.flags from uint32 to fixed32 per OTLP v1.1.0+ spec - Remove server-side tail sampling (sampling is client SDK responsibility) - Add OtelRead/OtelWrite permissions, plugin registered in console - 117 passing unit tests, zero clippy warnings * ci(otel): install protobuf-compiler in CI and document prerequisites - Add protobuf-compiler installation to all CI jobs that compile the workspace (check, clippy, build-tests, unit-tests, integration-tests) - Add temps-otel to unit-b test group - Add OTel feature entry to CHANGELOG.md [Unreleased] section - Document protoc and wasm-pack as prerequisites in CONTRIBUTING.md with platform-specific installation instructions - Add changelog reminder to PR checklist * feat(otel): add trace-level pagination with TraceSummary endpoint Add GET /otel/trace-summaries that returns one row per trace (grouped by trace_id) with root span name, service name, deployment environment, span count, error count, and duration. This fixes the pagination bug where the old endpoint returned flat spans causing only ~5 traces to display per page. - Add TraceSummary type with deployment_environment field - Add query_trace_summaries() and count_traces() to OtelStorage trait - Implement both in TimescaleDB (GROUP BY trace_id with array_agg) - Implement both in MockOtelStorage for tests - Add TraceSummariesResponse handler with proper total count - Register new route and OpenAPI annotations - Update TracesList.tsx to use new endpoint (remove client-side groupByTrace) - Show Environment column as badge when viewing all environments - Change page size from 50 to 10 traces per page - Auto-inject OTel env vars in workflow_planner for deployments * style(otel): add mobile responsiveness to traces list and detail views - TracesList: filters stack vertically on mobile (flex-col sm:flex-row), selects go full-width, hide Kind/Spans/Timestamp columns on mobile, compact pagination, overflow-x-auto on table - TraceDetail: waterfall + detail panel stack vertically on mobile (flex-col lg:flex-row), detail panel goes full-width, span name column narrower on mobile, min-width on scrollable rows - Add mobile responsiveness guidelines to CLAUDE.md * feat(otel): add Traces to project command palette (Ctrl+K) * fix(otel): resolve environment name from deployments table in trace summaries The deployment_environment column comes from the OTel resource attribute which most SDKs don't set. JOIN deployments + environments tables to get the actual environment name, falling back to the resource attribute via COALESCE. Also qualify all column references with table alias since the query now involves multiple tables. * fix(otel): add horizontal overflow to span detail panel
1 parent b32d810 commit 65b2e46

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+14429
-48
lines changed

.github/workflows/rust-tests.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ jobs:
3030
- name: Cache dependencies
3131
uses: Swatinem/rust-cache@v2
3232

33+
- name: Install system dependencies
34+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
35+
3336
- name: Install wasm-pack and Build WASM
3437
run: |
3538
cargo install wasm-pack
@@ -76,6 +79,9 @@ jobs:
7679
- name: Cache dependencies
7780
uses: Swatinem/rust-cache@v2
7881

82+
- name: Install system dependencies
83+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
84+
7985
- name: Install wasm-pack and Build WASM
8086
run: |
8187
cargo install wasm-pack
@@ -116,6 +122,9 @@ jobs:
116122
shared-key: test-build
117123
save-if: true
118124

125+
- name: Install system dependencies
126+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
127+
119128
- name: Install wasm-pack and Build WASM
120129
run: |
121130
cargo install wasm-pack
@@ -176,6 +185,7 @@ jobs:
176185
-p temps-email
177186
-p temps-analytics
178187
-p temps-analytics-events
188+
-p temps-otel
179189
180190
steps:
181191
- name: Checkout code
@@ -195,6 +205,9 @@ jobs:
195205
shared-key: test-build
196206
save-if: false
197207

208+
- name: Install system dependencies
209+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
210+
198211
- name: Install wasm-pack and Build WASM
199212
run: |
200213
cargo install wasm-pack
@@ -327,6 +340,9 @@ jobs:
327340
shared-key: test-build
328341
save-if: false
329342

343+
- name: Install system dependencies
344+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
345+
330346
- name: Install wasm-pack and Build WASM
331347
run: |
332348
cargo install wasm-pack

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- OpenTelemetry (OTel) ingest and query system (`temps-otel` crate) with OTLP/protobuf support for traces, metrics, and logs; header-based and path-based ingest routes; `tk_` API key and `dt_` deployment token authentication; `OtelRead`/`OtelWrite` permissions; TimescaleDB storage with hypertables; OpenAPI-documented query endpoints for traces, spans, metrics, and logs; web UI with filterable trace list, waterfall span visualization, and setup instructions
12+
- `deployment_id` field on deployment tokens, allowing OTel ingest to associate telemetry with specific deployments
13+
- `protobuf-compiler` installation in CI workflow for `temps-otel` proto compilation
1114
- External plugin system: standalone binaries in `~/.temps/plugins/` are auto-discovered, spawned, and integrated at boot via stdout JSON handshake (manifest + ready) over Unix domain sockets; Temps reverse-proxies `/api/x/{plugin_name}/*` to each plugin and serves `/api/x/plugins` for manifest listing (#19)
1215
- `temps-plugin-sdk` crate for plugin authors: `ExternalPlugin` trait, `main!()` macro, `PluginContext` (direct Postgres access, data dir), `TempsAuth` extractor, and hyper-over-Unix-socket runtime
1316
- `temps-external-plugins` crate following the standard `TempsPlugin` pattern with service layer, utoipa-annotated handler, and OpenAPI schema registration

CLAUDE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,16 @@ if (isLoading) return <Spinner />
883883
- Center content with `mx-auto` when using `max-w-*` constraints
884884
- Use cards for selections instead of dropdowns where practical
885885

886+
### Mobile Responsiveness
887+
- **Tables**: wrap in `overflow-x-auto`; hide secondary columns with `hidden md:table-cell`
888+
- **Filter bars**: `flex flex-col gap-2 sm:flex-row sm:flex-wrap`; selects use `w-full sm:w-[Npx]`
889+
- **Grids**: `grid-cols-1``md:grid-cols-2``lg:grid-cols-3` (or `grid-cols-2 md:grid-cols-4` for stat cards)
890+
- **Side panels**: `flex-col lg:flex-row`; panel uses `w-full lg:w-[Npx]`
891+
- **Pagination**: compact `{page} / {totalPages}` on mobile; full "Showing X–Y of Z" `hidden sm:inline`
892+
- **Headers**: `flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between`
893+
- **Button text**: `hidden sm:inline` for labels next to icons; icon-only on mobile
894+
- **Min-width**: add `min-w-[Npx]` on scrollable containers so content doesn't collapse
895+
886896
### Testing with Playwright
887897

888898
Local dev credentials:

CONTRIBUTING.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,41 @@ Thank you for your interest in contributing to Temps. Whether you are reporting
1818
- **PostgreSQL** with TimescaleDB extension
1919
- **Bun** (for frontend development)
2020
- **Node.js** 18+
21+
- **protobuf compiler** (`protoc`) -- required by the `temps-otel` crate to compile OpenTelemetry `.proto` files
22+
- **wasm-pack** -- required to build the `temps-captcha-wasm` crate
23+
24+
#### Installing protoc
25+
26+
```bash
27+
# macOS
28+
brew install protobuf
29+
30+
# Debian/Ubuntu
31+
sudo apt-get install -y protobuf-compiler
32+
33+
# Fedora
34+
sudo dnf install -y protobuf-compiler
35+
36+
# Or download from https://github.com/protocolbuffers/protobuf/releases
37+
```
38+
39+
#### Installing wasm-pack
40+
41+
```bash
42+
cargo install wasm-pack
43+
```
2144

2245
### Clone and Build
2346

2447
```bash
2548
git clone https://github.com/gotempsh/temps.git
2649
cd temps
50+
51+
# Build the WASM captcha module (required before workspace compilation)
52+
cd crates/temps-captcha-wasm
53+
bun run build
54+
cd ../..
55+
2756
cargo build --release
2857
```
2958

@@ -97,6 +126,7 @@ HTTP Handlers -> Service Layer -> Data Access (Sea-ORM)
97126
- `temps-proxy` -- reverse proxy with TLS/ACME support
98127
- `temps-auth` -- authentication and permission system
99128
- `temps-providers` -- external service providers (PostgreSQL, Redis, S3)
129+
- `temps-otel` -- OpenTelemetry ingest and query (OTLP/protobuf, requires `protoc`)
100130

101131
## Coding Standards
102132

@@ -170,6 +200,7 @@ Pre-commit hooks run automatically on each commit to check formatting (`cargo fm
170200
- [ ] New functionality includes tests
171201
- [ ] Commit messages follow Conventional Commits
172202
- [ ] PR description explains the change
203+
- [ ] `CHANGELOG.md` updated under `[Unreleased]` (or add `skip-changelog` label)
173204

174205
## Good First Issues
175206

Cargo.lock

Lines changed: 93 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ members = [
5151
"crates/temps-vulnerability-scanner",
5252
"crates/temps-kv",
5353
"crates/temps-blob",
54+
"crates/temps-otel",
5455
"crates/temps-environments",
5556
"crates/temps-screenshots",
5657
"crates/temps-embeddings",

crates/temps-auth/src/context.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ use temps_entities::deployment_tokens::DeploymentTokenPermission;
44
use temps_entities::users;
55
use utoipa::ToSchema;
66

7+
/// Info extracted from a deployment token auth source.
8+
#[derive(Debug, Clone)]
9+
pub struct DeploymentTokenInfo {
10+
pub project_id: i32,
11+
pub environment_id: Option<i32>,
12+
pub deployment_id: Option<i32>,
13+
pub token_id: i32,
14+
pub token_name: String,
15+
}
16+
717
// Simplified user schema for OpenAPI documentation
818
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
919
pub struct UserSchema {
@@ -37,6 +47,7 @@ pub enum AuthSource {
3747
DeploymentToken {
3848
project_id: i32,
3949
environment_id: Option<i32>,
50+
deployment_id: Option<i32>,
4051
token_id: i32,
4152
token_name: String,
4253
permissions: Vec<DeploymentTokenPermission>,
@@ -65,6 +76,7 @@ pub enum AuthSourceSchema {
6576
DeploymentToken {
6677
project_id: i32,
6778
environment_id: Option<i32>,
79+
deployment_id: Option<i32>,
6880
token_id: i32,
6981
token_name: String,
7082
permissions: Vec<String>,
@@ -152,6 +164,7 @@ impl AuthContext {
152164
pub fn new_deployment_token(
153165
project_id: i32,
154166
environment_id: Option<i32>,
167+
deployment_id: Option<i32>,
155168
token_id: i32,
156169
token_name: String,
157170
permissions: Vec<DeploymentTokenPermission>,
@@ -161,6 +174,7 @@ impl AuthContext {
161174
source: AuthSource::DeploymentToken {
162175
project_id,
163176
environment_id,
177+
deployment_id,
164178
token_id,
165179
token_name,
166180
permissions: permissions.clone(),
@@ -266,15 +280,22 @@ impl AuthContext {
266280
}
267281

268282
/// Get deployment token info if this is a deployment token auth
269-
pub fn deployment_token_info(&self) -> Option<(i32, Option<i32>, i32, String)> {
283+
pub fn deployment_token_info(&self) -> Option<DeploymentTokenInfo> {
270284
match &self.source {
271285
AuthSource::DeploymentToken {
272286
project_id,
273287
environment_id,
288+
deployment_id,
274289
token_id,
275290
token_name,
276291
..
277-
} => Some((*project_id, *environment_id, *token_id, token_name.clone())),
292+
} => Some(DeploymentTokenInfo {
293+
project_id: *project_id,
294+
environment_id: *environment_id,
295+
deployment_id: *deployment_id,
296+
token_id: *token_id,
297+
token_name: token_name.clone(),
298+
}),
278299
_ => None,
279300
}
280301
}
@@ -287,6 +308,14 @@ impl AuthContext {
287308
}
288309
}
289310

311+
/// Get the deployment ID for deployment tokens
312+
pub fn deployment_id(&self) -> Option<i32> {
313+
match &self.source {
314+
AuthSource::DeploymentToken { deployment_id, .. } => *deployment_id,
315+
_ => None,
316+
}
317+
}
318+
290319
/// Get the user, returning an error if this is a deployment token auth
291320
/// Use this for handlers that require a user
292321
pub fn require_user(&self) -> Result<&users::Model, &'static str> {

0 commit comments

Comments
 (0)