# Stack guides

> Per-stack integration snippets and the canonical http.* metric names Lumin chart panels expect.

Canonical: https://docs.getlumin.dev/stacks/

The Lumin agent is stack-agnostic — it scrapes whatever Prometheus exposition you point it at and tails whatever stdout/stderr you label. But the **Metrics page in the UI is built around three canonical metric names**, and the per-stack guides below give you the exact snippet to emit them.

## The canonical schema

If you want the canned KPIs, latency-distribution histogram, and top-routes table to light up, your service should emit:

| Metric | Type | Labels | What it counts |
|---|---|---|---|
| `http.requests` | counter | `{method, route, status_class}` | Every HTTP request. `status_class` ∈ `{2xx, 3xx, 4xx, 5xx}`. |
| `http.latency_ms` | histogram | `{method, route}` | Request latency in ms. Bucket bounds `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000]` work for typical Go/Python/Node services. |
| `http.errors` | counter | `{method, route, status}` | Increment only when `status >= 500`. Raw 3-digit code is fine here — 5xx codes are low cardinality in practice. |

**Use the templated route, not the expanded path.** `/api/projects/:projectId/logs` is one route; `/api/projects/1/logs` and `/api/projects/2/logs` are two paths sharing a route. Templating keeps cardinality bounded as your project count grows.

## How frameworks map onto this

You will rarely emit these names by hand. Each supported framework has a library that already emits HTTP metrics under its own names (`http_server_requests_seconds_count` for Spring's Micrometer, `gin_http_requests_total` for `prometheus/client_golang`, …) and the agent re-keys them to the canonical names when you tell it the framework via `lumin.metrics.framework`.

The `framework` value to pass in the Docker label:

| Framework | `lumin.metrics.framework` | Underlying library |
|---|---|---|
| [Browser SDK](https://docs.getlumin.dev/stacks/browser/) | n/a (event ingest, not metric scrape) | `@lumin-monitor/browser` |
| [Gin (Go)](https://docs.getlumin.dev/stacks/gin/) | `gin` | `prometheus/client_golang` |
| [Spring Boot (JVM)](https://docs.getlumin.dev/stacks/spring-boot/) | `spring` | Micrometer + Spring Boot Actuator |
| [Express (Node)](https://docs.getlumin.dev/stacks/express/) | `express` | `prom-client` |
| [FastAPI (Python)](https://docs.getlumin.dev/stacks/fastapi/) | `fastapi` | `prometheus-fastapi-instrumentator` |
| [Axum (Rust)](https://docs.getlumin.dev/stacks/axum/) | `axum` | `axum-prometheus` |
| [React Native SDK](https://docs.getlumin.dev/stacks/react-native/) | n/a (event ingest, not metric scrape) | `@lumin-monitor/react-native` |

## What if my framework isn't listed?

Two paths:

1. **You only need logs.** Label the container with `lumin.logs=true` and ignore metrics. The agent tails stdout/stderr regardless of framework; if your logger writes JSON with a `level` field, Lumin parses it as structured.

2. **You want metrics too.** Expose Prometheus exposition on a known path and port, label with `lumin.metrics=true` and `lumin.metrics.port=…`, but **omit `lumin.metrics.framework`**. The agent ingests metric names verbatim. The canned UI panels won't light up, but custom dashboards work and the metrics are queryable.

3. **You want the canned panels on an unlisted framework.** Emit the three canonical names from the table above yourself, then omit `lumin.metrics.framework`. The generic pass-through preserves names it does not recognise, so `http.requests`, `http.latency_ms`, and `http.errors` reach the store intact and every panel lights up. This is more work than a framework label, and it is the only path that does not need a new adapter.

**An unrecognised `lumin.metrics.framework` value is not an error.** The agent falls back to the generic pass-through and keeps shipping metrics under their original names. Nothing warns you; the symptom is a Metrics page whose KPI cards stay on their empty state. Check the spelling against the table above if that happens.

Open an issue if your framework is common enough to warrant first-class support — adding a new value to `lumin.metrics.framework` is one mapping table.
