Kepler.Source.Report (Kepler v0.1.0)

Copy Markdown View Source

Tier 0: crash and supervisor reports, enriched with what OTP already captured.

This is the case for being inside the VM. A crash event carrying the dying process's state, its last message, its stacktrace, its ancestors, and the request context it was holding is not assemblable from outside the BEAM — no scrape interval and no log line reconstructs it.

watch :process_crash do
  source crash_report: :any
  enrich [:stacktrace, :process_state, :last_message, :request_context]
  severity :error
  fire immediately, cooldown: :timer.minutes(1)
end

watch :child_terminated do
  source supervisor_report: :child_terminated
  enrich [:supervisor, :child_id, :reason, :pid]
  fire immediately
end

What you can actually get

The process is already dead

By the time a report arrives there is nothing left to introspect — Process.info/2 on the pid returns nil. Enrichment reads what OTP captured at crash time, so what is available depends on what crashed.

Crashed thingWhat the report carries
A GenServer, gen_statem, or gen_eventReason, stacktrace, state, last message, plus everything below
Any proc_lib process (Task, Agent, supervised children)Reason, stacktrace, initial call, ancestors, message queue, dictionary
A bare spawnNothing — bare processes produce no report at all

:request_context

The one field Kepler cannot supply for you. A log event carries the metadata of the process that logged it, so :request_context is whatever your application put in Logger.metadata/1 on the process that died, with :logger's own bookkeeping keys removed.

That means user and request attribution works only if you propagate context into spawned processes. Kepler can read Logger.metadata; it cannot make your application set it. If your Tasks do not inherit request context today, this field will be empty and no amount of library will fix it — that is an application discipline problem, and worth finding out about before you build on it.

SASL reports are off by default

Elixir disables SASL reports, which filters proc_lib crash reports and all supervisor reports out before any handler sees them. What still works:

Default configWith handle_sasl_reports: true
crash_report: :anyOTP behaviours only — but these are the rich onesEvery proc_lib process, including plain Tasks
supervisor_report:Never firesEvery child termination

Kepler warns at boot when you declare a watch this affects. To see everything:

config :logger, handle_sasl_reports: true

Deduplication

One GenServer crash under a supervisor produces three reports: a gen_server terminate report, a proc_lib crash report, and a supervisor child_terminated report. A crash_report: :any watch would otherwise fire twice for one crash.

Kepler fires on the first report for a given pid and suppresses the rest for a short window. The first is also the richest — the behaviour's own report is the one carrying state and last message. supervisor_report: matches exactly one report kind and is never deduplicated, so a watch on supervisor churn still sees every restart.

Cost

A :logger filter that pattern-matches the report label, and nothing else until something crashes. If nothing in your system is dying, this costs a failed match per log event.

Summary

Types

One watch's interest in a report kind.

Functions

Installs the :logger handler for the given routes.

Whether the VM is delivering SASL reports to handlers.

Removes the handler. Safe to call when it was never installed.

Types

route()

@type route() :: %{name: atom(), enrich: [atom()], ring: Kepler.RingBuffer.t() | nil}

One watch's interest in a report kind.

Functions

install(routes)

@spec install(%{required(atom()) => [route()]}) :: {:ok, :ets.table()} | :ignore

Installs the :logger handler for the given routes.

routes maps each report label to the watches interested in it. Returns :ignore when no watch asked for reports, so applications that do not use this source never add a handler.

sasl_reports?()

@spec sasl_reports?() :: boolean()

Whether the VM is delivering SASL reports to handlers.

Elixir disables them by default, which filters out proc_lib crash reports and every supervisor report before any handler sees them.

uninstall()

@spec uninstall() :: :ok

Removes the handler. Safe to call when it was never installed.