Error Budget Report
Compute availability and error-budget burn from access logs, and gate releases on the SLO
SRE error budgets turn reliability into a number teams can spend. This report parses an access log, computes availability and budget burn in JavaScript (4xx are client errors - they do not burn budget), writes the report, and fails when the SLO is breached - which is exactly the signal a release pipeline should consume.
Executed for real against OrchStep v0.7.1 over 2,000 synthesized requests.
What it demonstrates
| Capability | Where |
|---|---|
| Log crunching with real tools | awk status-class tallies |
| SLO math in JavaScript | budget (transform) |
| Runtime-tunable policy | --var slo=... staged through outputs |
| Reports as artifacts | write_report |
| The gate other pipelines consume | gate exits non-zero on breach |
The workflow
name: error-budget-report
desc: "Compute availability and error-budget burn from access logs; gate on the SLO"
defaults:
log_file: "./access.log"
slo: "99.5"
report_dir: "./reports"
tasks:
generate_demo_logs:
desc: "Synthesize a day of access logs (demo helper)"
steps:
- name: synth
func: shell
do: |
: > {{ vars.log_file }}
i=0
while [ $i -lt 2000 ]; do
if [ $((i % 250)) -eq 0 ]; then CODE=500; elif [ $((i % 97)) -eq 0 ]; then CODE=404; else CODE=200; fi
echo "2026-06-11T10:00:00Z GET /api/checkout $CODE 42ms" >> {{ vars.log_file }}
i=$((i+1))
done
wc -l < {{ vars.log_file }} | tr -d ' '
report:
desc: "Parse, compute the budget, write the report, gate"
steps:
- name: tally
desc: "Count requests by class straight from the log"
func: shell
do: |
TOTAL=$(wc -l < {{ vars.log_file }} | tr -d ' ')
S5XX=$(awk '$4 >= 500' {{ vars.log_file }} | wc -l | tr -d ' ')
S4XX=$(awk '$4 >= 400 && $4 < 500' {{ vars.log_file }} | wc -l | tr -d ' ')
echo "total=$TOTAL s5xx=$S5XX s4xx=$S4XX slo={{ vars.slo }}"
outputs:
slo_in: '{{ result.output | regexFind "slo=([0-9.]+)" }}'
total: '{{ result.output | regexFind "total=([0-9]+)" }}'
s5xx: '{{ result.output | regexFind "s5xx=([0-9]+)" }}'
s4xx: '{{ result.output | regexFind "s4xx=([0-9]+)" }}'
- name: budget
desc: "SLO math in JavaScript (4xx are client errors - they do not burn budget)"
func: transform
do: |
const total = Number(steps.tally.total);
const bad = Number(steps.tally.s5xx);
const availability = ((total - bad) / total) * 100;
const slo = Number(steps.tally.slo_in);
const allowed = total * (1 - slo / 100);
const burnPct = allowed > 0 ? (bad / allowed) * 100 : 0;
return {
availability: availability.toFixed(3),
allowed_errors: Math.floor(allowed),
burned: bad,
burn_pct: burnPct.toFixed(1),
breached: availability < slo,
};
- name: write_report
func: shell
do: |
mkdir -p {{ vars.report_dir }}
cat > {{ vars.report_dir }}/error-budget.txt <<R
error budget report (SLO {{ vars.slo }}%)
requests = {{ steps.tally.total }}
server errors = {{ steps.tally.s5xx }} (4xx ignored: {{ steps.tally.s4xx }})
availability = {{ steps.budget.availability }}%
budget burned = {{ steps.budget.burned }}/{{ steps.budget.allowed_errors }} ({{ steps.budget.burn_pct }}%)
R
cat {{ vars.report_dir }}/error-budget.txt
- name: gate
func: assert
args:
condition: "!steps.budget.breached"
desc: "availability is above the SLO"Run it
orchstep run generate_demo_logs
orchstep run report # SLO 99.5
orchstep run report --var slo=99.9 # stricter SLOVerified results:
availability = 99.600%
budget burned = 8/10 (80.0%) # 99.5% SLO: passes (exit 0)
# --var slo=99.9 -> assertion "availability is above the SLO" fails (exit 1)Design notes
The --var bridge. The SLO arrives at the CLI, but transform JavaScript
does not see runtime --var values - so tally echoes slo={ vars.slo }
(templates do see it) and exports it as an output the JS reads. This is
the standard pattern for getting runtime knobs into JS math.
80% burn is the interesting number. Passing the SLO while having spent
most of the budget is what error budgets are for - wire burn_pct into
your release-freeze policy, not just the pass/fail bit.