Sits between a React Native OpenTelemetry client and OpenObserve: ingests OTLP/JSON, symbolicates minified JS stack traces using uploaded source maps, and forwards the enriched telemetry downstream.
[React Native App] --OTLP/JSON--> [Bun Middleware] --OTLP/JSON--> [OpenObserve]
^
(source maps) |
[CI/CD Pipeline]
- On release, CI calls
POST /api/upload-url(OIDC-authenticated) for a presigned URL, then PUTs the Metro source map straight to object storage as[app]/[platform]/[version].map. The map bytes never transit the middleware. - The RN app points its OTLP exporter here instead of OpenObserve, attaching
service.name,app.version, andos.platformresource attributes plus its own OpenObserveAuthorizationheader. - On ingest, the service loads the matching source map (LRU-cached), rewrites
each
exception.stacktraceframe to original source coordinates, and forwards the payload — passing the client'sAuthorizationheader straight through.
Telemetry is never dropped: if a map is missing or symbolication fails, the original payload is forwarded unchanged.
- Upload-URL requests use asymmetric OIDC/JWT: the middleware verifies the
bearer token against the issuer's JWKS and checks audience + repository/owner
allowlists before returning a short-lived presigned PUT URL. Nothing secret is
stored server-side. At least one allowlist is required or the service refuses
to start. Defaults target GitHub Actions; override
OIDC_*for any issuer. - Ingestion forwards the client's own
Authorizationheader downstream, so the middleware never holds OpenObserve credentials.
bun install
cp .env.example .env # then edit values
bun run dev # hot-reload, or: bun run start| Env var | Purpose |
|---|---|
PORT |
Listen port (default 4318). |
CACHE_MAX |
Max parsed maps kept in memory (default 32). |
CACHE_TTL |
Cached map lifetime before re-read, seconds (default 300). |
UPLOAD_URL_TTL |
Presigned upload URL lifetime, seconds (default 300). |
S3_BUCKET |
Source-map bucket. Required — the service is stateless. |
S3_PREFIX |
Key prefix (default maps/). |
S3_REGION / S3_ENDPOINT |
Region and (for MinIO/R2/Spaces) custom endpoint. |
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY |
S3 credentials (fall back to AWS env). |
OIDC_ISSUER |
Token issuer (default GitHub Actions). |
OIDC_JWKS_URI |
JWKS URL (default ${issuer}/.well-known/jwks). |
OIDC_AUDIENCE |
Expected aud claim (default rn-otel-map-upload). |
OIDC_ALLOWED_REPOSITORIES / OIDC_ALLOWED_OWNERS / OIDC_ALLOWED_SUBJECTS |
Upload allowlists (comma-sep). At least one required. |
OPENOBSERVE_URL |
Downstream base URL, e.g. .../api/default. Required. |
POST /api/upload-url(CI/CD only) — OIDC-authenticated. Headersx-app-name,x-app-version,x-app-platform(ios/android). Returns a presigned PUT URL; CI then PUTs the.mapbytes directly to it. From GitHub Actions, use the bundled.github/actions/upload-mapaction (see.github/workflows/upload-map.example.yml).POST /v1/logs·POST /v1/traces— public OTLP/JSON ingestion. Returns202with{ "symbolicated": <n> }.GET /health— liveness probe.
bun test
docker build -t rn-otel -f Containerfile .
docker run -p 4318:4318 --env-file .env rn-otelPushes to main build and publish a multi-arch (amd64/arm64) image to ghcr.io
via .github/workflows/trunk.yml.