Under construction

This documentation is still in progress. Some information may be incomplete or out of date. Please check back later.

Docs/Observability

Running it

Observability

Everything httpjet tells you about itself: Prometheus metrics on a loopback listener, the cache-entries report, access and error logs in text or JSON with an optional syslog tap, per-request correlation ids, the telemetry TSV and PHP slow log, opt-in OpenTelemetry traces and metrics, and the profiling endpoints. All control surfaces are loopback-only by design.

Prometheus metrics

--metrics-addr (default 127.0.0.1:9090; empty disables) serves /metrics in the Prometheus text format. Over 130 series today; the families that matter, grouped:

MetricTypeMeaning
Requests & connections
httpjet_requests_total · httpjet_active_requests · httpjet_active_connectionscounter / gaugeServed requests, requests in the response-head phase, open connections.
httpjet_requests_by_vhost · httpjet_responses_by_vhost · httpjet_request_duration_by_vhost_secondslabeledPer-vhost volume, status class and handle() wall time.
httpjet_status_{2xx,3xx,4xx,5xx} · httpjet_status_{400,403,404,410,414,429,500,502,503,504}counterStatus classes and the interesting exact codes.
httpjet_proto_{h1,h2,h3} · httpjet_served_{static,php,proxy}counterWire protocol and terminal mix.
httpjet_request_duration_seconds · httpjet_phase_{rewrite,cache_lookup,compress,upstream}_secondshistogramTotal handle() time and sampled per-stage costs (upstream = proxy checkout → response head).
httpjet_tls_handshakes_{full,resumed}_totalcounterFull vs resumed TLS handshakes.
httpjet_throttle_{allowed,rejected}_totalcounterPer-IP throttle decisions (0 when disabled).
httpjet_body_buffer_{bytes,capacity_bytes,rejected_total}gauge / counterThe process-wide request-body budget.
Fast path & rewrite
httpjet_fast_memo_{hits,stores,ineligible}_totalcounterFinished-response memo activity; ineligible = the chain refused the store (see httpjet check).
httpjet_fast_cookie_{none,member_session,benign_only}_totalcounterCookie shape of fast-path GET/HEADs.
httpjet_rewrite_outcome_{hits,misses,uncacheable}_total · httpjet_rewrite_ua_classify_entriescounter / gaugeRewrite-outcome cache and UA-classification table size.
Page cache
httpjet_pagecache_{hits,misses,stores,purges,purges_received,store_purge_rejections,expired_misses}_total · httpjet_pagecache_hit_ratio · httpjet_cache_hit_ratio_realcounter / gaugeCore activity; _real excludes loopback probes; httpjet_cache_{hits,misses}_by_vhost{vhost} split it per vhost and httpjet_pagecache_evictions_total counts size/TTL teardowns. Telemetry twins httpjet_cache_{hits,misses,bypass,stale_serves,sie_serves,admitted,rejected} and httpjet_disp_* (why a response was or wasn't stored) plus httpjet_cache_admit_ratio.
httpjet_pagecache_entries · _memory_bytes · _hot_bytes · _decoded_entries · _meta_{raw,compressed}_bytes · _tag_{keys,memberships,index_bytes} · _tag_purge_{tombstones,floor_milliseconds}gaugeExact RAM accounting of the index and tag indexes.
httpjet_pagecache_disk_{bytes,max_bytes} · _disk_evictions_total · _disk_evict_age_{lt_1h,1h_6h,6h_24h,ge_24h}_total · _swept_expired_totalgauge / counterFile-tier footprint vs budget, LRU evictions by entry age, proactive expiry sweeps.
httpjet_pagecache_disk_{read,write,full}_errors_total · _missing_file_invalidations_total · _orphan_{reclaimed,deferred}_total · _meta_decode_errors_total · _key_id_collisions_total · _poisoned_locks_totalcounterHealth: every one of these should be flat. Poisoned locks nonzero = indexes may be inconsistent. httpjet_pagecache_integrity_active = 1 once the file-tier integrity key is installed.
httpjet_pagecache_dict_recompress_total · _dict_recompress_bytes_total · httpjet_pagecache_refresh_inflightcounter / gaugeStore-time dictionary recompressions (bounded background pool); stale-while-revalidate refreshes in flight.
httpjet_pagecache_shared_path_{public_routes,canary_skipped}_total · _shared_paths_{matchers,canary_percent}counter / gaugeShared-paths routing.
httpjet_xf_capsule_{hits,misses,stale_hits,dedicated_stores,canary_filtered}_total · _hits_by_class_total · _shell_age_secscounter / histogramXenForo capsule tier.
httpjet_process_rss_bytes · httpjet_process_rss_anon_bytes · httpjet_pagecache_unaccounted_rss_anon_bytesgaugeProcess RSS and the part the cache accounting cannot explain.
LSAPI / PHP
httpjet_lsapi_{acquire,dispatch,ttfb}_secondshistogramPool contention · backend call · request-committed-to-first-byte.
httpjet_lsapi_generation_advances_total · _stale_{idle,checked_out}_drops_total · _stale_worker_retire_{signals,failures}_total · _worker_attribution_failures_total · _eagain_{retries,terminal_exhaustions}_totalcounterExternal-pool generation lifecycle and backlog pressure.
Proxy
httpjet_proxy_peer_{healthy,probes_total,health_transitions_total,active_requests,selections_total,prehead_failures_total} · httpjet_proxy_failover_totallabeledPer-peer (scope, group, ordinal) health and selection; failovers served by a non-primary peer.
Logging, OTel, debug
httpjet_log_access_dropped_lines_totalcounterAccess lines shed by the bounded writer queue.
httpjet_otel_spans_{queue,dropped_total,exported_total,export_failed_total} · httpjet_otel_http_{exports,export_failures}_totalgauge / counterSpan queue depth and loss; collector HTTP outcomes (observable even when the collector is down).
httpjet_debug_cache_entries_{renders,throttled}_totalcounterRenders of the /cache-entries report and locally throttled requests for it.

The cache-entries report

GET /cache-entries on the metrics listener (not the main port — there it hits the vhost and 301s) renders a throttled text report: total entries and bytes, per-URL-class counts, top-N by size, a ram: entries=… disk: bytes=…/max evictions=… swept_expired=… line (live index count vs charged tmpfs footprint), and a health line (hits/misses/stores/purges/disk_{read,write}_err/poisoned_locks). /__mimalloc-stats beside it prints allocator statistics; /debug/pprof/profile (build feature profiling, optional --profile-token) returns a CPU profile — the release binary keeps its symbol table and frame pointers for exactly this.

Logs

The unified access log (path from the XML; combined format with a trailing reqid= token) writes from a dedicated non-blocking task with a bounded queue (httpjet_log_access_dropped_lines_total counts sheds), rolls at 50 MB and prunes after 30 days (HTTPJET_ACCESS_ROLLING_BYTES, HTTPJET_ACCESS_KEEP_DAYS). HTTPJET_ACCESS_LOG_FORMAT=json emits one object per line with a fixed field order:

access · json
{"ts":"17/Sep/2026:11:02:09 +0000","client_ip":"203.0.113.9","remote_user":null,"method":"GET","uri":"/threads/hello.123/","protocol":"HTTP/2.0","status":200,"bytes":41211,"referer":null,"user_agent":"Mozilla/5.0 …","reqid":"3f9c2a7b81d04e55"}

Unix-socket peers render the literal host unix:. Cache disposition is in the response header (x-litespeed-cache), not the log line; join on reqid.

The rolling httpjet_error.log (and captured backend stderr) is text by default. HTTPJET_ERROR_LOG_FORMAT=json switches to JSON events with schema_version 1: timestamp_unix_ms, level, nullable target, message, and a fields object that keeps tracing WARN/ERROR fields named rather than flattened. JSON-only redaction replaces values of authorization/cookie/password/token/secret/key/credential-named fields and suppresses whole values that look like credential syntax or URL userinfo — a conservative heuristic, not a guarantee. Bounds: 32 fields, 1024-char values, 8192-char messages; inputs over 32 KiB become [OVERSIZED]. The stdout/journald tracing formatter (RUST_LOG) is unaffected.

HTTPJET_SYSLOG_TARGET mirrors every rendered access line as an RFC 5424 (or 3164) datagram from the writer task — request path untouched, file logging unaffected, send failures counted. The MSG part is the rendered line itself, so existing parsers keep working. Targets: udp://host:514, bare host:port, or a unix-dgram path like /run/systemd/journal/syslog. Facility/severity/hostname/app-name knobs are on the CLI page.

A vhost's <logging> block with useServer=0 and a fileName gives it its own access and error files (rolling size and keep-days per file; logHeaders 7 = combined format with request headers). Log paths are boot-owned: they cannot be changed through the write API.

Request ids

Every request gets a 64-bit correlation id (a per-process seed mixed into a lock-free counter — no syscall, no RNG on the hot path; a background stale-while-revalidate refresh inherits its parent's id) that appears in the access, error and PHP-slow logs. --request-id-header also echoes it as X-Request-Id so a client or CDN can join a response to the server-side records. Off by default.

Telemetry TSV and the PHP slow log

--telemetry-file logs/telemetry.tsv
Appends a cumulative snapshot row of the in-RAM aggregates every --telemetry-flush-secs (30): durability across restarts and a self-contained time series (~1 MB/day). The :9090 endpoint works without it.
--php-slow-log logs/php-slow.tsv
A TSV line for every LSAPI request whose render TTFB exceeds --php-slow-threshold-ms (50), plus a 1-in-128 sample of all PHP requests as the population baseline — the per-URL / user-class attribution behind httpjet_lsapi_ttfb_seconds. Rolls at 64 MiB, gzip archives, 30-day prune.

These two files are what produced the production attribution on the Performance page: mean request 9.99 ms, of which 8.96 ms is inside PHP.

OpenTelemetry --features otel

Build with --features otel, then set HTTPJET_OTEL=1; without it no provider or exporter is created, and requesting it in a binary without the feature fails startup. The official Rust SDK exports OTLP HTTP/protobuf to OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (complete URL). HTTPJET_OTEL_METRICS=1 adds a 30-second periodic metrics reader to OTEL_EXPORTER_OTLP_METRICS_ENDPOINT.

ends when the transportfinishes the write(H2 flow control, H3 FINaccepted, streams to EOF)

httpjet.request
root · H1/H2 dispatch boundary or H3 entry
attrs: status, httpjet.execution.path = on-core | bridge, httpjet.body.outcome

httpjet.rewrite (internal, sampled)

httpjet.cache.lookup (internal)

terminal

httpjet.proxy (client)
traceparent → named upstreams

httpjet.lsapi (client)
HTTP_TRACEPARENT in the LSAPI env

httpjet.websocket (client)
handshake only, not the relay

httpjet.static (internal)

httpjet.cache.store (internal)

OTLP HTTP/protobuf exporter
2048-span queue · 256/batch · 1 s · drops on saturation

What gets exported. One root span per request at the dispatch boundary, backend client spans, internal stage spans on sampled requests, and a loss-accounted batch exporter. Spans carry status and bounded internal attributes only — no headers, bodies, URLs or error text.
diagram source (mermaid)
flowchart TB
  classDef root fill:#13203a,stroke:#818cf8,color:#fff
  classDef c fill:#0e1424,stroke:#22d3ee,color:#e8ecf6
  R["httpjet.request<br/>root · H1/H2 dispatch boundary or H3 entry<br/>attrs: status, httpjet.execution.path = on-core | bridge, httpjet.body.outcome"]:::root
  R --> RW["httpjet.rewrite (internal, sampled)"]:::c
  R --> CL["httpjet.cache.lookup (internal)"]:::c
  R --> T("terminal")
  T --> P["httpjet.proxy (client)<br/>traceparent → named upstreams"]:::c
  T --> L["httpjet.lsapi (client)<br/>HTTP_TRACEPARENT in the LSAPI env"]:::c
  T --> WS["httpjet.websocket (client)<br/>handshake only, not the relay"]:::c
  T --> S["httpjet.static (internal)"]:::c
  R --> CS["httpjet.cache.store (internal)"]:::c
  R -- "ends when the transport finishes the write<br/>(H2 flow control, H3 FIN accepted, streams to EOF)" --> X["OTLP HTTP/protobuf exporter<br/>2048-span queue · 256/batch · 1 s · drops on saturation"]
enable
HTTPJET_OTEL=1 HTTPJET_OTEL_METRICS=1 HTTPJET_OTEL_SAMPLE_RATIO=0.05 \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces \
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://127.0.0.1:4318/v1/metrics \
httpjet --root /abs/root serve …

The read-only status API

--admin-addr 127.0.0.1:9091 exposes GET /v1/status: generation, configuration fingerprint, listener names, vhost names, upstream peer health/active/selection counters and page-cache aggregates — never paths, credentials or cached URLs. Full contract on Admin API & reload.

Debugging a request

tracing filters
RUST_LOG=debug,hj_rewrite=trace httpjet --root /abs/root serve --http-addr 127.0.0.1:8080 …
# where to look: vhost resolution (hj-core router) → rewrite outcome (hj-rewrite eval) →
#   cache lookup (httpjet lscache) → terminal (pipeline dispatch) → response (handler)

Production units run RUST_LOG=httpjet=info; anything you must see in the journal should log at WARN or above.

httpjet · documentation · synced to the source tree on 2026-10-13 (v0.0.5).
Source: github.com/faratech/httpjet · Home: httpjet.net · Found an error? Open an issue on the repository.