# `Firkin.Telemetry`
[🔗](https://harton.dev/james/firkin)

Telemetry events emitted by Firkin.

Firkin emits a single span per HTTP request routed through `Firkin.Plug`.
Attach handlers in your application start callback using
`&Module.function/4` captures — not anonymous functions — so the runtime
can hot-reload your code.

    :telemetry.attach_many(
      "my-firkin-handler",
      [
        [:firkin, :request, :start],
        [:firkin, :request, :stop],
        [:firkin, :request, :exception]
      ],
      &MyApp.FirkinTelemetry.handle_event/4,
      %{}
    )

## Events

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

Emitted when a request enters `Firkin.Plug`.

  * Measurements: `%{monotonic_time, system_time}`
  * Metadata:
    * `:method` — HTTP method (e.g. `"GET"`)
    * `:request_path` — request path as seen by the plug
    * `:request_id` — value placed in the `X-Amz-Request-Id` response header
    * `:telemetry_span_context` — reference correlating start/stop/exception

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

Emitted when the request completes (including handled error responses).

  * Measurements: `%{monotonic_time, duration}` (duration in native units)
  * Metadata: all start metadata plus
    * `:operation` — one of the operation atoms listed below, or `:unknown`
    * `:bucket` — bucket name or `nil` (service-level requests)
    * `:key` — object key or `nil`
    * `:status` — HTTP status code on the response
    * `:access_key_id` — authenticated key id, or `nil` if auth failed
    * `:error_code` — S3 error code atom when the response was an error,
      otherwise `nil`

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

Emitted when an unhandled exception escapes request dispatch. Firkin
re-raises after emitting this event, letting the exception propagate to
the enclosing Plug pipeline rather than sending its own response.

  * Measurements: `%{monotonic_time, duration}`
  * Metadata: all start metadata plus the fields described for `:stop`
    (best-effort — `:operation` may be `:unknown` if the exception
    happened before classification; `:status` and `:error_code` are
    `nil` because no response was sent) and
    * `:kind` — `:error | :exit | :throw`
    * `:reason` — the raised term
    * `:stacktrace` — the captured stacktrace

## Operation atoms

The `:operation` metadata is one of:

  * `:list_buckets`
  * `:create_bucket`, `:delete_bucket`, `:head_bucket`, `:get_bucket_location`
  * `:list_objects_v2`, `:list_multipart_uploads`
  * `:get_object`, `:head_object`, `:put_object`, `:delete_object`,
    `:delete_objects`, `:copy_object`
  * `:create_multipart_upload`, `:upload_part`,
    `:complete_multipart_upload`, `:abort_multipart_upload`, `:list_parts`
  * `:unknown` — request did not match any recognised S3 operation

# `operation`

```elixir
@type operation() ::
  :list_buckets
  | :create_bucket
  | :delete_bucket
  | :head_bucket
  | :get_bucket_location
  | :list_objects_v2
  | :list_multipart_uploads
  | :get_object
  | :head_object
  | :put_object
  | :delete_object
  | :delete_objects
  | :copy_object
  | :create_multipart_upload
  | :upload_part
  | :complete_multipart_upload
  | :abort_multipart_upload
  | :list_parts
  | :unknown
```

# `classify_operation`

```elixir
@spec classify_operation(
  String.t(),
  bucket :: String.t() | nil,
  key :: String.t() | nil,
  query :: map(),
  req_headers :: [{String.t(), String.t()}]
) :: operation()
```

Classifies an S3 request into an operation atom.

Mirrors the routing in `Firkin.Plug` so the operation can be included in
telemetry metadata without threading it through every handler.

---

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