meta-tracing 0.1.0

Flexible metadata collection layered on top of the tracing crate. Accumulate structured sections, timings, and issues during processing and emit them as JSON alongside the usual tracing spans.
Documentation

meta-tracing

crates.io docs.rs License

A small companion to the tracing crate that collects structured metadata as you go and hands you a single JSON-serializable record at the end.

Why

tracing is great at producing a stream of events for humans, log aggregators, and OTel pipelines. But sometimes you also want a single structured object describing what a unit of work did — sizes, timings, named summary sections, warnings — to return alongside an API response, attach to a job record, or write to S3.

meta-tracing runs alongside tracing. Every call also emits a span or event, so your logs stay consistent; and at the end you call .build() and get a CollectedMeta value you can serialize.

At a glance

MetaCollector  ──►  (sections + issues + timings)  ──►  CollectedMeta { JSON }

Quick start

# Cargo.toml
[dependencies]
meta-tracing = "0.1"
serde        = { version = "1", features = ["derive"] }
serde_json   = "1"
use meta_tracing::MetaCollector;
use serde::Serialize;

#[derive(Serialize)]
struct LoadStats { input_rows: usize, output_rows: usize }

let mut meta = MetaCollector::new();
meta.set_input_rows(1_000);

// Timed section — opens a tracing span, captures elapsed_ms when finished.
{
    let section = meta.timed_section("load");
    // ... do work ...
    section.finish_with_data(&LoadStats { input_rows: 1_000, output_rows: 950 });
}

// Free-form warnings; prefix with [WARN] / [ERROR] to drive log levels.
meta.add_issue("[WARN] 50 rows dropped");
meta.set_output_rows(950);

let result = meta.build();
println!("{}", serde_json::to_string_pretty(&result).unwrap());

The output is a CollectedMeta with sections, issues, input_rows, output_rows, and processing_time_ms — all skip-serialize-if-empty so the JSON stays tight.

What's in the box

  • MetaCollector — the builder you hand around in your processing code.
  • Sections (add_section, merge_section) — named JSON blobs from any Serialize type. merge_section deep-merges into an existing object, which is useful when multiple stages contribute to the same section (e.g. "validation").
  • Issues (add_issue, add_issues) — free-form strings; [WARN] / [ERROR] prefixes drive the corresponding tracing level, and CollectedMeta::warning_count() / error_count() count them.
  • Row tracking (set_input_rows, set_output_rows, set_rows) — common enough to deserve dedicated fields.
  • Timed sections (timed_section returns a TimedSection guard) — opens an INFO meta_section span and, on finish*, writes { elapsed_ms, data? } into the section under that name.

Convenience free functions (record_input_rows, record_section, record_issue, …) take Option<&mut MetaCollector> so optional collection sites can stay one-liner.

What this is not

  • Not a tracing layer / subscriber. It calls into tracing but doesn't hook the dispatch — bring your own subscriber.
  • Not opinionated about transport. CollectedMeta implements Serialize / Deserialize; do what you like with the JSON.
  • Not a metrics library. If you need counters, histograms, and an exporter, look at metrics or opentelemetry.

License

Dual-licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.