# `Tinkex.Telemetry.Reporter`
[🔗](https://github.com/North-Shore-AI/tinkex/blob/v0.4.0/lib/tinkex/telemetry/reporter.ex#L1)

Client-side telemetry reporter that batches events and ships them to the Tinker
backend via `/api/v1/telemetry`.

A reporter is scoped to a single Tinker session. It:

  * Emits session start/end events.
  * Accepts generic events and exceptions via `log/4`, `log_exception/3`, and
    `log_fatal_exception/3`.
  * Optionally listens to `:telemetry` events and forwards ones that include
    matching `:session_id` metadata.
  * Batches up to 100 events per request, flushing periodically and whenever
    the queue crosses the configured threshold.
  * Retries failed sends with exponential backoff (up to 3 retries).
  * Supports wait-until-drained semantics for graceful shutdown.

Telemetry can be disabled by setting `TINKER_TELEMETRY=0|false|no`.

## Typed Events

The reporter now uses typed structs for all telemetry events. See
`Tinkex.Types.Telemetry` for available types:

  * `Tinkex.Types.Telemetry.GenericEvent` - custom application events
  * `Tinkex.Types.Telemetry.SessionStartEvent` - session start marker
  * `Tinkex.Types.Telemetry.SessionEndEvent` - session end marker
  * `Tinkex.Types.Telemetry.UnhandledExceptionEvent` - exception reports

# `severity`

```elixir
@type severity() :: :debug | :info | :warning | :error | :critical | String.t()
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `find_user_error_in_chain`

```elixir
@spec find_user_error_in_chain(term(), map()) :: {:ok, term()} | :not_found
```

Traverse the exception cause chain to find a user error.

Checks exception fields in order: :cause, :reason, :plug_status (4xx except 408/429),
:__cause__, :__context__. Depth-first, first match wins.

# `flush`

```elixir
@spec flush(
  pid() | nil,
  keyword()
) :: :ok | boolean()
```

Flush pending events.

Options:
  * `:sync?` - when true, blocks until all batches are sent (default: false)
  * `:wait_drained?` - when true with sync?, waits until push_counter == flush_counter

# `log`

```elixir
@spec log(pid() | nil, String.t(), map(), severity()) :: boolean()
```

Log a generic telemetry event.

# `log_exception`

```elixir
@spec log_exception(pid() | nil, Exception.t(), severity()) :: boolean()
```

Log an exception (non-fatal) and trigger an async flush.

# `log_fatal_exception`

```elixir
@spec log_fatal_exception(pid() | nil, Exception.t(), severity()) :: boolean()
```

Log a fatal exception, emit a session end event, and flush synchronously.

Waits until all queued events are flushed (up to flush_timeout_ms) and logs
a notification with the session ID for debugging purposes.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start() | :ignore
```

Start a reporter for the provided session/config.

Options:
  * `:config` (**required**) - `Tinkex.Config.t()`
  * `:session_id` (**required**) - Tinker session id
  * `:handler_id` - telemetry handler id (auto-generated)
  * `:events` - telemetry events to capture (default: HTTP + queue state)
  * `:attach_events?` - whether to attach telemetry handlers (default: true)
  * `:flush_interval_ms` - periodic flush interval (default: 10s)
  * `:flush_threshold` - flush when queue reaches this size (default: 100)
  * `:flush_timeout_ms` - max wait time for drain operations (default: 30s)
  * `:http_timeout_ms` - HTTP request timeout (default: 5s)
  * `:max_retries` - max retries per batch (default: 3)
  * `:retry_base_delay_ms` - base delay for exponential backoff (default: 1s)
  * `:max_queue_size` - drop events beyond this size (default: 10_000)
  * `:max_batch_size` - events per POST (default: 100)
  * `:enabled` - override env flag; when false returns `:ignore`

# `stop`

```elixir
@spec stop(pid() | nil, timeout()) :: :ok | boolean()
```

Stop the reporter gracefully.

Emits a session end event (if not already emitted) and flushes all pending
events synchronously before stopping the GenServer.

# `wait_until_drained`

```elixir
@spec wait_until_drained(pid() | nil, timeout()) :: boolean()
```

Wait until all queued events have been flushed.

Returns `true` if drained within timeout, `false` otherwise.

---

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