Mailglass.Telemetry (Mailglass v0.1.0)

Copy Markdown View Source

Telemetry integration for mailglass.

Event Naming Convention

All mailglass events follow the 4-level path plus a phase suffix:

[:mailglass, :domain, :resource, :action, :start | :stop | :exception]

Named span helpers wrap :telemetry.span/3 for each domain. Domain helpers land in their owning phase (render in Phase 1, send/batch in Phase 3, persist/events in Phase 2, webhook_verify/webhook_ingest in Phase 4, preview_render in Phase 5).

Phase 1 Events

Render pipeline

  • [:mailglass, :render, :message, :start | :stop | :exception] — Measurements on :start: %{system_time: integer} — Measurements on :stop: %{duration: native_time} — Metadata: %{tenant_id: string, mailable: atom}

Metadata Policy (D-31)

Whitelisted keys: :tenant_id, :mailable, :provider, :status, :message_id, :delivery_id, :event_id, :latency_ms, :recipient_count, :bytes, :retry_count.

Forbidden (PII): :to, :from, :body, :html_body, :subject, :headers, :recipient, :email.

Enforcement is lint-time (Phase 6 custom Credo check NoPiiInTelemetryMeta) plus a runtime StreamData property test that asserts every emitted stop event's metadata keys are a subset of the whitelist across 1000 varied inputs.

Handler Isolation

:telemetry.span/3 wraps each attached handler in a try/catch. A handler that raises is detached automatically and [:telemetry, :handler, :failure] is emitted — the caller's pipeline is unaffected. Mailglass does not add a parallel try/rescue wrapper (would duplicate or — worse — swallow the meta-event operators rely on).

Default Logger

Call attach_default_logger/1 at boot (or configure [telemetry: [default_logger: true]] in the Application env) to log every Mailglass event:

Mailglass.Telemetry.attach_default_logger()
Mailglass.Telemetry.attach_default_logger(level: :warning)

Summary

Functions

Attaches the default logger handler for the Phase 1 event set.

Named span helper wrapping the adapter.deliver/2 call (Phase 3, D-26).

Named span helper for the events-append write path. Phase 2 surface.

One-shot wrapper around :telemetry.execute/3 for non-span counter events.

Named span helper wrapping each Multi commit in the send pipeline (Phase 3, D-26).

Named span helper for persist-layer write paths (projector, reconciler). Phase 2 surface.

Named span helper for the render pipeline. Phase 1 surface.

Named span helper for the Outbound hot path (Phase 3, D-26).

Wraps a zero-arity function in :telemetry.span/3, emitting :start, :stop, and (on exception) :exception events under event_prefix.

Functions

attach_default_logger(opts \\ [])

(since 0.1.0)
@spec attach_default_logger(keyword()) :: :ok | {:error, :already_exists}

Attaches the default logger handler for the Phase 1 event set.

Returns :ok on first attach and {:error, :already_exists} if a handler with the same ID is already attached (useful for idempotent boot paths).

Options

  • :level — log level passed to Logger.log/2. Default: :info.

dispatch_span(metadata, fun)

(since 0.1.0)
@spec dispatch_span(map(), (-> any())) :: any()

Named span helper wrapping the adapter.deliver/2 call (Phase 3, D-26).

Emits [:mailglass, :outbound, :dispatch, :start | :stop | :exception]. Provider latency is the fat tail — this span captures it.

events_append_span(metadata, fun)

(since 0.1.0)
@spec events_append_span(map(), (-> result)) :: result when result: term()

Named span helper for the events-append write path. Phase 2 surface.

Equivalent to span([:mailglass, :events, :append], metadata, fun). :stop metadata SHOULD include inserted?: boolean and idempotency_key_present?: boolean per D-04.

execute(event_name, measurements \\ %{}, metadata \\ %{})

(since 0.1.0)
@spec execute([atom()], map(), map()) :: :ok

One-shot wrapper around :telemetry.execute/3 for non-span counter events.

Callers are expected to prepend :mailglass to the event path.

persist_outbound_multi_span(metadata, fun)

(since 0.1.0)
@spec persist_outbound_multi_span(map(), (-> any())) :: any()

Named span helper wrapping each Multi commit in the send pipeline (Phase 3, D-26).

Emits `[:mailglass, :persist, :outbound, :multi, :start:stop:exception]`.
Metadata carries :step_name (`:persist_queued:persist_dispatched:persist_failed`).

persist_span(suffix, metadata, fun)

(since 0.1.0)
@spec persist_span([atom()], map(), (-> result)) :: result when result: term()

Named span helper for persist-layer write paths (projector, reconciler). Phase 2 surface.

Event path: [:mailglass, :persist | suffix]. Examples:

Mailglass.Telemetry.persist_span([:delivery, :update_projections], meta, fn -> ... end)
Mailglass.Telemetry.persist_span([:reconcile, :link], meta, fn -> ... end)

render_span(metadata, fun)

(since 0.1.0)
@spec render_span(map(), (-> result)) :: result when result: term()

Named span helper for the render pipeline. Phase 1 surface.

Equivalent to span([:mailglass, :render, :message], metadata, fun).

send_span(metadata, fun)

(since 0.1.0)
@spec send_span(map(), (-> any())) :: any()

Named span helper for the Outbound hot path (Phase 3, D-26).

Emits [:mailglass, :outbound, :send, :start | :stop | :exception]. Metadata whitelist per D-31: :tenant_id, :mailable, :stream, :delivery_id, :status, :latency_ms.

span(event_prefix, metadata, fun)

(since 0.1.0)
@spec span([atom()], map(), (-> result)) :: result when result: term()

Wraps a zero-arity function in :telemetry.span/3, emitting :start, :stop, and (on exception) :exception events under event_prefix.

The same metadata map is emitted on every phase. The function's return value is returned unchanged.

Examples

Mailglass.Telemetry.span([:mailglass, :render, :message],
  %{tenant_id: "acme", mailable: MyMailer},
  fn -> render(message) end)