metrique 0.1.25

Library for generating wide event metrics
Documentation
# Testing and Debugging

## Testing emitted metrics

### Quick assertions with `test_metric`

For simple tests where you just want to verify field values without setting up a sink, [`test_metric`] closes a metric struct and returns a [`TestEntry`] you can assert against directly:

```rust,ignore
use metrique::test_util::test_metric;

let entry = test_metric(RequestMetrics { operation: "SayHello", number_of_ducks: 10 });
assert_eq!(entry.metrics["NumberOfDucks"], 10);
```

For tests that need to verify the full emit pipeline, use `test_entry_sink` below.

### Testing with `test_entry_sink`

`metrique` provides `test_entry_sink` which allows introspecting the entries that are emitted (without needing to read EMF directly). You can use this functionality in combination with the [`TestEntrySink`] to test that you are emitting the metrics that you expect:

> Note: enable the `test-util` feature of `metrique` to enable test utility features.

```rust,no_run

use metrique::unit_of_work::metrics;

use metrique::test_util::{self, TestEntrySink};

#[metrics(rename_all = "PascalCase")]
struct RequestMetrics {
    operation: &'static str,
    number_of_ducks: usize
}

#[test]
fn test_metrics () {
    let TestEntrySink { inspector, sink } = test_util::test_entry_sink();
    let metrics = RequestMetrics {
        operation: "SayHello",
        number_of_ducks: 10
    }.append_on_drop(sink);

    // In a real application, you would run some API calls, etc.

    let entries = inspector.entries();
    assert_eq!(entries[0].values["Operation"], "SayHello");
    assert_eq!(entries[0].metrics["NumberOfDucks"], 10);
}
```

There are two ways to control the queue:

1. Pass the queue explicitly when constructing your metric object, e.g. by passing it into `init` (as done above)
2. Use the test-queue functionality provided out-of-the-box by global entry queues:

```rust
use metrique::writer::GlobalEntrySink;
use metrique::ServiceMetrics;
use metrique::test_util::{self, TestEntrySink};

let TestEntrySink { inspector, sink } = test_util::test_entry_sink();
let _guard = ServiceMetrics::set_test_sink(sink);
```

See `examples/testing.rs` and `examples/testing-global-queues.rs` for more detailed examples.

### Lazy sink resolution with `sink_or_discard`

`sink_or_discard()` returns a lazily-resolved sink that checks for an attached sink each time an entry is appended. If a sink is available at that point the entry is forwarded to it; otherwise the entry is silently discarded.

This is useful when the code emitting metrics doesn't control when (or whether) a sink is attached. Metrics will automatically flow to the real sink once one is available, and are silently discarded otherwise.

```rust,no_run
use metrique::unit_of_work::metrics;
use metrique::ServiceMetrics;
use metrique::writer::GlobalEntrySink;

#[metrics(rename_all = "PascalCase")]
struct RequestMetrics {
    operation: &'static str,
}

#[test]
fn test_that_does_not_care_about_metrics() {
    let mut metrics = RequestMetrics {
        operation: "SayHello",
    }.append_on_drop(ServiceMetrics::sink_or_discard());

    // ... run your test logic ...
}
```

Unlike `set_test_sink(DevNullSink::boxed())` or passing a `DevNullSink` explicitly,
`sink_or_discard()` resolves lazily on every append: if a real sink is attached later,
entries will flow to it automatically. `DevNullSink` permanently discards all entries
and cannot be swapped for a real sink after creation.

## Debugging common issues

### Human-readable output with `LocalFormat`

[`LocalFormat`] renders metric entries in a readable
format (pretty, JSON, or markdown table) instead of EMF. Swap it in during local
development to see what your code is emitting:

```rust,no_run
use metrique::ServiceMetrics;
use metrique::local::{LocalFormat, OutputStyle};
use metrique::writer::{AttachGlobalEntrySinkExt, FormatExt, GlobalEntrySink};

let _handle = ServiceMetrics::attach_to_stream(
    LocalFormat::new(OutputStyle::Pretty)
        .output_to(std::io::stderr()),
);
```

Example output:

```text
---
  TotalTime: 302.457ms
  Success: 1
  Failure: 0
  SegmentIndex: 180
  UncompressedSize: 20.97MB
  CompressedSize: 7.97MB
  InvalidFileHeader: 1
  Gzip.Time: 113.549ms
  Gzip.Success: 1
  Gzip.Failure: 0
  S3Upload.Time: 188.904ms
  S3Upload.Success: 1
  S3Upload.Failure: 0
```

### No entries in the log

If you see empty files e.g. "service_log.{date}.log", this could be because your entries are invalid and being dropped by `metrique-writer`. This will occur if your entry is invalid (e.g. if you have two fields with the same name). Enable tracing logs to see the errors.

```rust
# #[allow(clippy::needless_doctest_main)]
fn main() {
    tracing_subscriber::fmt::init();
}
```

[`LocalFormat`]: https://docs.rs/metrique/latest/metrique/local/struct.LocalFormat.html
[`test_metric`]: https://docs.rs/metrique/latest/metrique/test_util/fn.test_metric.html
[`TestEntry`]: https://docs.rs/metrique/latest/metrique/test_util/struct.TestEntry.html
[`TestEntrySink`]: https://docs.rs/metrique/latest/metrique/test_util/struct.TestEntrySink.html