Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Health, metrics, and request identifiers

FerroTERM answers a health probe at GET /health, a Prometheus scrape at GET /metrics, and names every request with an X-Request-Id. None of these is a FHIR interaction: the routes live off the FHIR base path, so a probe or a scrape is never mistaken for a terminology request.

Health

GET /health answers 200 OK with an empty body. The server binds its listener only after every index and CodeSystem directory it was configured with is open, and refuses to start on a missing or damaged one, so an answer at all means the deployment serves what it was told to serve. There is no degraded state to report: a probe that gets a 200 may send terminology requests.

The container image has no shell and no HTTP client, so the probe is a subcommand of the binary:

$ ferroterm healthcheck
$ ferroterm healthcheck --url http://127.0.0.1:8080/health

It sends one GET /health, waits at most three seconds, and exits 0 with no output on 200 OK. Otherwise it exits 1 with the reason on stderr: cannot reach, answered 404 Not Found, or did not answer within 3s. Without --url it follows FERROTERM_LISTEN, on the loopback when the server listens on every interface, which is what the image sets. The image declares it as its HEALTHCHECK (every ten seconds, a five-second limit, a sixty-second start period for reading an edition), and the shipped compose.yaml states the same block, so docker compose up --wait and condition: service_healthy work. Kubernetes ignores the image’s instruction and takes GET /health in its own probes.

Scraping

curl -s http://127.0.0.1:8080/metrics

The exposition is the text format Prometheus reads, with every metric under the ferroterm prefix:

MetricTypeLabelsWhat it says
ferroterm_http_requests_totalcountermethod, route, statusthe requests answered
ferroterm_http_request_duration_secondshistogrammethod, route, statushow long each took
ferroterm_code_system_loadedgaugesystem, versionone per code system version loaded
ferroterm_reloads_totalcounteroutcome (ok, failed)the reloads of the served set

route is the matched route, /r4b/CodeSystem/$lookup, never the URI, so the series count stays bounded no matter how many codes clients ask about. The duration buckets start at half a millisecond and double, which brackets the bars the engine is held to: a point read under a millisecond, a page of an expansion under ten.

ferroterm_code_system_loaded describes the set the server answers from now: a reload that drops a version drops its series, and one that adds a version adds one. Both outcome series of ferroterm_reloads_total start at zero, so increase(ferroterm_reloads_total{outcome="failed"}[1h]) > 0 is an alert you can write before the first reload happens. A failed reload means the server is still answering from the old set and the content on disk did not open; the the served set was not reloaded log line carries the reason.

A scrape configuration is one job:

scrape_configs:
  - job_name: ferroterm
    static_configs:
      - targets: ["ferroterm:8080"]

The endpoint carries no code system content and no request bodies, so it is safe to expose to a monitoring network. It is not authenticated: keep it on an internal listener or behind the same reverse proxy rules as the rest of the server (see Configuration).

Request identifiers

Every response carries X-Request-Id. A client that sends the header gets its own value back, so a trace started at the caller stays one trace; a client that sends none gets a UUID the server makes. An id that is empty, longer than 128 characters, or not printable ASCII is replaced rather than echoed, so a header cannot smuggle a line break into a log.

The same id is a field of the request’s log line:

{"timestamp":"2026-09-05T09:21:04.113Z","level":"INFO","message":"request",
 "method":"GET","route":"/r4b/CodeSystem/$lookup","status":200,
 "latency_ms":0.42,"named":"system=http://snomed.info/sct code=73211009",
 "request_id":"0f0f6c1e-9d0e-4a5f-8c1a-1f6b0f1f6b0f"}

So a client that reports a slow or refused call gives you the id, and the log line, the status, and the latency follow from it. Bodies are never logged, and the named field carries only the system, url, version, and code parameters a request stated.