Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/autumn-js-pre-1-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@api-blitz/otel-autumn": minor
---

Add `autumn-js` pre-1.0 compatibility.

- Widen `autumn-js` peer range to `>=0.0.70 <2.0.0`.
- Wrap pre-1.0 flat top-level methods (`attach`, `cancel`, `setupPayment`, `usage`) alongside the existing 1.x sub-resource coverage; methods missing from the installed SDK are skipped silently.
- Unwrap the pre-1.0 `Result<T, E>` response envelope so response-side span attributes populate the same on both versions.
- Map `product_id`/`product_ids` to `autumn.plan_id`/`autumn.plan_ids` for dashboard consistency across versions.
67 changes: 67 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Examples — visualize traces locally with Jaeger UI

Each instrumentation package ships an `examples/` playground that replays a realistic workload through the instrumentation and exports the resulting spans to a local Jaeger via OTLP/HTTP. Use it to sanity-check that the traces look right in an actual observability UI — span tree, timings, kind, and attributes — without standing up a real Autumn/Drizzle environment.

## Prerequisites

- Docker (Jaeger runs in a container)
- pnpm + Node ≥ 18.19 / 20.6 (same as the packages themselves)

## 1. Start Jaeger

From the repo root:

```bash
docker compose up -d
```

This launches `jaegertracing/all-in-one` with OTLP enabled, exposing:

- `http://localhost:16686` — Jaeger UI
- `http://localhost:4318` — OTLP HTTP endpoint (what the demos push to)
- `http://localhost:4317` — OTLP gRPC endpoint

## 2. Install workspace dependencies (first time only)

```bash
pnpm install
```

## 3. Run a demo

Pick the package you want to exercise. Each demo uses **plain-object mocks** of the underlying SDK — no API keys, no running Postgres.

```bash
# Autumn — replays all 36 instrumented methods + 1 error path (37 spans total)
pnpm --filter @api-blitz/otel-autumn example

# Drizzle — replays every instrumentation surface (30 spans: all SQL verbs,
# query-object shapes, transactions, callback pattern, error path, truncation)
pnpm --filter @api-blitz/otel-drizzle example
```

You can run both back-to-back; each writes to a different service name so the traces don't collide.

## 4. Inspect traces

Open <http://localhost:16686>, then in the **Service** dropdown pick:

- `otel-autumn-demo` — every `autumn.*` operation
- `otel-drizzle-demo` — every `drizzle.*` operation

Click **Find Traces** and drill into any trace to see its attributes. Each per-package `examples/README.md` has a full table of expected span names and attributes so you can cross-reference what you're seeing:

- [otel-autumn example](./packages/otel-autumn/examples/README.md)
- [otel-drizzle example](./packages/otel-drizzle/examples/README.md)

## 5. Stop Jaeger

```bash
docker compose down
```

## Troubleshooting

- **`ECONNREFUSED` from the demo** — Jaeger isn't up, or `:4318` is being used by something else. Run `docker compose up -d` first.
- **Pointing at a different OTLP collector** — set `OTEL_EXPORTER_OTLP_ENDPOINT`, e.g. `OTEL_EXPORTER_OTLP_ENDPOINT=http://my-collector:4318 pnpm --filter @api-blitz/otel-autumn example`.
- **Traces stick around between runs** — Jaeger's in-memory storage retains traces until the container restarts. `docker compose restart` clears them.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ Versions follow semver. Pin with `^1.0.0` (caret) to get patch and minor updates

---

## Examples

Want to see what the traces actually look like before you install? Each package ships a playground that replays a realistic workload through the instrumentation and exports the resulting spans to a local Jaeger UI — no API keys, no database required. See **[EXAMPLES.md](./EXAMPLES.md)**.

---

## Releasing (maintainers)

Releases go out by hand from a local checkout. Per change:
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
jaeger:
image: jaegertracing/all-in-one:1.62.0
container_name: otel-davis-jaeger
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686"
- "4317:4317"
- "4318:4318"
11 changes: 10 additions & 1 deletion packages/otel-autumn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ yarn add @api-blitz/otel-autumn
bun add @api-blitz/otel-autumn
```

**Peer dependencies:** `@opentelemetry/api` >= 1.9.0, `autumn-js` >= 1.0.0 < 2.0.0
**Peer dependencies:** `@opentelemetry/api` >= 1.9.0, `autumn-js` >= 0.0.70 < 2.0.0

## Quick start

Expand All @@ -37,6 +37,15 @@ await autumn.billing.attach({ customerId: "cus_123", planId: "pro" });

`instrumentAutumn` wraps the Autumn client instance you already use — no configuration changes needed. Every SDK call creates a `CLIENT` span with operation-specific attributes, and the same client instance is returned so instrumentation is idempotent (calling it twice is a no-op).

## Version compatibility

Works across `autumn-js` from the last pre-1.0 releases (`>= 0.0.70`) through the current 1.x line.

- **1.x** — full 36-method coverage across `check`, `track`, and every `billing.*` / `customers.*` / `entities.*` / `balances.*` / `events.*` / `plans.*` / `features.*` / `referrals.*` sub-resource.
- **Pre-1.0 (0.0.70 – 0.0.80)** — `check` and `track`, plus the flat top-level billing methods that existed before the 1.x rename: `attach`, `cancel`, `setupPayment`, `usage`. Pre-1.0's `Result<T, E>` response envelope is unwrapped automatically so response-side span attributes are populated the same way as on 1.x. Pre-1.0's `product_id` / `product_ids` are surfaced under the `autumn.plan_id` / `autumn.plan_ids` attribute so dashboards stay consistent across versions.

Methods that don't exist on the installed SDK version are skipped silently — instrumenting a pre-1.0 client doesn't fail because `autumn.billing` / `autumn.plans` aren't present.

## What gets traced

The instrumentation wraps every method on the Autumn SDK client — 2 top-level entry points plus 34 sub-resource operations across 8 namespaces.
Expand Down
103 changes: 103 additions & 0 deletions packages/otel-autumn/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# otel-autumn trace playground

Replays **every** instrumented Autumn method through `instrumentAutumn` and exports the spans via OTLP/HTTP to a local Jaeger. Use this to visually verify that the instrumentation covers the full SDK surface with the expected span names and attributes.

## Run it

From the repo root:

```bash
docker compose up -d # starts Jaeger on :16686 and :4318
pnpm --filter @api-blitz/otel-autumn install # first time only
pnpm --filter @api-blitz/otel-autumn example
```

Then open <http://localhost:16686>, pick **Service** `otel-autumn-demo`, and click **Find Traces**.

When finished:

```bash
docker compose down
```

## What you should see

**37 spans total**: 36 successful `CLIENT`-kind spans covering every method on the 1.x client surface, plus one `autumn.check` span with `otel.status_code=ERROR`.

The demo runs with `captureCustomerData: true` so `autumn.payment_url` and `autumn.portal_url` are populated (they're redacted by default — the unit tests cover the redacted case).

### Top-level (2 spans)

| Span | Key attributes |
|---|---|
| `autumn.check` | `autumn.allowed=true`, `autumn.balance=42`, `autumn.feature_id=messages`, `autumn.plan_id=pro`, `autumn.flag_id=flag_demo`, `autumn.has_preview=true` |
| `autumn.track` | `autumn.event_name=message_sent`, `autumn.value=1`, `autumn.balance=41`, `autumn.balance_count=2` |

### billing (8 spans)

| Span | Key attributes |
|---|---|
| `autumn.billing.attach` | `autumn.plan_id=pro`, `autumn.plan_version=2`, `autumn.invoice_mode=true`, `autumn.feature_quantities_count=2`, `autumn.discount_count=1`, `autumn.has_payment_url=true`, `autumn.invoice_id=in_demo`, `autumn.currency=usd`, `autumn.total_amount=2000` |
| `autumn.billing.multiAttach` | `autumn.plan_ids=pro,addon_seats`, `autumn.plan_count=2` |
| `autumn.billing.previewAttach` | `autumn.total_amount=2000`, `autumn.has_prorations=true` |
| `autumn.billing.previewMultiAttach` | `autumn.total_amount=5000`, `autumn.has_prorations=false` |
| `autumn.billing.update` | `autumn.cancel_action=cancel_end_of_cycle`, `autumn.proration_behavior=none`, `autumn.plan_version=3` |
| `autumn.billing.previewUpdate` | `autumn.total_amount=1000`, `autumn.has_prorations=true` |
| `autumn.billing.openCustomerPortal` | `autumn.has_portal_url=true`, `autumn.portal_url=https://billing.stripe.com/...` |
| `autumn.billing.setupPayment` | `autumn.has_payment_url=true`, `autumn.payment_url=https://checkout.stripe.com/setup/...` |

### customers (4 spans)

`autumn.customers.getOrCreate`, `.list`, `.update`, `.delete` — each carries `autumn.customer_id=cus_demo`.

### entities (4 spans)

`autumn.entities.create`, `.get`, `.update`, `.delete` — each carries `autumn.entity_id=seat_demo` and `autumn.entity_feature_id=seats`.

### balances (4 spans)

`autumn.balances.create`, `.update`, `.delete`, `.finalize` — each carries `autumn.feature_id=messages` and `autumn.balance` (the remaining value from the response).

### events (2 spans)

| Span | Key attributes |
|---|---|
| `autumn.events.list` | `autumn.event_count=3`, `autumn.has_more=false` |
| `autumn.events.aggregate` | `autumn.aggregate_range=7d`, `autumn.feature_count=2`, `autumn.period_count=2`, `autumn.event_count=7` (sum), `autumn.value=1578` (sum) |

### plans (5 spans)

`autumn.plans.create`, `.get`, `.list`, `.update`, `.delete` — each non-`list` span carries `autumn.plan_id=pro` and `autumn.plan_name`.

### features (5 spans)

`autumn.features.create`, `.get`, `.list`, `.update`, `.delete` — each non-`list` span carries `autumn.feature_id=messages`, `autumn.feature_name`, `autumn.feature_type=metered`.

### referrals (2 spans)

`autumn.referrals.createCode`, `autumn.referrals.redeemCode` — each carries `autumn.referral_code=REF123` and `autumn.referral_program_id=prog_demo`.

### Error path (1 span)

A second `autumn.check` span with `otel.status_code=ERROR`, `error=true`, and an `exception` event with message `demo: feature not found`.

## Quick Jaeger API checks

After running the demo, these commands validate the trace set programmatically:

```bash
# 36 unique operation names expected
curl -s 'http://localhost:16686/api/services/otel-autumn-demo/operations' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['total'])"

# 37 spans total (36 successes + 1 error)
curl -s 'http://localhost:16686/api/traces?service=otel-autumn-demo&limit=200' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(len(t['spans']) for t in d['data']))"
```

## Notes

- The demo uses a **plain-object mock** of the `autumn-js` client (same pattern as the unit tests), so no Autumn API key or network access is required.
- Pre-1.0 `autumn-js` (0.0.x) top-level methods (`attach`, `cancel`, `setupPayment`, `usage`) are instrumented but only wrap if the method exists on the client — they're absent from a 1.x shape, so the demo doesn't exercise them. The unit tests cover that compatibility surface.
- Spans are pushed via `BatchSpanProcessor`; the demo explicitly calls `forceFlush()` and `shutdown()` before exit so nothing is dropped.
- OTLP endpoint defaults to `http://localhost:4318`; override with `OTEL_EXPORTER_OTLP_ENDPOINT` if Jaeger runs elsewhere.
Loading
Loading