Files
http-latency-prober/docs/usage/step-5-failure-handling.md
Jan Novak c323d879d0 feat(go): step 5 — failure handling with classified exit codes
Classify network failures (dns/connect/timeout/tls) with distinct exit
codes 2-5. Add --timeout (default 10s) via context.WithTimeout. Add
--fail for exit 6 on HTTP status >= 400. Preserve partial phase timing
up to the failure point. -n sampling continues on network failures,
aggregating successes and reporting fail counts. JSON extended with
succeeded/failed/errors fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:45:56 +02:00

3.9 KiB
Raw Blame History

Step 5 — Failure Handling

What this step delivers

latprobe now handles all three real-world failure modes explicitly:

Failure Exit code Behaviour
DNS resolution failure 2 Partial timing (DNS duration) shown; classified as dns
Connection failure (refused / unreachable) 3 Partial timing shown; classified as connect
Timeout (exceeded --timeout) 4 Partial timing shown; classified as timeout
TLS handshake failure 5 Partial timing shown; classified as tls
HTTP status ≥ 400 (with --fail) 6 Full timing shown, status annotated with ✗
HTTP status ≥ 400 (without --fail) 0 Full timing shown, status visible but exit is 0

With -n sampling, network failures do not abort the run — remaining samples continue and successful ones are aggregated normally. The failure count and cause are reported alongside the aggregate.

When multiple URLs or failure types occur, the process exits with the highest exit code encountered.

New flags

Flag Default Description
--timeout 10s Per-request timeout (e.g. 500ms, 2s, 30s)
--fail off Exit with code 6 when any URL returns HTTP status ≥ 400

Examples

DNS failure

$ ./latprobe https://nonexistent.invalid; echo $?
https://nonexistent.invalid  (FAILED)
  ✗ dns: dial tcp: lookup nonexistent.invalid: no such host
2

Connection refused

$ ./latprobe http://localhost:1; echo $?
http://localhost:1  (FAILED)
  ✗ connect: dial tcp [::1]:1: connect: connection refused
3

Timeout

$ ./latprobe --timeout 1s https://example.com:81; echo $?
https://example.com:81  (FAILED)
  ✗ timeout: context deadline exceeded
4

HTTP error — default (exit 0)

$ ./latprobe https://httpbin.org/status/500; echo $?
https://httpbin.org/status/500  (500)
  DNS lookup     :     27.75 ms
  TCP connect    :    114.47 ms
  TLS handshake  :    245.59 ms
  Server (TTFB)  :    113.06 ms
  Transfer       :      0.21 ms
  ─────────────────────────────
  Total          :    502.05 ms
0

HTTP error — with --fail (exit 6)

$ ./latprobe --fail https://httpbin.org/status/404; echo $?
https://httpbin.org/status/404  (404)
  DNS lookup     :      3.35 ms
  ...
  Total          :    476.01 ms
6

Mixed sampling (some network failures)

$ ./latprobe -n 5 https://flaky-host.example.com; echo $?
https://flaky-host.example.com  (200, 3 samples, 2 failed)
                          min        avg        max
  DNS lookup     :    5.00 ms    5.10 ms    5.20 ms
  ...
  ─────────────────────────────────────────────────
  Total          :   80.00 ms   85.00 ms   92.00 ms
  ✗ 2 × connect: connection refused
3

JSON output with failure

$ ./latprobe --json https://nonexistent.invalid | jq .
[
  {
    "url": "https://nonexistent.invalid",
    "status": 0,
    "succeeded": 0,
    "failed": 1,
    "errors": [
      {
        "phase": "dns",
        "count": 1,
        "message": "dial tcp: lookup nonexistent.invalid: no such host"
      }
    ]
  }
]

JSON schema changes

jsonEntry now includes:

{
  "url":       "https://example.com",
  "status":    200,
  "succeeded": 3,
  "failed":    2,
  "phases":    { ... },
  "errors": [
    { "phase": "connect", "count": 2, "message": "connection refused" }
  ]
}
  • phases is omitted when succeeded == 0
  • errors is omitted when failed == 0
  • status is 0 when no sample reached a response

Exit code reference

Code Meaning
0 All probes succeeded
1 Usage error (no URLs, bad flags)
2 DNS resolution failure
3 Connection failure
4 Timeout
5 TLS handshake failure
6 HTTP status ≥ 400 (only with --fail)