Limen.DecisionLog (limen v0.2.1)

Copy Markdown View Source

A sampled, structured decision log.

Writing to Logger, or anywhere else, from the request path would send a message or block for every request, so decisions are instead sampled into a fixed-size ring buffer: an ETS table indexed by an :atomics cursor, one per instance. Limen.DecisionLog.Flusher drains the buffer periodically and hands the decisions, in batches, to the instance's sink: by default Limen.DecisionLog.Logger, which writes each as a structured Logger report, or your own Limen.DecisionLog.Sink, to keep them in a database. When the buffer wraps faster than it is drained, the oldest entries are overwritten and the flusher logs how many were lost.

In tests, delivery: :inline hands each sampled decision to the sink at once, in the process that made it, instead (see the :decision_log options in Limen.Config); the buffer still keeps it for recent/2.

The same buffer backs recent/2, which the LiveDashboard page uses to show the latest decisions.

Sampling is configured with the :decision_log option, see Limen.Config.

Summary

Functions

Returns up to limit of the most recent sampled decisions, newest first.

Samples decision into the instance's buffer according to the configured rates.

Converts a decision into a structured report map.

Types

buffer()

@type buffer() :: %{table: :ets.tid(), cursor: :atomics.atomics_ref()}

Functions

recent(instance, limit \\ 50)

@spec recent(atom() | Limen.Instance.t(), pos_integer()) :: [Limen.Decision.t()]

Returns up to limit of the most recent sampled decisions, newest first.

record(instance, decision)

@spec record(Limen.Instance.t(), Limen.Decision.t()) :: :ok

Samples decision into the instance's buffer according to the configured rates.

Called on the request path. Costs a random number and, when sampled, one atomic increment and one ETS insert. With delivery: :inline (tests only), a sampled decision is also written to the sink right away, and marked so the flusher does not write it again; an error in the sink is then the caller's, so a broken sink fails the test using it.

report(decision)

@spec report(Limen.Decision.t()) :: map()

Converts a decision into a structured report map.