Bloccs.Telemetry (bloccs v0.7.0)

Copy Markdown View Source

Telemetry events emitted by the bloccs runtime, and an optional default logger handler.

Attach your own handlers with :telemetry.attach/4 (or attach_many/4) to feed metrics into Telemetry.Metrics, OpenTelemetry, StatsD, etc. A node's [observability] block is passed through verbatim in every event's metadata under :observability, so a handler can filter by the metrics/traces a node declared.

Events

Per-message node execution is wrapped in :telemetry.span/3, which emits:

  • [:bloccs, :node, :start]
    • measurements: %{system_time, monotonic_time}
    • metadata: %{network, node, in_port, attempt, observability, telemetry_span_context}
  • [:bloccs, :node, :stop]
    • measurements: %{duration, monotonic_time}
    • metadata: above plus :outcome (:ok | :failed) and, when failed, :reason

  • [:bloccs, :node, :exception] — only if execution raises (the runtime rescues node errors, so this fires only on a bug in the runtime itself)
    • measurements: %{duration, monotonic_time}
    • metadata: above plus :kind, :reason, :stacktrace

Routing emits a per-dispatch event (the signal Bloccs.Trace records for coverage):

  • [:bloccs, :emit] — a node emitted on an out-port
    • measurements: %{targets} (number of downstream edges)
    • metadata: %{network, from_node, from_port, targets, payload, msg_id, parents, trace_id} where targets is a list of {to_node, to_port}, payload is the opt-in Bloccs.Inspect snapshot (nil unless enabled), and msg_id/parents/trace_id are the emitted message's Bloccs.Lineage (a fresh msg_id; parents lists the input message ids that caused it — one for a transform/split, many for a batch/join fan-in)

Two additional point-in-time events:

  • [:bloccs, :node, :retry] — a backed-off retry was scheduled
    • measurements: %{delay_ms, next_attempt}
    • metadata: base metadata plus :reason
  • [:bloccs, :node, :skipped] — an idempotent duplicate was dropped
    • measurements: %{}
    • metadata: base metadata
  • [:bloccs, :node, :dropped] — a node filtered a message: its effect shell returned :drop (or an empty emit list), so the message was consumed and acked without dispatching anything downstream. Distinct from :skipped (which is idempotency de-duplication), so filter and dedup metrics stay separable.
    • measurements: %{}
    • metadata: base metadata
  • [:bloccs, :node, :dispatch_error] — a node emitted successfully but one or more downstream deliveries failed; the message is failed (not retried, since the effect already ran)
    • measurements: %{count} (number of failed edges)
    • metadata: base metadata plus :failures (a list of {endpoint, reason})

Routing reports each individual delivery failure too:

  • [:bloccs, :dispatch, :error] — a single downstream push failed
    • measurements: %{}
    • metadata: %{network, from_node, from_port, to_node, to_port, reason} where reason is :no_producer or {:error, :timeout}

:producer back-pressure is reported separately by Bloccs.Producer:

  • [:bloccs, :producer, :backpressure] — a push/3 had to park because the buffer was full (the caller waits until a consumer drains space)
    • measurements: %{size}
    • metadata: %{name, buffer}

Request/response (Bloccs.call/4 / cast/4) is wrapped by Bloccs.Collector, which emits a start/stop pair per request:

  • [:bloccs, :request, :start] — a request registered
    • measurements: %{system_time}
    • metadata: %{network, trace_id, mode} where mode is :call | :cast

  • [:bloccs, :request, :stop] — the request resolved
    • measurements: %{duration} (native units, since :start)
    • metadata: %{network, trace_id, mode, outcome} where outcome is :reply | :error | :timeout, plus :error (the %Bloccs.EffectError{} or reason) when the outcome is :error. trace_id ties the request to the [:bloccs, :emit] / [:bloccs, :node, *] events of the messages it spawned.

Summary

Functions

Attach a built-in handler that logs the interesting runtime events. Handy in development; in production you'll usually attach your own metrics handlers instead. Idempotent — re-attaching is a no-op.

Detach the default logger handler.

List the events the default logger handler subscribes to.

Functions

attach_default_logger(level \\ :debug)

@spec attach_default_logger(Logger.level()) :: :ok | {:error, :already_exists}

Attach a built-in handler that logs the interesting runtime events. Handy in development; in production you'll usually attach your own metrics handlers instead. Idempotent — re-attaching is a no-op.

detach_default_logger()

@spec detach_default_logger() :: :ok | {:error, :not_found}

Detach the default logger handler.

events()

List the events the default logger handler subscribes to.