Recipe: Decision And DLQ Logs

Configure decision output, bad-record handling, and dashboard log inspection.

Decision logs are compact records for downstream routing. Dead-letter logs keep bad input out of the hot path while preserving samples for debugging.

Python Engine Logs

import blazerules

cfg = blazerules.EngineConfig()
cfg.output_detail = blazerules.OutputDetail.DECISIONS
cfg.decision_log_path = "decisions.ndjson"
cfg.ingest_error_mode = blazerules.IngestErrorMode.SKIP_TO_DEAD_LETTER
cfg.dead_letter_path = "dead_letters.ndjson"

engine = blazerules.RuleEngine(cfg)
engine.load_rules("rules.yaml")
result = engine.evaluate_ndjson(payload)
CLI equivalent: write compact decisions with the agent
blazerules_agent \
  --rules rules.yaml \
  --input stdin \
  --output ndjson \
  --output-path decisions.ndjson

The agent writes compact decisions. Engine-level dead-letter policy is configured through EngineConfig in Python/C++.

Agent Output

blazerules_agent \
  --rules rules.yaml \
  --input stdin \
  --output ndjson \
  --output-path decisions.ndjson
Python equivalent: evaluate a batch and write decisions yourself
import json
import time
import blazerules

engine = blazerules.RuleEngine()
engine.load_rules("rules.yaml")
result = engine.evaluate_ndjson(payload)

with open("decisions.ndjson", "a", encoding="utf-8") as out:
    ts_ms = int(time.time() * 1000)
    for row, decision in enumerate(result.decisions):
        out.write(json.dumps({
            "ts_ms": ts_ms,
            "batch_row": row,
            "decision": decision,
            "score": result.scores[row],
            "risk_band": result.risk_bands[row],
            "winning_rule_id": result.winning_rule_ids[row],
        }) + "\n")

The agent writes one decision per evaluated row:

{"ts_ms":1782150000000,"service":"checkout","source":"stdin","batch_row":12,"decision":"BLOCK","score":91.0,"risk_band":"HIGH","winning_rule_id":"blocked_device"}

Dead-Letter Mode

Use dead-letter mode when bad records should be inspectable instead of silently counted:

cfg.ingest_error_mode = blazerules.IngestErrorMode.SKIP_TO_DEAD_LETTER
cfg.dead_letter_path = "dead_letters.ndjson"
CLI equivalent: inspect dead-letter output with the dashboard
blazerules_dashboard \
  --decision-log decisions.ndjson \
  --dead-letter-log dead_letters.ndjson \
  --host 127.0.0.1 \
  --port 9470

The result still reports counters:

print(result.messages_processed)
print(result.messages_skipped)
print(result.error_counts)
print(result.error_samples)

Dashboard

blazerules_dashboard \
  --decision-log decisions.ndjson \
  --dead-letter-log dead_letters.ndjson \
  --rules rules.yaml \
  --host 127.0.0.1 \
  --port 9470
Python equivalent: start the dashboard process
import subprocess

subprocess.Popen([
    "blazerules_dashboard",
    "--decision-log", "decisions.ndjson",
    "--dead-letter-log", "dead_letters.ndjson",
    "--rules", "rules.yaml",
    "--host", "127.0.0.1",
    "--port", "9470",
])

Open http://127.0.0.1:9470.