# `LatticeStripe.Telemetry`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v1.1.0/lib/lattice_stripe/telemetry.ex#L1)

Telemetry integration for LatticeStripe.

LatticeStripe emits [`:telemetry`](https://hexdocs.pm/telemetry) events for all
HTTP requests and webhook signature verification. Attach handlers to these events
to integrate with your observability stack (Prometheus, DataDog, OpenTelemetry, etc.).

## HTTP Request Events

### `[:lattice_stripe, :request, :start]`

Emitted before each HTTP request is dispatched.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:system_time` | `integer` | System time at span start (in native time units). See `System.system_time/0`. |
| `:monotonic_time` | `integer` | Monotonic time at span start. See `System.monotonic_time/0`. |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:method` | `atom` | HTTP method atom: `:get`, `:post`, `:delete` |
| `:path` | `String.t()` | Request path, e.g. `"/v1/customers"` |
| `:resource` | `String.t()` | Parsed resource name, e.g. `"customer"`, `"payment_intent"`, `"checkout.session"` |
| `:operation` | `String.t()` | Parsed operation name, e.g. `"create"`, `"retrieve"`, `"list"`, `"confirm"` |
| `:api_version` | `String.t()` | Stripe API version, e.g. `"2026-03-25.dahlia"` |
| `:stripe_account` | `String.t() \| nil` | Connected account ID from Stripe-Account header, or `nil` |
| `:telemetry_span_context` | `reference` | Auto-injected by `:telemetry.span/3` for correlating start/stop/exception events |

---

### `[:lattice_stripe, :request, :stop]`

Emitted after each HTTP request completes (success or API error).

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:duration` | `integer` | Elapsed time in native time units. Convert via `System.convert_time_unit/3`. |
| `:monotonic_time` | `integer` | Monotonic time at span stop. |

**Metadata (all start fields plus):**

| Key | Type | Description |
|-----|------|-------------|
| `:method` | `atom` | HTTP method atom |
| `:path` | `String.t()` | Request path |
| `:resource` | `String.t()` | Parsed resource name |
| `:operation` | `String.t()` | Parsed operation name |
| `:api_version` | `String.t()` | Stripe API version |
| `:stripe_account` | `String.t() \| nil` | Connected account ID or `nil` |
| `:status` | `:ok \| :error` | Outcome of the request |
| `:http_status` | `integer \| nil` | HTTP status code (nil for connection errors) |
| `:request_id` | `String.t() \| nil` | Stripe `request-id` header value |
| `:attempts` | `integer` | Total attempts made (1 = no retries, 2 = one retry, etc.) |
| `:retries` | `integer` | Number of retries (attempts - 1) |
| `:error_type` | `atom \| nil` | Error type atom on failure: `:connection_error`, `:card_error`, etc. |
| `:idempotency_key` | `String.t() \| nil` | Idempotency key used (on error only) |
| `:telemetry_span_context` | `reference` | Correlates with start event |

---

### `[:lattice_stripe, :request, :exception]`

Emitted when an uncaught exception or throw escapes the request function. This covers
transport-level bugs, not API errors (which produce `[:lattice_stripe, :request, :stop]`
with `status: :error`). Handled automatically by `:telemetry.span/3`.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:duration` | `integer` | Elapsed time in native time units |
| `:monotonic_time` | `integer` | Monotonic time at exception |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:method` | `atom` | HTTP method atom |
| `:path` | `String.t()` | Request path |
| `:resource` | `String.t()` | Parsed resource name |
| `:operation` | `String.t()` | Parsed operation name |
| `:api_version` | `String.t()` | Stripe API version |
| `:stripe_account` | `String.t() \| nil` | Connected account ID or `nil` |
| `:kind` | `:error \| :exit \| :throw` | Exception kind |
| `:reason` | `any` | Exception reason |
| `:stacktrace` | `list` | Exception stacktrace |
| `:telemetry_span_context` | `reference` | Correlates with start event |

---

### `[:lattice_stripe, :request, :retry]`

Emitted for each retry attempt before the retry delay sleep.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:attempt` | `integer` | Retry attempt number (1 = first retry after initial failure) |
| `:delay_ms` | `integer` | Delay in milliseconds before the retry |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:method` | `atom` | HTTP method atom |
| `:path` | `String.t()` | Request path |
| `:error_type` | `atom` | Error type that triggered the retry |
| `:status` | `integer \| nil` | HTTP status code (nil for connection errors) |

---

## Webhook Verification Events

### `[:lattice_stripe, :webhook, :verify, :start]`

Emitted before webhook signature verification begins.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:system_time` | `integer` | System time at span start |
| `:monotonic_time` | `integer` | Monotonic time at span start |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:path` | `String.t() \| nil` | Request path where webhook was received, if available |
| `:telemetry_span_context` | `reference` | Auto-injected for span correlation |

---

### `[:lattice_stripe, :webhook, :verify, :stop]`

Emitted after webhook signature verification completes.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:duration` | `integer` | Elapsed time in native time units |
| `:monotonic_time` | `integer` | Monotonic time at span stop |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:path` | `String.t() \| nil` | Request path where webhook was received |
| `:result` | `:ok \| :error` | Verification outcome |
| `:error_reason` | `atom \| nil` | Failure reason: `:invalid_signature`, `:stale_timestamp`, `:missing_header`, `:no_valid_signature`, or `nil` on success |
| `:telemetry_span_context` | `reference` | Correlates with start event |

---

### `[:lattice_stripe, :webhook, :verify, :exception]`

Emitted when an uncaught exception escapes webhook verification. Handled automatically by `:telemetry.span/3`.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:duration` | `integer` | Elapsed time in native time units |
| `:monotonic_time` | `integer` | Monotonic time at exception |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:path` | `String.t() \| nil` | Request path |
| `:kind` | `:error \| :exit \| :throw` | Exception kind |
| `:reason` | `any` | Exception reason |
| `:stacktrace` | `list` | Exception stacktrace |
| `:telemetry_span_context` | `reference` | Correlates with start event |

---

## Usage with `Telemetry.Metrics`

If you're using `telemetry_metrics` with Prometheus or StatsD, here are ready-to-use metric
definitions:

```elixir
[
  # Request latency by resource and operation
  Telemetry.Metrics.summary("lattice_stripe.request.stop.duration",
    tags: [:resource, :operation, :status],
    unit: {:native, :millisecond}
  ),

  # Request throughput by outcome
  Telemetry.Metrics.counter("lattice_stripe.request.stop",
    tags: [:resource, :operation, :status]
  ),

  # Latency distribution (p50/p95/p99)
  Telemetry.Metrics.distribution("lattice_stripe.request.stop.duration",
    tags: [:resource, :operation],
    unit: {:native, :millisecond}
  ),

  # Retry rate by error type
  Telemetry.Metrics.counter("lattice_stripe.request.retry",
    tags: [:error_type]
  ),

  # Webhook verification outcomes
  Telemetry.Metrics.counter("lattice_stripe.webhook.verify.stop",
    tags: [:result, :error_reason]
  )
]
```

## Invoice Auto-Advance Events

### `[:lattice_stripe, :invoice, :auto_advance_scheduled]`

Emitted after a successful `Invoice.create/3` when the returned invoice has
`auto_advance: true`. This signals that Stripe will automatically finalize the
draft invoice after approximately 1 hour. Attach a handler to log a warning or
trigger a monitoring alert when auto-advance invoices are created.

**Measurements:**

| Key | Type | Description |
|-----|------|-------------|
| `:system_time` | `integer` | System time at emission (in native time units). See `System.system_time/0`. |

**Metadata:**

| Key | Type | Description |
|-----|------|-------------|
| `:invoice_id` | `String.t()` | The created invoice ID (e.g. `"in_123"`) |
| `:customer` | `String.t() \| nil` | Customer ID associated with the invoice, or `nil` |

---

## Attaching a Default Logger

For instant visibility during development or to log all Stripe requests in production,
use `attach_default_logger/1`:

```elixir
# In your application start/2:
LatticeStripe.Telemetry.attach_default_logger()

# Or with options:
LatticeStripe.Telemetry.attach_default_logger(level: :debug)
```

This logs one line per request in the format:

```
[info] POST /v1/customers => 200 in 145ms (1 attempt, req_abc123)
[warn] GET /v1/payment_intents/pi_123 => :error in 301ms (3 attempts, connection_error)
[warning] Invoice in_123 (customer: cus_456) has auto_advance: true — Stripe will auto-finalize in ~1 hour
```

## Converting Duration

The `:duration` measurement is in native time units. Convert to milliseconds:

```elixir
duration_ms = System.convert_time_unit(duration, :native, :millisecond)
```

# `attach_default_logger`

```elixir
@spec attach_default_logger(keyword()) :: :ok
```

Attaches a default structured logger for all LatticeStripe request events.

Safe to call multiple times -- detaches any existing handler with the same ID first.

## Options
  * `:level` -- log level (default: `:info`)

## Example

    LatticeStripe.Telemetry.attach_default_logger(level: :info)

Logs one line per completed request:

    [info] POST /v1/customers => 200 in 145ms (1 attempt, req_abc123)
    [warning] GET /v1/customers/cus_xxx => 404 in 12ms (1 attempt, req_yyy)

Also logs a warning when an invoice is created with `auto_advance: true`:

    [warning] Invoice in_123 (customer: cus_456) has auto_advance: true — Stripe will auto-finalize in ~1 hour

---

*Consult [api-reference.md](api-reference.md) for complete listing*
