merni 0.1.3

A low overhead metrics implementation.
# Mérni


> Hungarian: to measure
> [wiktionary]https://en.wiktionary.org/wiki/m%C3%A9r#Hungarian

An opinionated metrics crate with low overhead, and thread-local aggregation.

## Quick start


With the `"datadog"` feature enabled, you can quickly initialize a global
metrics dispatcher, which will periodically flush metrics to Datadog.

```rust,ignore
use std::time::Duration;

#[tokio::main]

async fn main() {
    // Or `None`, which defaults to using the `DD_API_KEY` env.
    let flusher = merni::datadog("datadog API key").try_init().unwrap();

    // Later on, anywhere in your code:
    merni::counter!("some.counter": 1);

    // It is also possible to add tags, as well as units.
    // In this example, the value is converted to seconds, which is also used as unit.
    merni::distribution!("with.tags"@s: Duration::from_millis(10), "tag" => "value");

    // `None` means no timeout waiting for the flush.
    flusher.flush(None).await.unwrap_err(); // expecting an error as we have an invalid API key :-)
}
```

## Details


Mérni consists of some layers of abstraction.
At the core, there is the [`Dispatcher`], which dispatches metrics to a generic [`Sink`].
The dispatcher can then be registered globally using [`set_global_dispatcher`].

One of the sinks is the [`ThreadLocalAggregator`], enabled using the `"aggregator"` feature.
This specialized sink does thread-local pre-aggregation, before periodically
flushing the fully aggregated metrics to yet another generic [`AggregationSink`].

It is possible to drop down to the most basic APIs, and implement the [`Sink`]
trait directly.

```rust
struct PrintlnSink;
impl merni::Sink for PrintlnSink {
    fn emit(&self, metric: merni::Metric) {
        let mut tags = metric.tags().peekable();
        let tags = if tags.peek().is_some() {
            let mut tags_str = String::new();
            for (i, (k,v)) in tags.enumerate() {
                tags_str.push(if i == 0 { '[' } else { ',' });
                tags_str.push_str(k);
                tags_str.push(':');
                tags_str.push_str(v);
            }
            tags_str.push(']');
            tags_str
        } else {
            String::new()
        };
        println!("{}{tags}: {}", metric.key(), metric.value().get());
    }
}

let dispatcher = merni::Dispatcher::new(PrintlnSink);
merni::set_global_dispatcher(dispatcher).expect("global recorder already registered");

merni::counter!("some.counter": 1);
merni::counter!("with.tags": 1, "tag" => "value");
```