{"id":51077057,"url":"https://github.com/yawn/tracing-wide","last_synced_at":"2026-06-23T15:02:36.696Z","repository":{"id":364809367,"uuid":"1264091168","full_name":"yawn/tracing-wide","owner":"yawn","description":"Log and catalogue wide events with tracing ","archived":false,"fork":false,"pushed_at":"2026-06-14T14:45:00.000Z","size":75,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T16:45:30.638Z","etag":null,"topics":["logging","tracing"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yawn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-09T14:51:15.000Z","updated_at":"2026-06-14T14:45:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yawn/tracing-wide","commit_stats":null,"previous_names":["yawn/tracing-wide"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/yawn/tracing-wide","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yawn%2Ftracing-wide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yawn%2Ftracing-wide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yawn%2Ftracing-wide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yawn%2Ftracing-wide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yawn","download_url":"https://codeload.github.com/yawn/tracing-wide/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yawn%2Ftracing-wide/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34694786,"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-06-23T02:00:07.161Z","response_time":65,"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":["logging","tracing"],"created_at":"2026-06-23T15:02:35.716Z","updated_at":"2026-06-23T15:02:36.691Z","avatar_url":"https://github.com/yawn.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `tracing-wide`\n\n[![CI](https://github.com/yawn/tracing-wide/actions/workflows/ci.yml/badge.svg)](https://github.com/yawn/tracing-wide/actions/workflows/ci.yml)\n\n\u003e [!CAUTION]\n\u003e This is *NOT* an official [tokio](https://tokio.rs) / [tokio-tracing](https://github.com/tokio-rs/tracing) product or associated crate.\n\nWide structured events for tokio [`tracing`](https://docs.rs/tracing): one struct\nper event, defined once, carrying every observability-relevant field for that\nsite. The message text stays static; all variance lives in typed fields. The\ncore is `no_std` and runs in WASM — everything else is opt-in behind features.\n\n```rust,ignore\nuse serde::Serialize;\nuse tracing_wide::{event, message};\n\n/// A request finished handling.                 // doc comment, recorded in the catalogue\n#[message(\n    msg = \"request completed\",                   // static text, the catalogue's unique key\n    level = info,                                // severity (default: info)\n    tags = [\"analytics\", \"api\"],                 // routing intent for potential subscribers\n    owner = \"platform\",                          // arbitrary metadata, recorded in the catalogue\n)]\n#[derive(Default, Serialize)]                    // Serialize: opt-in, per type\nstruct RequestCompleted {\n    /// Route template, e.g. `/users/:id`.       // field docs, recorded in the catalogue\n    route: \u0026'static str,                         // required: set at the event! site\n    #[field(unit = \"ms\")]                        // arbitrary field metadata, recorded in the catalogue\n    duration: u64,\n    region: Option\u003cString\u003e,                      // Option: may fill from the ambient span\n}\n\n/// A payment was captured.\n#[message(msg = \"payment captured\", level = warn, tags = [\"analytics\", \"persist\"])]\n#[derive(Serialize)]\nstruct PaymentCaptured {\n    amount_cents: u64,\n    currency: \u0026'static str,\n    #[deprecated = \"use amount_cents\"]           // recorded in the catalogue; warns at construction\n    amount: Option\u003cu64\u003e,\n}\n\n// Emit: required fields are checked here; an unset Option stays None\n// (and may fill from the surrounding span — see Instrument (ambient) autocapture).\nevent!(RequestCompleted { route: \"/users/:id\", duration: 12, ..Default::default() });\n```\n\nSee [`examples/`](tracing-wide/examples) for more examples.\n\n## Emit — `event!`\n\n`event!(RequestCompleted { .. })` builds the struct, fills any unset `Option`\nfields from the ambient span, fans it out to registered subscribers, then records\nit to `tracing` at the type's level. `#[message]` is the only supported way to make\na type emittable (the trait is pseudo-sealed); a field named `message` is rejected\n(tracing reserves it for the event text) and generics aren't allowed (a message is\na concrete `'static` type).\n\nFor spans, use stock `tracing::instrument` — tracing-wide ships no span macro.\n\n## Catalogue — every message a system can emit *(`catalogue`)*\n\nA catalogue enables stakeholder engagement: a serialized catalogue lets non-technical\nstakeholders see every message a system emits, so they can reason about it and\nbuild downstream recipients — analytics, BI, alerts — against a stable contract.\n\n`#[message]` auto-registers one descriptor per type; `catalogue::all()` walks\nthem. Each descriptor carries everything from the definitions above — `msg`,\n`level`, `tags`, `origin`, doc comments, `#[field(unit = ...)]` and other\nmetadata, and deprecations (field- or struct-level).\n\nWith the `serde` feature the descriptors `Serialize`, so a build step can dump a\nmanifest — the `catalogue-serde` example emits YAML keyed by `msg`. With the\n`facet` feature they derive `Facet`, so the same manifest can be produced\nthrough any facet serializer — the `catalogue-facet` example emits the *same* YAML\nvia `facet-yaml`. `level`, `origin`, and the `meta` maps render identically either\nway, so the two are interchangeable.\n\n- `msg` is the unique join key; `catalogue::duplicates()` flags collisions (run\n  it in a test).\n- Link-accurate: the catalogue holds exactly the messages of the crates linked\n  into the binary. Registration survives dead-code elimination, so it may\n  over-report what actually fires but never under-reports.\n\n### Origin — where a message is defined\n\n`origin()` is automatic provenance captured by `#[message]`: crate, module, file,\nline, column — no input, can't drift. It's object-safe, so a subscriber can\nattribute or route a `\u0026dyn Message` by its originating crate without a downcast,\nand it has a compact `Display`: `mycrate src/lib.rs:12:1`.\n\n## Instrument (ambient) autocapture *(`instrument`)*\n\n`RequestCompleted::region` is an `Option`, so when left unset at the `event!` site\nit fills at emit time from the surrounding span scope — by field name, innermost\nspan wins, across crate boundaries. Required (bare) fields never do this, and a\nfield already `Some` is never overwritten.\n\nStock `tracing::instrument` is the contribution surface — it records function\narguments and `fields(..)` by default; install `instrument::layer()` on a\n`tracing_subscriber::registry()` stack to capture them. With no layer or no\ncurrent span the lookup simply misses; `event!` never fails on ambient state.\n\n\u003e Name-based by design: an `Option` field fills from *any* same-named span field\n\u003e in scope, including spans the message author doesn't own — name fields\n\u003e deliberately (`token`, `id`, `user` collide easily). The layer also retains\n\u003e every span field for the span's lifetime; keep that in mind for sensitive data.\n\n## Subscribe to wide events *(`subscriber`)*\n\nWith the `subscriber` feature, `event!` hands each message to every registered\nsubscriber as a typed `\u0026dyn Message` *before* the tracing handoff — useful for\nstoring events in a database or forwarding to another subsystem (especially in\nfrontends). A sink can stay generic through the object-safe accessors, or\ndowncast to the concrete type via `m.as_any()`.\n\n```rust,ignore\nfn on_message(\u0026self, m: \u0026dyn Message) {\n    if let Some(body) = m.as_serialize() {     // Some iff the type derives Serialize\n        // body is a \u0026dyn erased_serde::Serialize — serialize it with any format\n    }\n}\n```\n\nSerialization is opt-in per type (`#[derive(Serialize)]`, as on both messages\nabove) and never a bound on `Message`; a type that doesn't derive it yields\n`None`. Register sinks into `Subscribers`, then `install()` once — set-once, like\ntracing's global default.\n\n\u003e A panicking `on_message` panics the `event!` call site — same posture as\n\u003e tracing itself, but a logging sink can crash the app.\n\n### Routing on message types via Tags\n\n`RequestCompleted` is tagged `analytics` + `api`; `PaymentCaptured`, `analytics`\n+ `persist`. Tags are *where to send*, not *where from*: a subscriber routes on\nthem with no downcast, and one message can fan out to several subsystems.\n\n```rust,ignore\nfn on_message(\u0026self, m: \u0026dyn Message) {\n    if m.tags().contains(\u0026\"analytics\") { /* forward */ }\n}\n```\n\nTags are sorted, deduped and lowercased at compile time, and lowercase is\nenforced. Crate-prefix namespacing is unnecessary — `origin()` already carries\nthe crate.\n\n### Routing on messages data via reflect *(`facet`)*\n\nWhen the decision lives in the *data* (only the `eu` region, only\npayments over a threshold), it's per-event and can't be a tag. With the `facet`\nfeature, `as_facet()` hands back a `facet::Peek` over the live body, so a\nsubscriber reads a field *by name* and filters on its value — no downcast, no\nper-type match.\n\n```rust,ignore\nfn on_message(\u0026self, m: \u0026dyn Message) {\n    let Some(peek) = m.as_facet() else { return };   // Some iff the type derives Facet\n    let Ok(body) = peek.into_struct() else { return };\n\n    // pull one field out by name and filter on its live value\n    if body.field_by_name(\"region\").ok().and_then(|f| f.as_str()) == Some(\"eu\") {\n        // matched on data, not on a tag — and you can walk every field from here\n    }\n}\n```\n\nReflection is opt-in per type (`#[derive(Facet)]`) and never a bound on `Message`;\na type that doesn't derive it yields `None` — the same shape as `as_serialize`.\nWhere serialization forwards the body whole, reflection pulls it apart. See the\n`subscriber-facet` example.\n\n\u003e The `facet` feature is unstable: [facet](https://docs.rs/facet) is pre-1.0 and\n\u003e every minor is a breaking change, so expect churn. It's re-exported as\n\u003e `tracing_wide::facet` so a subscriber names `Peek`/`Facet` through the exact\n\u003e version this crate compiled against.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyawn%2Ftracing-wide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyawn%2Ftracing-wide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyawn%2Ftracing-wide/lists"}