{"id":51482552,"url":"https://github.com/imp/trace-context-level3-rs","last_synced_at":"2026-07-07T03:01:17.130Z","repository":{"id":368418543,"uuid":"1283678283","full_name":"imp/trace-context-level3-rs","owner":"imp","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-30T12:31:56.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T14:22:13.360Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-29T06:23:17.000Z","updated_at":"2026-06-30T12:32:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/imp/trace-context-level3-rs","commit_stats":null,"previous_names":["imp/trace-context-level3-rs"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/imp/trace-context-level3-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imp%2Ftrace-context-level3-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imp%2Ftrace-context-level3-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imp%2Ftrace-context-level3-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imp%2Ftrace-context-level3-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imp","download_url":"https://codeload.github.com/imp/trace-context-level3-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imp%2Ftrace-context-level3-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35212581,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-07T02:00:07.222Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-07T03:01:16.222Z","updated_at":"2026-07-07T03:01:17.097Z","avatar_url":"https://github.com/imp.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# trace-context-level3-rs\n\nW3C Trace Context Level 3 implementation in Rust.\n\nParses and serializes the `traceparent` and `tracestate` HTTP headers, propagates\ncontext through Tower middleware, and exposes an axum extractor — all following the\n[W3C Trace Context specification](https://w3c.github.io/trace-context/).\n\n## Workspace\n\n| Crate | Description |\n|---|---|\n| [`trace-context-level3`] | Core types: `TraceParent`, `TraceState`, `TraceId`, `ParentId`, `TraceFlags` |\n| [`trace-context-level3-http`] | HTTP extraction and injection via `TraceContext` |\n| [`trace-context-level3-tower`] | Tower middleware (`TraceContextLayer`) and optional task-local storage |\n| [`trace-context-level3-axum`] | axum `FromRequestParts` extractor (`TraceContext`) |\n\n---\n\n## Core types — `trace-context-level3`\n\n```toml\n[dependencies]\ntrace-context-level3 = { git = \"https://github.com/imp/trace-context-level3-rs\" }\n```\n\n```rust\nuse trace_context_level3::{TraceParent, TraceState};\n\n// Parse a traceparent header value\nlet tp: TraceParent = \"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\"\n    .parse()\n    .unwrap();\n\nassert_eq!(tp.trace_id.to_string(), \"4bf92f3577b34da6a3ce929d0e0e4736\");\nassert_eq!(tp.parent_id.to_string(), \"00f067aa0ba902b7\");\nassert!(tp.is_sampled());\n\n// Parse and mutate tracestate\nlet mut state: TraceState = \"vendorname=opaquevalue\".parse().unwrap();\nstate.insert(\"myvendor\", \"data\").unwrap();\nassert_eq!(state.to_string(), \"myvendor=data,vendorname=opaquevalue\");\n```\n\nRandom ID generation requires the `rand` feature:\n\n```toml\ntrace-context-level3 = { ..., features = [\"rand\"] }\n```\n\n`serde` support (`Serialize`/`Deserialize` for all public types) is available as an optional feature:\n\n```toml\ntrace-context-level3 = { ..., features = [\"serde\"] }\n```\n\nTypes serialize as their canonical wire-format strings (`TraceParent`, `TraceState`, `TraceId`, `ParentId`) or as a plain `u8` (`TraceFlags`). `TraceContext` in `trace-context-level3-http` (enabled with `trace-context-level3-http/serde`) serializes as a struct with `\"traceparent\"` and `\"tracestate\"` string fields:\n\n```json\n{\n  \"traceparent\": \"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\",\n  \"tracestate\": \"vendor=value\"\n}\n```\n\n---\n\n## HTTP extraction — `trace-context-level3-http`\n\n```toml\n[dependencies]\ntrace-context-level3-http = { git = \"https://github.com/imp/trace-context-level3-rs\" }\n```\n\n```rust\nuse http::HeaderMap;\nuse trace_context_level3_http::TraceContext;\n\n// Extract\nlet mut headers = HeaderMap::new();\nheaders.insert(\"traceparent\",\n    \"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\".parse().unwrap());\n\nmatch TraceContext::extract(\u0026headers) {\n    Ok(Some(ctx)) =\u003e println!(\"trace-id: {}\", ctx.traceparent.trace_id),\n    Ok(None)      =\u003e println!(\"no traceparent header\"),\n    Err(e)        =\u003e println!(\"invalid traceparent: {e}\"),\n}\n\n// Inject\nlet mut outgoing = HeaderMap::new();\nctx.inject(\u0026mut outgoing);\n```\n\n`extract` returns `Result\u003cOption\u003cSelf\u003e, TraceParentError\u003e`:\n\n- `Ok(None)` — no `traceparent` header present\n- `Err(_)` — header present but invalid (e.g. multiple values, bad format)\n- `Ok(Some(ctx))` — valid context; malformed or missing `tracestate` is treated\n  leniently and silently dropped, per spec guidance\n\n---\n\n## Tower middleware — `trace-context-level3-tower`\n\n```toml\n[dependencies]\ntrace-context-level3-tower = { git = \"https://github.com/imp/trace-context-level3-rs\" }\n```\n\n`TraceContextLayer` intercepts every request:\n\n1. If a valid `traceparent` header arrives, it advances the span by generating a\n   fresh `parent-id` (preserving the `trace-id`) and stores the result in request\n   extensions.\n2. If the header is absent or malformed, it starts a new root span with random IDs.\n\n```rust\nuse tower::ServiceBuilder;\nuse trace_context_level3_tower::{TraceContextLayer, TraceResponseLayer};\n\nlet service = ServiceBuilder::new()\n    .layer(TraceContextLayer::new())   // outer: extracts/creates context\n    .layer(TraceResponseLayer::new())  // inner: adds Server-Timing to response\n    .service(inner);\n```\n\n### Response propagation (`Server-Timing`)\n\n`TraceResponseLayer` appends `Server-Timing: trace;desc=\u003ctraceparent\u003e` to every\nHTTP response, implementing the Level 3 response header. It must be placed after\n`TraceContextLayer` in the stack (inside it, closer to the handler).\n\nOn the receiving side, `extract_server_timing` reads the header back:\n\n```rust\nuse trace_context_level3_http::extract_server_timing;\n\nif let Some(tp) = extract_server_timing(response.headers()) {\n    println!(\"server span: {tp}\");\n}\n```\n\n### Task-local storage\n\nEnable the `task-local` feature to additionally store the context in a\n`tokio::task_local!` for the duration of each request future, so any code in the\ncall stack can read it without threading it through function arguments:\n\n```toml\ntrace-context-level3-tower = { ..., features = [\"task-local\"] }\n```\n\n```rust\nuse trace_context_level3_tower::{TraceContextLayer, TRACE_CONTEXT};\n\nlet layer = TraceContextLayer::new().enable_task_local();\n\n// Inside a handler called from within that layer:\nTRACE_CONTEXT.with(|ctx| println!(\"{}\", ctx.traceparent));\n```\n\n---\n\n## axum extractors — `trace-context-level3-axum`\n\n```toml\n[dependencies]\ntrace-context-level3-axum = { git = \"https://github.com/imp/trace-context-level3-rs\" }\n```\n\nTwo extractors are provided, both implementing `FromRequestParts`:\n\n### `TraceContext` — spec-compliant (recommended)\n\nWhen `traceparent` is absent or invalid a fresh root span is generated, matching\nthe W3C spec's guidance (\"receivers SHOULD start a new trace\"). Handlers always\nreceive a valid context and the extractor never rejects.\n\n```rust\nuse axum::{Router, routing::get};\nuse trace_context_level3_axum::TraceContext;\nuse trace_context_level3_tower::TraceContextLayer;\n\nasync fn handler(ctx: TraceContext) -\u003e String {\n    ctx.traceparent.to_string()\n}\n\nlet app = Router::new()\n    .route(\"/\", get(handler))\n    .layer(TraceContextLayer::new());\n```\n\n### `StrictTraceContext` — policy override\n\nRejects with `400 Bad Request` when `traceparent` is absent or invalid. Use this\nwhen your service must refuse requests that don't carry an upstream trace.\n\n```rust\nuse trace_context_level3_axum::StrictTraceContext;\n\nasync fn strict_handler(ctx: StrictTraceContext) -\u003e String {\n    ctx.traceparent.to_string()\n}\n```\n\nUse `Option\u003cStrictTraceContext\u003e` to distinguish \"context was propagated\" from \"was\nnot\" without rejecting the request — axum's blanket impl converts any rejection to\n`None`.\n\nBoth extractors check request extensions first: when `TraceContextLayer` is active\nit stores the already-advanced child span there, so neither extractor re-parses\nthe raw headers.\n\n---\n\n## Running the examples\n\n### In-process propagation demo\n\nShows both scenarios — incoming traceparent (child span) and no header (root span) —\nusing `tower::ServiceExt::oneshot` without a real network:\n\n```text\ncargo run --example propagation -p trace-context-level3-axum\n```\n\n### Live axum server\n\nStarts a server on `127.0.0.1:3000` and prints ready-to-use curl commands:\n\n```text\ncargo run --example server -p trace-context-level3-axum\n```\n\n```text\n# Fresh root span (no incoming header)\ncurl http://127.0.0.1:3000/\n\n# Child span — middleware advances parent-id, preserves trace-id\ncurl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \\\n     http://127.0.0.1:3000/\n\n# With tracestate\ncurl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \\\n     -H 'tracestate: vendor=value' \\\n     http://127.0.0.1:3000/\n```\n\n### Running the full test suite\n\n```text\nmise run ci\n```\n\nRuns `fmt-check`, `clippy`, `test`, and feature-variant tests in parallel.\n\n---\n\n## Spec compliance\n\n- `traceparent` version `00` parsing and serialisation\n- Unknown higher versions normalised to `v00` on parse (forward-compatibility rule)\n- `tracestate` entry validation, deduplication (first occurrence wins), and\n  truncation to 32 entries using the two-step spec algorithm\n- Lenient `tracestate` parsing across multiple header values (comma-separated lists\n  are merged; malformed entries are silently dropped)\n- `RANDOM_TRACE_ID` flag (`0x02`) set on freshly generated root spans\n- Multiple `traceparent` headers → `TraceParentError::MultipleValues`\n- Response propagation via `Server-Timing: trace;desc=\u003ctraceparent\u003e` (Level 3)\n- Optional `serde` feature: `Serialize`/`Deserialize` for all public types\n\n---\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or\n  \u003chttps://www.apache.org/licenses/LICENSE-2.0\u003e)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or\n  \u003chttps://opensource.org/licenses/MIT\u003e)\n\nat your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimp%2Ftrace-context-level3-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimp%2Ftrace-context-level3-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimp%2Ftrace-context-level3-rs/lists"}