monitor.status-code-summary
Count HTTP status codes by class (1xx to 5xx) and give the server error and client error rates in basis points.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 12 tests, run in TypeScript, Python and Rust.
What it does
Counts a batch of HTTP status codes by class, as RFC 9110 section 15 groups them (1xx informational, 2xx successful, 3xx redirection, 4xx client error, 5xx server error), and gives two rates in basis points (10000 = 100%).
## Why 5xx and 4xx are separate
For example
summarise_status_codes(200, 201, 204, 301, 304, 404, 500, 503, 101, 200)→ total 10, informational 1, success 4, redirect 2, client error 1, server error 2, error rate basis points 20%, client error rate basis points 10% a mixed batch: 2 server errors in 10 is 20%, the 404 is reported apartsummarise_status_codes()→ total 0, informational 0, success 0, redirect 0, client error 0, server error 0, error rate basis points 0%, client error rate basis points 0% an empty batch is all zeros, not a division by zerosummarise_status_codes(100, 199, 200, 299, 300, 399, 400, 499, 500, 599)→ total 10, informational 2, success 2, redirect 2, client error 2, server error 2, error rate basis points 20%, client error rate basis points 20% every class boundary: 199 is informational, 299 success, 399 redirect, 499 client, 599 server
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn summarise_status_codes(statuses: &[i64]) -> StatusCodeSummary
| statuses | int[] | HTTP status codes, each 100 to 599, e.g. from parsed access log lines |
| returns | StatusCodeSummary |
The type it declares, generated into your project
/// Status codes counted by class, with the two error rates.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StatusCodeSummary {
pub total: i64,
/// 1xx
pub informational: i64,
/// 2xx
pub success: i64,
/// 3xx
pub redirect: i64,
/// 4xx
pub client_error: i64,
/// 5xx
pub server_error: i64,
/// 5xx / total x10000, half-up; 0 when empty
pub error_rate_basis_points: i64,
/// 4xx / total x10000, half-up; 0 when empty
pub client_error_rate_basis_points: i64,
}
Your code names it in one line, in the file that uses it
fune!(monitor.status-code-summary@^1); // then call summarise_status_codes(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
/// HTTP status codes counted by class, with the server error rate (5xx) and
/// the client error rate (4xx) kept apart: only 5xx is the server's failure.
///
/// # Panics
/// Panics on a status code outside 100 to 599.
pub fn summarise_status_codes(statuses: &[i64]) -> StatusCodeSummary {
let mut counts = [0i64; 5];
for &s in statuses {
if !(100..=599).contains(&s) {
panic!("status code must be 100 to 599, received {}", s);
}
counts[(s / 100 - 1) as usize] += 1;
}
let total = statuses.len() as i64;
let rate = |n: i64| if total == 0 { 0 } else { round_div(n * 10000, total, "half-up") };
StatusCodeSummary {
total,
informational: counts[0],
success: counts[1],
redirect: counts[2],
client_error: counts[3],
server_error: counts[4],
error_rate_basis_points: rate(counts[4]),
client_error_rate_basis_points: rate(counts[3]),
}
}
pub fn status_code_summary_to_value(s: &StatusCodeSummary) -> Value {
Value::obj(vec![
("total", Value::Int(s.total)),
("informational", Value::Int(s.informational)),
("success", Value::Int(s.success)),
("redirect", Value::Int(s.redirect)),
("clientError", Value::Int(s.client_error)),
("serverError", Value::Int(s.server_error)),
("errorRateBasisPoints", Value::Int(s.error_rate_basis_points)),
("clientErrorRateBasisPoints", Value::Int(s.client_error_rate_basis_points)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let statuses: Vec<i64> = args[0]
.as_arr()
.iter()
.map(|v| match v {
Value::Int(i) => *i,
_ => panic!("status code must be a whole number"),
})
.collect();
status_code_summary_to_value(&summarise_status_codes(&statuses))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add monitor.status-code-summary
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./monitor.status-code-summary-1.0.0-rust.fune, or fetch it from a terminal with fune pull monitor.status-code-summary@1.0.0:rust.
The whole function, every language, is one file too: monitor.status-code-summary-1.0.0.fune, 11,312 bytes, sha256 e27fd5dc4c4c4687f89e92d503e9fed8929a4049f125a06aa3acb874a2e6fa0c. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before monitor.status-code-summary
after — your function gets the result and the arguments, and returns the final result.
// fune: after monitor.status-code-summary
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace math.round-div in monitor.status-code-summary
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show monitor.status-code-summary --steps.
// fune: step monitor.status-code-summary after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| a mixed batch: 2 server errors in 10 is 20%, the 404 is reported apart | 200, 201, 204, 301, 304, 404, 500, 503, 101, 200 | → | total 10, informational 1, success 4, redirect 2, client error 1, server error 2, error rate basis points 20%, client error rate basis points 10% |
| an empty batch is all zeros, not a division by zero | → | total 0, informational 0, success 0, redirect 0, client error 0, server error 0, error rate basis points 0%, client error rate basis points 0% | |
| every class boundary: 199 is informational, 299 success, 399 redirect, 499 client, 599 server | 100, 199, 200, 299, 300, 399, 400, 499, 500, 599 | → | total 10, informational 2, success 2, redirect 2, client error 2, server error 2, error rate basis points 20%, client error rate basis points 20% |
| only server errors is 100% | 500, 502 | → | total 2, informational 0, success 0, redirect 0, client error 0, server error 2, error rate basis points 100%, client error rate basis points 0% |
| 4xx is not a server error: 404s and a 429 leave the error rate at zero | 404, 404, 429, 200 | → | total 4, informational 0, success 1, redirect 0, client error 3, server error 0, error rate basis points 0%, client error rate basis points 75% |
| 2 in 3 rounds half up to 6667 | 500, 500, 200 | → | total 3, informational 0, success 1, redirect 0, client error 0, server error 2, error rate basis points 66.67%, client error rate basis points 0% |
| 1 in 3 rounds down to 3333 | 503, 200, 404 | → | total 3, informational 0, success 1, redirect 0, client error 1, server error 1, error rate basis points 33.33%, client error rate basis points 33.33% |
| 1 in 32 is exactly 312.5 and rounds up to 313 | 500, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 | → | total 32, informational 0, success 31, redirect 0, client error 0, server error 1, error rate basis points 3.13%, client error rate basis points 0% |
| all success | 200, 200, 200, 200 | → | total 4, informational 0, success 4, redirect 0, client error 0, server error 0, error rate basis points 0%, client error rate basis points 0% |
| 600 is not an HTTP status code | 200, 600 | → | error: status code must be 100 to 599, received 600 |
Show the other 2 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 99 is not an HTTP status code | 99 | → | error: status code must be 100 to 599, received 99 |
| a fractional status code is an error | 200.5 | → | error: status code must be a whole number |
More from the author
`errorRateBasisPoints` counts only 5xx: the server failed a request it should have served, which is what an availability SLO measures (the Google SRE book's request-based availability counts server errors). A 404 or 429 is usually the client's doing, or the server working as designed, so it is reported apart as `clientErrorRateBasisPoints` rather than burning the error budget. A sudden rise in 4xx is still worth watching, which is why it is there at all.
## Rounding
Both rates are rounded half up (`math.round-div`): 2 in 3 is 6667, 1 in 32 (312.5) is 313. An empty list gives zeros rather than dividing by zero.
## Errors
- `status code must be 100 to 599, received 600` - `status code must be a whole number` (a fractional value)
## Sources
- RFC 9110, HTTP Semantics, section 15 "Status Codes", https://www.rfc-editor.org/rfc/rfc9110#section-15 - Google SRE book, "Embracing Risk" (request success rate as availability), https://sre.google/sre-book/embracing-risk/
Files
| Path | Bytes |
|---|---|
| README.md | 1,271 |
| impl/python.py | 1,137 |
| impl/rust.rs | 1,923 |
| impl/typescript.ts | 1,063 |
| vectors.json | 2,951 |