# `Otel.Metrics.Counter`
[🔗](https://github.com/yangbancode/otel/blob/main/lib/otel/metrics/counter.ex#L1)

Synchronous Counter instrument facade (OTel
`metrics/api.md` §Counter, Status: **Stable**, L497-L598).

A Counter supports monotonically increasing non-negative
increments recorded synchronously at the call site (spec
L499-L500). Example uses: bytes received, requests
completed, accounts created, HTTP 5xx error counts (spec
L504-L508).

Created exclusively through a `Meter` per spec L512
*"MUST NOT be any API for creating a Counter other than
with a Meter"*.

## Non-negative value contract

Per spec L561-L564, the increment value is **expected to
be non-negative**. The API:

- SHOULD be documented to communicate the non-negative
  expectation (satisfied here)
- SHOULD NOT validate — validation is the SDK's
  responsibility

A negative value passed to `add/3` will be accepted at
the API boundary and forwarded to the SDK, which decides
how to handle it. If the domain supports bidirectional
motion, use `Otel.Metrics.UpDownCounter` instead.

## BEAM representation

`opentelemetry-erlang` implements this as a `defmacro`
that resolves the meter implicitly via
`opentelemetry_experimental:get_meter/1` at expansion
time and injects `OpenTelemetry.Ctx.get_current()` into
`:otel_counter.add/5` (`lib/open_telemetry/counter.ex`).
We take a different path:

- plain `def` with an explicit `Meter.t()` handle —
  macro-free, Dialyzer-visible, consistent with the
  `Tracer` and `Meter` facades project-wide
- no implicit context threaded through `add/3` —
  synchronous metric measurements are not
  context-associated at the API boundary; the SDK
  attaches context per `Otel.Ctx` if relevant

All functions are safe for concurrent use (spec
L1351-L1352, §Concurrency §Instrument).

## Public API

| Function | Role |
|---|---|
| `create/3` | **Application** (OTel API MUST) — Counter creation (L510-L542) |
| `add/3` | **Application** (OTel API MUST) — Counter Add (L545-L598) |

## References

- OTel Metrics API §Counter: `opentelemetry-specification/specification/metrics/api.md` L497-L598
- OTel Metrics API §Synchronous Instrument API: `opentelemetry-specification/specification/metrics/api.md` L302-L348
- OTel Metrics API §Concurrency §Instrument: `opentelemetry-specification/specification/metrics/api.md` L1351-L1352

# `primitive`

```elixir
@type primitive() ::
  String.t() | {:bytes, binary()} | boolean() | integer() | float() | nil
```

# `primitive_any`

```elixir
@type primitive_any() ::
  primitive() | [primitive_any()] | %{required(String.t()) =&gt; primitive_any()}
```

# `add`

```elixir
@spec add(
  instrument :: Otel.Metrics.Instrument.t(),
  value :: number(),
  attributes :: %{required(String.t()) =&gt; primitive_any()}
) :: :ok
```

**Application** (OTel API MUST) — "Add" (`metrics/api.md`
§Counter operations — Add, L545-L598).

Increments the Counter by `value`. Per spec L561-L564 the
value is expected to be non-negative; the API does not
validate (`SHOULD NOT validate` per spec — SDK's job).

Attributes default to `%{}` per spec L565-L570 *"MUST be
structured to accept a variable number of attributes,
including none"*.

Delegates to `Otel.Metrics.Meter.record/3` — both
Counter.add and the synchronous siblings share a single
Meter dispatch.

# `create`

```elixir
@spec create(
  name :: String.t(),
  opts :: Otel.Metrics.Instrument.create_opts()
) :: Otel.Metrics.Instrument.t()
```

**Application** (OTel API MUST) — "Counter creation"
(`metrics/api.md` §Counter creation, L510-L542).

Creates the instrument handle via the given Meter. Per
spec L512, there is no other API surface for creating a
Counter.

Options (per §Synchronous Instrument API L302-L348):

- `:unit` — case-sensitive ASCII string, max 63 chars
- `:description` — opaque string (BMP Plane 0), at least
  1023 chars supported
- `:advisory` — advisory parameters

Delegates to `Otel.Metrics.Meter.create_counter/3`.

---

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