DOC_INDEX
THEME
DOCS/Use Cases/Error Budget Report

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

CapabilityWhere
Log crunching with real toolsawk status-class tallies
SLO math in JavaScriptbudget (transform)
Runtime-tunable policy--var slo=... staged through outputs
Reports as artifactswrite_report
The gate other pipelines consumegate 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 SLO

Verified 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.