Add request tracing and the rules about what must not be logged

v1's backend had 157 println! calls with emoji prefixes, no levels, no
filtering and no structure -- and one of them printed "Password length:"
from the login handler. There was no way to turn any of it down, and no
way to find the lines belonging to one request.

Every request now gets an id, echoed in x-request-id and attached to every
line logged while handling it. A supplied id is kept, so a reverse proxy
correlates with us. Internal errors return that id in the body: "something
went wrong" is only useful if it leads somewhere, and the detail stays in
the log where it belongs rather than describing the inside of the server
to whoever asked.

The middleware instruments the inner future rather than tagging its own
events with a parent. A test caught the difference: with the parent form,
an error raised inside a handler logged outside the span, so the id the
client was told to quote led nowhere.

Four tests assert what must not appear. A password never reaches the log,
nor does its length -- knowing it is nine characters is knowing something.
A session token never reaches it either. Query strings are not logged at
all, because they carry calendar paths, and the path alone says what
happened.

And a test greps the source for println!, print!, eprintln! and dbg! in
runway-core, runway-caldav and runway-server. main.rs is exempt: genkey
printing a key to stdout is its whole job. A rule that only lives in a
document gets forgotten.
This commit is contained in:
2026-08-26 18:49:30 -04:00
parent 3dcd8f76ad
commit 2e5bd6ec34
11 changed files with 505 additions and 11 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ axum = "0.8"
axum-extra = { version = "0.10", features = ["cookie", "query"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "signal"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace"] }
tower-http = { version = "0.6", features = ["cors", "trace", "request-id"] }
sqlx = { version = "0.9", features = ["runtime-tokio", "sqlite", "chrono", "uuid", "migrate"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
quick-xml = "0.42"