Tracing¶
Every SDK call propagates audit trace context — trace, journey, and
device headers — so the Campus Audit waterfall shows which host route
(or which process boot) fired each campus call. The machinery lives in
campus_python.tracing and is on by default; hosts only
touch it to install providers or override journey attribution.
In-request calls¶
When the host application is itself traced — its Flask app wired with
campus.audit.middleware.init_app — every request it handles becomes
an audit span. SDK calls made while handling that request land as
child spans of it: the middleware's propagation module installs a
request-context provider via
set_request_context_provider(),
and its output (span/journey/device headers) is attached verbatim to
outbound SDK calls.
The SDK itself is framework-free: with no provider installed, calls are treated as out-of-request and carry only the boot journey below.
Boot journeys¶
Every SDK call is a user request, whether that user is human or agent.
Calls made outside any request context — startup, background threads —
carry a process journey (one uid-journey-... per process boot)
plus a process device tag so boot spans attribute to the deploy that
produced them:
- Device id defaults to
RAILWAY_DEPLOYMENT_IDwhen present. - Inside a request, the caller's own journey always wins; the strict fallback only fills the out-of-request gap.
Overrides:
import campus_python.tracing as tracing
tracing.set_process_journey("uid-journey-migration-run") # attribute to a named journey
tracing.set_process_device("worker-3") # override the device tag
tracing.disable_process_journey() # or suppress entirely
CAMPUS_PROCESS_JOURNEY=off (also false/0/no) suppresses the
boot journey via the environment — useful for tests and operators.
propagation_headers()
returns what the next SDK call would send, which is handy for
assertions in tests.
Instrumenting a custom transport¶
The propagation headers are attached by the default
CampusRequest transport.
If you bring your own JsonClient
(see Custom clients), call
instrument_requests_session(session)
on the underlying requests.Session to get the same header
propagation, or attach propagation_headers()
to each request yourself.
Related seam: session ids¶
Session-id resolution (login/auth-session lanes) is a separate
injectable provider — see
Authentication › User sessions and
set_session_id_provider().
Reference¶
campus_python.tracing
¶
campus_python.tracing
Trace-context propagation for SDK HTTP calls (campus-api-python#92, parent campus#816 item 1).
When the host application is itself traced — its Flask app wired with campus.audit.middleware.init_app — every request it handles becomes an audit span. SDK calls made while handling that request should land as child spans of it, so the audit waterfall shows which host route fired each campus call.
In-request headers come from the installed request-context provider (set_request_context_provider) — flask_campus installs one that reads the flask.g state campus.audit.middleware stashes. The SDK itself is framework-free: with no provider installed, calls are treated as out-of-request and carry only the boot journey below.
Boot journeys (#114): every SDK call is a user request, whether that user is human or agent — so calls made outside any request context (startup, background threads) carry a process journey, one uid-journey-… per process boot, plus a process device tag (default: RAILWAY_DEPLOYMENT_ID) so boot spans attribute to the deploy that produced them. Inside a request the caller's own journey always wins; strict fallback only fills the out-of-request gap (campus-api-python#114 ruling, 2026-10-21). Set CAMPUS_PROCESS_JOURNEY=off to suppress (tests, operators); set_process_journey()/set_process_device() override in code.
PROCESS_JOURNEY_ENV = 'CAMPUS_PROCESS_JOURNEY'
module-attribute
¶
PROCESS_DEVICE_ENV = 'RAILWAY_DEPLOYMENT_ID'
module-attribute
¶
set_request_context_provider(provider: RequestContextProvider | None) -> None
¶
Install (or clear) the request-context propagation provider.
When installed, propagation_headers() takes the provider's output verbatim for in-request calls (the provider owns the header contract, including device gap-fill) and applies the process boot journey only when the provider reports no active request context (returns None). Pass None to revert to the built-in flask reader.
Source code in campus_python/tracing.py
210 211 212 213 214 215 216 217 218 219 220 221 222 | |
set_process_journey(journey_id: str | None = None, *, name: str | None = None) -> str
¶
Set this process's boot journey explicitly (#114).
Overrides CAMPUS_PROCESS_JOURNEY and re-enables journeys after disable_process_journey(). Mints a uid-journey-… when journey_id is omitted (keeping any previously minted/explicit id on later calls); a name labels the journey's cards (forwarded as X-Journey-Name, campus#952).
Returns:
| Type | Description |
|---|---|
str
|
The process journey id now in effect. |
Source code in campus_python/tracing.py
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
disable_process_journey() -> None
¶
Suppress the boot journey on out-of-request calls (#114).
For tests and operators who don't want the correlation; the process device tag is unaffected. set_process_journey() re-enables.
Source code in campus_python/tracing.py
159 160 161 162 163 164 165 166 167 168 | |
set_process_device(device_id: str) -> None
¶
Override this process's device tag (default: RAILWAY_DEPLOYMENT_ID).
Used as a fallback only — a request context with its own stashed device keeps it (campus#825/#837 semantics are unchanged).
Source code in campus_python/tracing.py
171 172 173 174 175 176 177 178 179 180 | |
process_device() -> str | None
¶
This process's device tag (default: RAILWAY_DEPLOYMENT_ID).
Public accessor for propagation hosts (flask_campus) implementing device gap-fill: a request that stashed no device of its own may present the process device. Never mints a journey (#114).
Source code in campus_python/tracing.py
97 98 99 100 101 102 103 104 | |
propagation_headers() -> dict[str, str]
¶
Headers to attach to an outbound SDK call from the active context.
Empty outside a request context until #114 — now: out-of-request calls carry the process boot journey (X-Journey-ID/X-Journey-Name) and the process device (X-Campus-Device, default RAILWAY_DEPLOYMENT_ID), so startup and background calls correlate under one journey per boot. Inside a request, the caller's own journey always wins — a journey-less request stays unjourneyed (strict MVP: the trace waterfall already correlates the chain) — and the process device only fills the gap when the request stashed none. The receiving campus service's tracing middleware turns the trace headers into a child span of the caller's span (#794, campus#816); the journeys middleware adopts X-Journey-ID so the child span joins the caller's action journey (campus#828), with X-Journey-Name carrying its human label (campus#952); the tracing middleware adopts X-Campus-Device for the child span's device tag when it knows nothing better (campus#837).
The in-request half resolves from the installed request-context provider (set_request_context_provider); with no provider there is no request-context half — every call is treated as out-of-request and carries only the boot journey (the former built-in flask reader was removed in 0.5.0; flask_campus installs the provider).
Source code in campus_python/tracing.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | |
instrument_requests_session(session: requests.Session) -> bool
¶
Wrap a requests.Session so its calls carry campus trace context.
Headers are computed at call time from the host's active request context, so a shared session stays correct under concurrent requests. Calls outside a request context (startup, background threads) carry the process boot journey and device (#114); calls inside a request never inherit it (the caller's journey, or none, wins).
Idempotent: re-instrumenting a session is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
The requests.Session to instrument. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the session was instrumented now, False if already done. |
Source code in campus_python/tracing.py
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | |