# `Chimeway.Telemetry`
[🔗](https://github.com/jonlunsford/chimeway/blob/v1.0.0/lib/chimeway/telemetry.ex#L1)

Instrumentation façade for Chimeway telemetry spans.

All lifecycle telemetry in Chimeway routes through this module so that
event naming is consistent and metadata is redacted before emission.

## Event Catalog

Chimeway emits the following `:telemetry` span events. Each span produces
three events: `[:..., :start]`, `[:..., :stop]`, and `[:..., :exception]`.

| Span | Event name | Metadata keys |
|------|-----------|--------------|
| Event creation | `[:chimeway, :events, :create]` | `notification_key`, `event_id`, `correlation_id` |
| Delivery planning | `[:chimeway, :deliveries, :plan]` | `notification_key`, `event_id`, `recipient_id` |
| Policy evaluation | `[:chimeway, :policy, :evaluate]` | `delivery_id`, `notification_key`, `channel` |
| Sync dispatch | `[:chimeway, :dispatch, :sync]` | `delivery_id`, `notification_key`, `channel` |
| Oban enqueue *(optional)* | `[:chimeway, :dispatch, :enqueue]` | `delivery_id`, `notification_key` |
| Oban perform *(optional)* | `[:chimeway, :dispatch, :perform]` | `delivery_id`, `notification_key` |
| Attempt record | `[:chimeway, :attempts, :record]` | `attempt_id`, `delivery_id`, `outcome` |

The two Oban spans are only emitted when Oban is present in the dependency tree.

## PII Redaction

`safe_meta/1` strips any key not in the allowed set before metadata is passed
to `:telemetry.span/3`. This prevents sensitive payload fields (email addresses,
template content, provider API responses) from appearing in telemetry handlers.

**Allowed keys:** `notification_key`, `event_id`, `recipient_id`, `channel`,
`delivery_id`, `attempt_id`, `outcome`, `suppression_reason`, `correlation_id`,
`attempt_number`, `error_class`, `adapter_module`

All metadata that call sites pass to `span/3` must be pre-filtered:

    Chimeway.Telemetry.span([:events, :create], safe_meta(%{
      notification_key: key,
      event_id: id,
      correlation_id: cid
    }), fn ->
      result = do_create_event()
      {result, %{event_id: id}}
    end)

## Attaching Handlers

Chimeway does NOT auto-attach handlers at library startup. Call
`attach_default_handlers/0` explicitly in your application's `start/2`:

    def start(_type, _args) do
      Chimeway.Telemetry.attach_default_handlers()
      # ...
    end

To attach a custom handler:

    :telemetry.attach_many(
      :my_handler,
      [
        [:chimeway, :events, :create, :stop],
        [:chimeway, :dispatch, :sync, :stop]
      ],
      &MyApp.TelemetryHandler.handle/4,
      nil
    )

## Example: Forwarding to StatsD / Datadog

    defmodule MyApp.ChimewayStatsD do
      def handle([:chimeway, :dispatch, :sync, :stop], measurements, meta, _config) do
        tags = ["channel:#{meta.channel}", "key:#{meta.notification_key}"]
        Statix.timing("chimeway.dispatch.sync", measurements.duration, tags: tags)
      end
    end

# `attach_default_handlers`

```elixir
@spec attach_default_handlers() :: :ok
```

Attaches a Logger-based default telemetry handler for all Chimeway spans.

Logs `:stop` events at `:debug` level and `:exception` events at `:warning` level.
Idempotent — calling this function multiple times does not crash or double-attach.

**Must be called explicitly** by the host application. Chimeway does not
auto-attach handlers at startup. A typical place is `Application.start/2`:

    def start(_type, _args) do
      Chimeway.Telemetry.attach_default_handlers()
      children = [...]
      Supervisor.start_link(children, strategy: :one_for_one)
    end

# `safe_meta`

```elixir
@spec safe_meta(map()) :: map()
```

Filters a metadata map to the allowed telemetry keys only.

Drops any key not in the allowed set. Normalizes both atom and string keys
to atoms before filtering. This is the single enforcement point for PII
redaction in Chimeway telemetry.

## Allowed keys

`notification_key`, `event_id`, `recipient_id`, `channel`, `delivery_id`, `attempt_id`, `outcome`, `suppression_reason`, `correlation_id`, `attempt_number`, `error_class`, `adapter_module`

## Example

    iex> Chimeway.Telemetry.safe_meta(%{notification_key: "order_shipped", email: "user@example.com"})
    %{notification_key: "order_shipped"}

# `span`

```elixir
@spec span(list(), map(), (-&gt; {term(), map()})) :: term()
```

Wraps a function with a `:telemetry` span using Chimeway's 4-level event prefix.

`event_suffix` is appended to `[:chimeway]`, e.g. `[:events, :create]`
produces `[:chimeway, :events, :create, :start/stop/exception]`.

The function `func` must return `{result, extra_stop_meta}` where
`extra_stop_meta` is merged into the stop event metadata by `:telemetry.span/3`.
Pass through `safe_meta/1` on any extra stop metadata to prevent PII leakage
from the stop event.

Returns `result`.

---

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