Skip to content

Getting started

Python and Airflow

Install the package (it ships a compiled Rust extension):

pip install opssignal

To load signal.yaml from a cloud bucket, add the matching extra: opssignal[aws], opssignal[gcp] or opssignal[azure] (see Installing cloud support).

Note

The package is not published to PyPI yet. Until it is, build it from a checkout with uv sync --dev && (cd python && uv run --project .. maturin develop).

Send a signal directly:

from opssignal import notify

notify(
    source="airflow",
    event_type="task_failed",
    title="dbt model orders failed",
    severity="error",          # info | success | warning | error | critical
    message="relation does not exist",
    environment="prod",
)

Or drop the Airflow adapter into a DAG:

from opssignal.integrations.airflow import failure_callback

default_args = {
    "on_failure_callback": failure_callback,
}

notify() also takes the rest of the signal's context as keyword arguments: service, component, team, run_id, correlation_id, trace_id, log_url, dedup_key, and metadata (any JSON-serialisable object, usually a dict; dates and other values become strings).

notify() is fire-and-forget: it validates the input, hands the signal to a background runtime and returns immediately, so a slow Slack webhook never delays the task being monitored.

  • Invalid input (empty source, event_type or title) raises ValueError.
  • A broken signal.yaml raises RuntimeError on every call until it is fixed.
  • An unknown severity string falls back to info rather than raising.

Short-lived processes

Delivery happens in the background. Signals still in flight when the interpreter exits are flushed for up to 5 seconds; call opssignal.flush(timeout) to wait at a point of your choosing.

dbt

Report a run's failures after any dbt command. dbt writes target/run_results.json on every run, so this works with dbt Core, dbt Fusion and artifacts downloaded from dbt Cloud, from a shell, CI job or orchestrator task:

dbt build; status=$?
python -m opssignal.integrations.dbt target/run_results.json --environment prod
exit $status

The reporter always exits 0, so the job still fails or passes on dbt's own status. From Python, call opssignal.integrations.dbt.report_run_results("target/run_results.json", environment="prod").

When dbt runs in-process (dbt-core 1.5+), pass the callback to dbtRunner to alert as each node fails, while the rest of the run continues:

from dbt.cli.main import dbtRunner
from opssignal.integrations.dbt import make_callback

dbtRunner(callbacks=[make_callback(environment="prod")]).invoke(["build"])

Both send one signal per failed node and per warned test:

dbt status Severity event_type Title
error, fail error dbt_<resource>_failed, such as dbt_model_failed dbt model failed: orders
warn warning dbt_<resource>_warned, such as dbt_test_warned dbt test warned: <test name>

service is the dbt project, component the node's unique_id, run_id the dbt invocation ID, and metadata holds the status, resource type, failure count, execution time, relation, dbt version, command and target. Skipped nodes are not reported: they were skipped because an upstream node failed, and that node has its own signal. The title is stable for a node, so a node that keeps failing run after run is deduplicated by providers that support it.

A broken source can fail hundreds of tests in one run. Past 20 signals (change it with --max-signals or max_signals=, 0 for no limit) the rest are rolled into one dbt_run_degraded signal that lists their IDs.

Route dbt signals like any other, for example:

routes:
  - match:
      source: dbt
      severity: [error]
      environment: prod
    providers: [pagerduty, slack]
  - match:
      source: dbt
      severity: [warning]
    providers: [slack]

Command line

cargo install --path crates/cli   # installs the `opssignal` binary
opssignal notify --source ci --event-type build_failed --severity error --title "main is red"

The CLI waits for delivery (up to 5 seconds) and exits non-zero if it fails. See CLI.

No configuration needed

With no signal.yaml, every signal goes to the console provider and is recorded in ./signals.db. Add a config file when you want Slack or webhooks; see Configuration.