Kepler.DSL (Kepler v0.1.0)

Copy Markdown View Source

The macros behind use Kepler.

You do not call anything here directly — use Kepler imports watch/2, and watch/2 imports the rest for the duration of its block. See Kepler for the language itself.

Functions prefixed with __ are called by the generated code and are not part of the public interface.

Summary

Functions

Names the fields to pull into the event's context when the watch fires.

Discards events that do not match, before they are counted.

Sets the condition and its debounce, or immediately for a system monitor.

fire immediately with debounce options.

Sets what to measure. See measure/2 for aggregates other than a gauge.

Sets what to measure, with an aggregate.

Sets what to measure, with a named aggregate and options.

Static key/value pairs copied verbatim into every event this watch emits.

Keeps the last N observations to attach to the event when the watch fires.

How loudly a consumer should react. Defaults to :warning.

Routes this watch to a named sink. Repeat for several.

Routes this watch to a named sink, asking for a delivery guarantee.

Sets where the watch's number comes from. Exactly one per watch.

Declares a watch.

Functions

enrich(fields)

(macro)

Names the fields to pull into the event's context when the watch fires.

enrich [:stacktrace, :process_state, :request_context]   # a report source
enrich [:remote_ip, :user_id, :path]                     # a telemetry source

On a report source these are read out of the report OTP already assembled, so they are free. On a telemetry source they are metadata keys, captured as events arrive — one ETS write per event, the same cost as recent/1.

See Kepler.Source.Report for the report vocabulary.

filter(expr)

(macro)

Discards events that do not match, before they are counted.

Available names are measurements and metadata (also spelled meta). Costs one function call per event on the emitting process, so keep it to a comparison.

filter metadata.status == :error

fire(spec)

(macro)

Sets the condition and its debounce, or immediately for a system monitor.

fire when: value > 2_000, sustained: :timer.seconds(30)
fire when: backlog and p99_rising, cooldown: :timer.minutes(5), resolve: true
fire immediately

The when: expression is captured unevaluated and compiled into a function on your module. Everything else is evaluated normally at compile time.

fire(immediately, opts)

(macro)

fire immediately with debounce options.

fire immediately, cooldown: :timer.minutes(1)

measure(key)

(macro)

Sets what to measure. See measure/2 for aggregates other than a gauge.

measure :message_queue_len
measure :count

measure(key, opts)

(macro)

Sets what to measure, with an aggregate.

measure :duration, :average
measure :duration, percentile: 99, unit: {:native, :millisecond}

measure(key, aggregate, opts)

(macro)

Sets what to measure, with a named aggregate and options.

measure :duration, :average, unit: {:native, :millisecond}

meta(pairs)

(macro)

Static key/value pairs copied verbatim into every event this watch emits.

This is how a generic consumer routes without Kepler knowing anything about it.

meta severity: :page, runbook: "https://runbooks.example/checkout"

recent(spec)

(macro)

Keeps the last N observations to attach to the event when the watch fires.

recent 25
recent size: 25, keys: [:user_id, :path]

An event that says "the queue passed 10,000" is a number going up. One that also carries the last 25 things through the queue is actionable.

On a telemetry source this costs one ETS write per event, so keys: is worth setting — metadata can be large, and a webhook payload should not be.

severity(level)

(macro)

How loudly a consumer should react. Defaults to :warning.

severity :critical

One of :info, :warning, :error, or :critical. Unlike everything else about a watch this is always present on the event, so a consumer can route on it without a nil check.

sink(name)

(macro)

Routes this watch to a named sink. Repeat for several.

sink :siem
sink :ops

A watch with no sink declaration goes to every configured sink. Naming one narrows it to the sinks you name. See sink/2 for delivery guarantees.

sink(name, opts)

(macro)

Routes this watch to a named sink, asking for a delivery guarantee.

sink :siem, guarantee: :at_least_once

:best_effort is the default and the only guarantee implemented: a bounded buffer, drops on backpressure, and a count of what was dropped.

:at_least_once is accepted, warns at boot, and behaves as best-effort. It is declared now because a dropped security event is a detection blind spot — an attacker who can generate volume can wash out their own trail — and that is a different system, not a formatting option. Declaring it means your watches do not have to change when it lands.

source(spec)

(macro)

Sets where the watch's number comes from. Exactly one per watch.

source telemetry: [:my_app, :checkout, :stop]
source process: MyApp.ExportWorker
source vm: :memory
source system_monitor: {:long_gc, 500}

watch(name, list)

(macro)

Declares a watch.

The block accepts source/1, measure/1, measure/2, measure/3, fire/1, fire/2, filter/1, enrich/1, recent/1, severity/1, sink/1, sink/2, and meta/1. Those names are imported for the duration of the block and withdrawn afterwards, so use Kepler leaves only watch/2 in scope and generic names like filter and context cannot collide with the rest of your module.