# Send OpenTelemetry traces and logs

> Point any OpenTelemetry SDK at https://app.lampwick.dev over OTLP/HTTP with your ingest token. Server spans become requests, errors become issues.

Lampwick takes OpenTelemetry traces and logs over OTLP/HTTP. Any app with an OpenTelemetry SDK can send them by setting a few environment variables, with no Lampwick package. We tested these setups end to end:

| Stack | Setup | Requests | Errors → issues | Queries | Logs |
|---|---|---|---|---|---|
| [Node.js (Express)](/docs/node/) | Zero-code: `--require` the auto-instrumentations | yes | yes, with stack trace | yes (`pg`) | via pino, winston or bunyan |
| [Next.js](/docs/nextjs/) | `instrumentation.ts` with `@vercel/otel` | yes | yes, stack trace points into `.next/` | with `PgInstrumentation` | not captured |
| [Python (FastAPI, Django)](/docs/python/) | Zero-code: `opentelemetry-instrument` | yes | yes, with stack trace | yes (`sqlite3`, Django ORM) | yes, `logging` |

Go, Ruby, .NET and Java should work the same way, because the endpoint only speaks the protocol, but we haven't tested them yet. Ruby's SDK needs Ruby 3.3 or later.

## Requirements

- The project's ingest token, from the **Connect your app** card on the project's **Settings** page. Only team owners see it.
- An OpenTelemetry SDK that exports OTLP over HTTP. Lampwick doesn't accept gRPC.

## Set the environment variables

Set these wherever your app's environment lives (`.env`, your process manager, your host's dashboard):

```dotenv
OTEL_EXPORTER_OTLP_ENDPOINT=https://app.lampwick.dev
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=x-lampwick-token=YOUR_PROJECT_TOKEN
OTEL_SERVICE_NAME=my-app
OTEL_METRICS_EXPORTER=none
OTEL_LOGS_EXPORTER=otlp
```

`YOUR_PROJECT_TOKEN` is the project's ingest token. The SDK adds `/v1/traces` and `/v1/logs` to the endpoint itself. `Authorization=Bearer%20YOUR_PROJECT_TOKEN` works as the header too.

`OTEL_METRICS_EXPORTER=none` matters: Lampwick doesn't take metrics, and without it most SDKs try to send them every minute and log an error each time.

## What shows up where

| OpenTelemetry | In Lampwick |
|---|---|
| Server span (`SpanKind.SERVER`) | **Requests**, one row per method and `http.route` |
| Database span (`db.system`) | **Queries**, one row per statement; linked to the request it ran in |
| Client span with an HTTP method | **Outgoing requests**, one row per host |
| Consumer / producer span | **Jobs** / **Queued jobs** |
| Root internal span | **Commands** |
| `exception` span event | **Issues**, grouped by type and the top 3 frames of your own code |
| Log record | **Logs**, linked to the request it was written in |
| Log record with `exception.*` attributes | **Logs** and **Issues** (handled) |
| `enduser.id` or `user.id` attribute | **Users** |

Lampwick doesn't store middleware, router and other internal child spans, and doesn't count them as events. Connection spans such as `pg.connect` aren't queries and are left out too.

An error is one occurrence per trace. SDKs often record the same error on several spans (Express 5 records it 3 times), and frameworks also log it (Django's `django.request` logger), so Lampwick keeps the first copy. The issue counts as unhandled when any copy came from a failed span.

## Check that it works

1. Start your app with the variables set and open a few pages.
2. Spans arrive within about 5 seconds: the SDKs export in batches. Open **Requests** in the project.
3. Lampwick answers each export with `200`. A `401` means the token is wrong, a `415` that the SDK sends gRPC or another content type.

## Limits

- Protobuf (`application/x-protobuf`) or JSON (`application/json`), optionally gzip-compressed.
- No gRPC and no metrics: `/v1/metrics` answers `404`.
- Spans and log records count as events like everything else; spans Lampwick doesn't store don't count.
- Stack traces are parsed from `exception.stacktrace` for Node, Python, JVM, .NET, Go, Ruby and PHP. Source maps aren't applied, so bundled code shows bundle file names.

## Troubleshooting

| What you see | Cause and fix |
|---|---|
| Nothing arrives, and the app logs `401` | The token in `OTEL_EXPORTER_OTLP_HEADERS` is wrong or has spaces around it. Copy it again from **Settings**. |
| Nothing arrives, and the app logs `415` or a connection error on port 4317 | The SDK uses gRPC. Set `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`. |
| The app logs metric export errors | Set `OTEL_METRICS_EXPORTER=none`. |
| Requests show up, logs don't | The language's logging bridge isn't on; see the page for your stack. |
| Requests show the URL but no route | The framework instrumentation isn't loaded, so only the HTTP server span exists without `http.route`. |
