cadence_with_flush/
builder.rs

1// Cadence - An extensible Statsd client for Rust!
2//
3// Copyright 2018 Philip Jenvey <pjenvey@mozilla.com>
4// Copyright 2018-2021 Nick Pillitteri
5//
6// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
7// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
9// option. This file may not be copied, modified, or distributed
10// except according to those terms.
11
12use crate::client::{MetricBackend, StatsdClient};
13use crate::types::{Metric, MetricError, MetricResult};
14use std::fmt::{self, Write};
15use std::marker::PhantomData;
16
17/// Type of metric that knows how to display itself
18#[derive(Debug, Clone, Copy)]
19enum MetricType {
20    Counter,
21    Timer,
22    Gauge,
23    Meter,
24    Histogram,
25    Set,
26    Distribution,
27}
28
29impl fmt::Display for MetricType {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match *self {
32            MetricType::Counter => "c".fmt(f),
33            MetricType::Timer => "ms".fmt(f),
34            MetricType::Gauge => "g".fmt(f),
35            MetricType::Meter => "m".fmt(f),
36            MetricType::Histogram => "h".fmt(f),
37            MetricType::Set => "s".fmt(f),
38            MetricType::Distribution => "d".fmt(f),
39        }
40    }
41}
42
43/// Holder for primitive metric values that knows how to display itself
44///
45/// This struct is internal to how various types that are valid for each type
46/// of metric (e.g. types for which `ToCounterValue`, `ToTimerValue`, etc) are
47/// implemented but is exposed for documentation purposes and advanced use cases.
48///
49/// Typical use of Cadence shouldn't require interacting with this type.
50#[derive(Debug, Clone)]
51pub enum MetricValue {
52    Signed(i64),
53    PackedSigned(Vec<i64>),
54    Unsigned(u64),
55    PackedUnsigned(Vec<u64>),
56    Float(f64),
57    PackedFloat(Vec<f64>),
58}
59
60impl MetricValue {
61    fn count(&self) -> usize {
62        match self {
63            Self::PackedSigned(x) => x.len(),
64            Self::PackedUnsigned(x) => x.len(),
65            Self::PackedFloat(x) => x.len(),
66            _ => 1,
67        }
68    }
69}
70
71fn write_value<T>(f: &mut fmt::Formatter<'_>, vals: &[T]) -> fmt::Result
72where
73    T: fmt::Display,
74{
75    for (i, value) in vals.iter().enumerate() {
76        if i > 0 {
77            f.write_char(':')?;
78        }
79        value.fmt(f)?;
80    }
81
82    fmt::Result::Ok(())
83}
84
85impl fmt::Display for MetricValue {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match &*self {
88            MetricValue::Signed(v) => v.fmt(f),
89            MetricValue::PackedSigned(v) => write_value(f, v),
90            MetricValue::Unsigned(v) => v.fmt(f),
91            MetricValue::PackedUnsigned(v) => write_value(f, v),
92            MetricValue::Float(v) => v.fmt(f),
93            MetricValue::PackedFloat(v) => write_value(f, v),
94        }
95    }
96}
97
98#[derive(Debug, Clone)]
99pub(crate) struct MetricFormatter<'a> {
100    prefix: &'a str,
101    key: &'a str,
102    val: MetricValue,
103    type_: MetricType,
104    tags: Vec<(Option<&'a str>, &'a str)>,
105    base_size: usize,
106    kv_size: usize,
107}
108
109impl<'a> MetricFormatter<'a> {
110    const TAG_PREFIX: &'static str = "|#";
111
112    pub(crate) fn counter(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
113        Self::from_val(prefix, key, val, MetricType::Counter)
114    }
115
116    pub(crate) fn timer(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
117        Self::from_val(prefix, key, val, MetricType::Timer)
118    }
119
120    pub(crate) fn gauge(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
121        Self::from_val(prefix, key, val, MetricType::Gauge)
122    }
123
124    pub(crate) fn meter(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
125        Self::from_val(prefix, key, val, MetricType::Meter)
126    }
127
128    pub(crate) fn histogram(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
129        Self::from_val(prefix, key, val, MetricType::Histogram)
130    }
131
132    pub(crate) fn distribution(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
133        Self::from_val(prefix, key, val, MetricType::Distribution)
134    }
135
136    pub(crate) fn set(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
137        Self::from_val(prefix, key, val, MetricType::Set)
138    }
139
140    #[rustfmt::skip]
141    fn from_val(prefix: &'a str, key: &'a str, val: MetricValue, type_: MetricType) -> Self {
142        let value_count = val.count();
143        MetricFormatter {
144            prefix,
145            key,
146            type_,
147            val,
148            tags: Vec::new(),
149            // keep track of the number of bytes we expect to use for both the key-value
150            // part of the tags for this metric as well as the base metric (name, value,
151            // and type). incrementing these counters when tags are added saves us from
152            // having to loop through the tags to count the expected number of bytes to
153            // allocate.
154            kv_size: 0,
155            base_size: prefix.len() + key.len() + 1 /* : */ + 10 * value_count /* value(s) */ + 1 /* | */ + 2, /* type */
156        }
157    }
158
159    fn with_tag(&mut self, key: &'a str, value: &'a str) {
160        self.tags.push((Some(key), value));
161        self.kv_size += key.len() + 1 /* : */ + value.len();
162    }
163
164    fn with_tag_value(&mut self, value: &'a str) {
165        self.tags.push((None, value));
166        self.kv_size += value.len();
167    }
168
169    fn write_base_metric(&self, out: &mut String) {
170        let _ = write!(out, "{}{}:{}|{}", self.prefix, self.key, self.val, self.type_);
171    }
172
173    fn write_tags(&self, out: &mut String) {
174        if !self.tags.is_empty() {
175            out.push_str(Self::TAG_PREFIX);
176            for (i, &(key, value)) in self.tags.iter().enumerate() {
177                if i > 0 {
178                    out.push(',');
179                }
180                if let Some(key) = key {
181                    out.push_str(key);
182                    out.push(':');
183                }
184                out.push_str(value);
185            }
186        }
187    }
188
189    fn tag_size_hint(&self) -> usize {
190        if self.tags.is_empty() {
191            return 0;
192        }
193
194        // prefix, keys and values, commas
195        Self::TAG_PREFIX.len() + self.kv_size + self.tags.len() - 1
196    }
197
198    pub(crate) fn format(&self) -> String {
199        let size_hint = self.base_size + self.tag_size_hint();
200        let mut metric_string = String::with_capacity(size_hint);
201        self.write_base_metric(&mut metric_string);
202        self.write_tags(&mut metric_string);
203        metric_string
204    }
205}
206
207/// Internal state of a `MetricBuilder`
208///
209/// The builder can either be in the process of formatting a metric to send
210/// via a client or it can be simply holding on to an error that it will be
211/// dealt with when `.try_send()` or `.send()` is finally invoked.
212#[derive(Debug)]
213enum BuilderRepr<'m, 'c> {
214    Success(MetricFormatter<'m>, &'c StatsdClient),
215    Error(MetricError, &'c StatsdClient),
216}
217
218/// Builder for adding tags to in-progress metrics.
219///
220/// This builder adds tags, key-value pairs or just values, to a metric that
221/// was previously constructed by a call to a method on `StatsdClient`. The
222/// tags are added to metrics and sent via the client when `MetricBuilder::send()`
223/// or `MetricBuilder::try_send()`is invoked. Any errors encountered constructing,
224/// validating, or sending the metrics will be propagated and returned when those
225/// methods are finally invoked.
226///
227/// Currently, only Datadog style tags are supported. For more information on the
228/// exact format used, see the
229/// [Datadog docs](https://docs.datadoghq.com/developers/dogstatsd/#datagram-format).
230///
231/// Adding tags to a metric via this builder will typically result in one or more
232/// extra heap allocations.
233///
234/// NOTE: The only way to instantiate an instance of this builder is via methods in
235/// in the `StatsdClient` client.
236///
237/// # Examples
238///
239/// ## `.try_send()`
240///
241/// An example of how the metric builder is used with a `StatsdClient` instance
242/// is given below.
243///
244/// ```
245/// use cadence::prelude::*;
246/// use cadence::{StatsdClient, NopMetricSink, Metric};
247///
248/// let client = StatsdClient::from_sink("some.prefix", NopMetricSink);
249/// let res = client.count_with_tags("some.key", 1)
250///    .with_tag("host", "app11.example.com")
251///    .with_tag("segment", "23")
252///    .with_tag_value("beta")
253///    .try_send();
254///
255/// assert_eq!(
256///     concat!(
257///         "some.prefix.some.key:1|c|#",
258///         "host:app11.example.com,",
259///         "segment:23,",
260///         "beta"
261///     ),
262///     res.unwrap().as_metric_str()
263/// );
264/// ```
265///
266/// In this example, two key-value tags and one value tag are added to the
267/// metric before it is finally sent to the Statsd server.
268///
269/// ## `.send()`
270///
271/// An example of how the metric builder is used with a `StatsdClient` instance
272/// when using the "quiet" method is given below.
273///
274/// ```
275/// use cadence::prelude::*;
276/// use cadence::{StatsdClient, NopMetricSink, Metric};
277///
278/// let client = StatsdClient::builder("some.prefix", NopMetricSink)
279///     .with_error_handler(|e| eprintln!("metric error: {}", e))
280///     .build();
281/// client.count_with_tags("some.key", 1)
282///    .with_tag("host", "app11.example.com")
283///    .with_tag("segment", "23")
284///    .with_tag_value("beta")
285///    .send();
286/// ```
287///
288/// Note that nothing is returned from the `.send()` method. Any errors encountered
289/// in this case will be passed to the error handler we registered.
290#[must_use = "Did you forget to call .send() after adding tags?"]
291#[derive(Debug)]
292pub struct MetricBuilder<'m, 'c, T>
293where
294    T: Metric + From<String>,
295{
296    repr: BuilderRepr<'m, 'c>,
297    type_: PhantomData<T>,
298}
299
300impl<'m, 'c, T> MetricBuilder<'m, 'c, T>
301where
302    T: Metric + From<String>,
303{
304    pub(crate) fn from_fmt(formatter: MetricFormatter<'m>, client: &'c StatsdClient) -> Self {
305        MetricBuilder {
306            repr: BuilderRepr::Success(formatter, client),
307            type_: PhantomData,
308        }
309    }
310
311    pub(crate) fn from_error(err: MetricError, client: &'c StatsdClient) -> Self {
312        MetricBuilder {
313            repr: BuilderRepr::Error(err, client),
314            type_: PhantomData,
315        }
316    }
317
318    /// Add a key-value tag to this metric.
319    ///
320    /// # Example
321    ///
322    /// ```
323    /// use cadence::prelude::*;
324    /// use cadence::{StatsdClient, NopMetricSink, Metric};
325    ///
326    /// let client = StatsdClient::from_sink("some.prefix", NopMetricSink);
327    /// let res = client.count_with_tags("some.key", 1)
328    ///    .with_tag("user", "authenticated")
329    ///    .try_send();
330    ///
331    /// assert_eq!(
332    ///    "some.prefix.some.key:1|c|#user:authenticated",
333    ///    res.unwrap().as_metric_str()
334    /// );
335    /// ```
336    pub fn with_tag(mut self, key: &'m str, value: &'m str) -> Self {
337        if let BuilderRepr::Success(ref mut formatter, _) = self.repr {
338            formatter.with_tag(key, value);
339        }
340        self
341    }
342
343    /// Add a value tag to this metric.
344    ///
345    /// # Example
346    ///
347    /// ```
348    /// use cadence::prelude::*;
349    /// use cadence::{StatsdClient, NopMetricSink, Metric};
350    ///
351    /// let client = StatsdClient::from_sink("some.prefix", NopMetricSink);
352    /// let res = client.count_with_tags("some.key", 4)
353    ///    .with_tag_value("beta-testing")
354    ///    .try_send();
355    ///
356    /// assert_eq!(
357    ///    "some.prefix.some.key:4|c|#beta-testing",
358    ///    res.unwrap().as_metric_str()
359    /// );
360    /// ```
361    pub fn with_tag_value(mut self, value: &'m str) -> Self {
362        if let BuilderRepr::Success(ref mut formatter, _) = self.repr {
363            formatter.with_tag_value(value);
364        }
365        self
366    }
367
368    /// Add tags to this metric.
369    pub(crate) fn with_tags<V>(mut self, tags: V) -> Self
370    where
371        V: IntoIterator<Item = (Option<&'m str>, &'m str)>,
372    {
373        if let BuilderRepr::Success(ref mut formatter, _) = self.repr {
374            for tag in tags.into_iter() {
375                match tag {
376                    (Some(key), value) => formatter.with_tag(key, value),
377                    (None, value) => formatter.with_tag_value(value),
378                }
379            }
380        }
381
382        self
383    }
384
385    /// Send a metric using the client that created this builder.
386    ///
387    /// Note that the builder is consumed by this method and thus `.try_send()`
388    /// can only be called a single time per builder.
389    ///
390    /// # Example
391    ///
392    /// ```
393    /// use cadence::prelude::*;
394    /// use cadence::{StatsdClient, NopMetricSink, Metric};
395    ///
396    /// let client = StatsdClient::from_sink("some.prefix", NopMetricSink);
397    /// let res = client.gauge_with_tags("some.key", 7)
398    ///    .with_tag("test-segment", "12345")
399    ///    .try_send();
400    ///
401    /// assert_eq!(
402    ///    "some.prefix.some.key:7|g|#test-segment:12345",
403    ///    res.unwrap().as_metric_str()
404    /// );
405    /// ```
406    pub fn try_send(self) -> MetricResult<T> {
407        match self.repr {
408            BuilderRepr::Error(err, _) => Err(err),
409            BuilderRepr::Success(ref formatter, client) => {
410                let metric = T::from(formatter.format());
411                client.send_metric(&metric)?;
412                Ok(metric)
413            }
414        }
415    }
416
417    /// Send a metric using the client that created this builder, discarding
418    /// successful results and invoking a custom handler for error results.
419    ///
420    /// By default, if no handler is given, a "no-op" handler is used that
421    /// simply discards all errors. If this isn't desired, a custom handler
422    /// should be supplied when creating a new `StatsdClient` instance.
423    ///
424    /// Note that the builder is consumed by this method and thus `.send()`
425    /// can only be called a single time per builder.
426    ///
427    /// # Example
428    ///
429    /// ```
430    /// use cadence::prelude::*;
431    /// use cadence::{StatsdClient, MetricError, NopMetricSink};
432    ///
433    /// fn my_handler(err: MetricError) {
434    ///     println!("Metric error: {}", err);
435    /// }
436    ///
437    /// let client = StatsdClient::builder("some.prefix", NopMetricSink)
438    ///     .with_error_handler(my_handler)
439    ///     .build();
440    ///
441    /// client.gauge_with_tags("some.key", 7)
442    ///    .with_tag("region", "us-west-1")
443    ///    .send();
444    /// ```
445    pub fn send(self) {
446        match self.repr {
447            BuilderRepr::Error(err, client) => client.consume_error(err),
448            BuilderRepr::Success(_, client) => {
449                if let Err(e) = self.try_send() {
450                    client.consume_error(e);
451                }
452            }
453        }
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use super::{MetricBuilder, MetricFormatter, MetricValue};
460    use crate::client::StatsdClient;
461    use crate::sinks::NopMetricSink;
462    use crate::test::ErrorMetricSink;
463    use crate::types::Counter;
464    use std::sync::atomic::{AtomicU64, Ordering};
465    use std::sync::Arc;
466
467    #[test]
468    fn test_metric_formatter_tag_size_hint_no_tags() {
469        let fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(1));
470        assert_eq!(0, fmt.tag_size_hint());
471    }
472
473    #[test]
474    fn test_metric_formatter_tag_size_hint_value() {
475        let mut fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(1));
476        fmt.with_tag_value("test");
477
478        assert_eq!(6, fmt.tag_size_hint());
479    }
480
481    #[test]
482    fn test_metric_formatter_tag_size_hint_key_value() {
483        let mut fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(1));
484        fmt.with_tag("host", "web");
485        fmt.with_tag("user", "123");
486
487        assert_eq!(19, fmt.tag_size_hint());
488    }
489
490    #[test]
491    fn test_metric_formatter_counter_no_tags() {
492        let fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(4));
493        assert_eq!("prefix.some.key:4|c", &fmt.format());
494    }
495
496    #[test]
497    fn test_metric_formatter_counter_with_tags() {
498        let mut fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(4));
499        fmt.with_tag("host", "app03.example.com");
500        fmt.with_tag("bucket", "2");
501        fmt.with_tag_value("beta");
502
503        assert_eq!(
504            "prefix.some.key:4|c|#host:app03.example.com,bucket:2,beta",
505            &fmt.format()
506        );
507    }
508
509    #[test]
510    fn test_metric_formatter_timer_no_tags() {
511        let fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::Unsigned(21));
512
513        assert_eq!("prefix.some.method:21|ms", &fmt.format());
514    }
515
516    #[test]
517    fn test_metric_formatter_timer_no_tags_multiple_values() {
518        let fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::PackedUnsigned(vec![21, 22, 23]));
519
520        assert_eq!("prefix.some.method:21:22:23|ms", &fmt.format());
521    }
522
523    #[test]
524    fn test_metric_formatter_timer_with_tags() {
525        let mut fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::Unsigned(21));
526        fmt.with_tag("app", "metrics");
527        fmt.with_tag_value("async");
528
529        assert_eq!("prefix.some.method:21|ms|#app:metrics,async", &fmt.format());
530    }
531
532    #[test]
533    fn test_metric_formatter_timer_with_tags_multiple_values() {
534        let mut fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::PackedUnsigned(vec![21, 22, 23]));
535        fmt.with_tag("app", "metrics");
536        fmt.with_tag_value("async");
537
538        assert_eq!("prefix.some.method:21:22:23|ms|#app:metrics,async", &fmt.format());
539    }
540
541    #[test]
542    fn test_metric_formatter_gauge_no_tags() {
543        let fmt = MetricFormatter::gauge("prefix.", "num.failures", MetricValue::Unsigned(7));
544
545        assert_eq!("prefix.num.failures:7|g", &fmt.format());
546    }
547
548    #[test]
549    fn test_metric_formatter_gauge_with_tags() {
550        let mut fmt = MetricFormatter::gauge("prefix.", "num.failures", MetricValue::Unsigned(7));
551        fmt.with_tag("window", "300");
552        fmt.with_tag_value("best-effort");
553
554        assert_eq!("prefix.num.failures:7|g|#window:300,best-effort", &fmt.format());
555    }
556
557    #[test]
558    fn test_metric_formatter_meter_no_tags() {
559        let fmt = MetricFormatter::meter("prefix.", "user.logins", MetricValue::Unsigned(3));
560
561        assert_eq!("prefix.user.logins:3|m", &fmt.format());
562    }
563
564    #[test]
565    fn test_metric_formatter_meter_with_tags() {
566        let mut fmt = MetricFormatter::meter("prefix.", "user.logins", MetricValue::Unsigned(3));
567        fmt.with_tag("user-type", "verified");
568        fmt.with_tag_value("bucket1");
569
570        assert_eq!("prefix.user.logins:3|m|#user-type:verified,bucket1", &fmt.format());
571    }
572
573    #[test]
574    fn test_metric_formatter_histogram_no_tags() {
575        let fmt = MetricFormatter::histogram("prefix.", "num.results", MetricValue::Unsigned(44));
576
577        assert_eq!("prefix.num.results:44|h", &fmt.format());
578    }
579
580    #[test]
581    fn test_metric_formatter_histogram_no_tags_multiple_values() {
582        let fmt = MetricFormatter::histogram("prefix.", "num.results", MetricValue::PackedUnsigned(vec![44, 45, 46]));
583
584        assert_eq!("prefix.num.results:44:45:46|h", &fmt.format());
585    }
586
587    #[test]
588    fn test_metric_formatter_histogram_with_tags() {
589        let mut fmt = MetricFormatter::histogram("prefix.", "num.results", MetricValue::Unsigned(44));
590        fmt.with_tag("user-type", "authenticated");
591        fmt.with_tag_value("source=search");
592
593        assert_eq!(
594            "prefix.num.results:44|h|#user-type:authenticated,source=search",
595            &fmt.format()
596        );
597    }
598
599    #[test]
600    fn test_metric_formatter_histogram_with_tags_multiple_values() {
601        let mut fmt =
602            MetricFormatter::histogram("prefix.", "num.results", MetricValue::PackedUnsigned(vec![44, 45, 46]));
603        fmt.with_tag("user-type", "authenticated");
604        fmt.with_tag_value("source=search");
605
606        assert_eq!(
607            "prefix.num.results:44:45:46|h|#user-type:authenticated,source=search",
608            &fmt.format()
609        );
610    }
611
612    #[test]
613    fn test_metric_formatter_distribution_no_tags() {
614        let fmt = MetricFormatter::distribution("prefix.", "latency.milliseconds", MetricValue::Unsigned(44));
615
616        assert_eq!("prefix.latency.milliseconds:44|d", &fmt.format());
617    }
618
619    #[test]
620    fn test_metric_formatter_distribution_no_tags_multiple_values() {
621        let fmt = MetricFormatter::distribution(
622            "prefix.",
623            "latency.milliseconds",
624            MetricValue::PackedUnsigned(vec![44, 45, 46]),
625        );
626
627        assert_eq!("prefix.latency.milliseconds:44:45:46|d", &fmt.format());
628    }
629
630    #[test]
631    fn test_metric_formatter_distribution_with_tags() {
632        let mut fmt = MetricFormatter::distribution("prefix.", "latency.milliseconds", MetricValue::Unsigned(44));
633        fmt.with_tag("user-type", "authenticated");
634        fmt.with_tag_value("source=search");
635
636        assert_eq!(
637            "prefix.latency.milliseconds:44|d|#user-type:authenticated,source=search",
638            &fmt.format()
639        );
640    }
641
642    #[test]
643    fn test_metric_formatter_distribution_with_tags_multiple_values() {
644        let mut fmt = MetricFormatter::distribution(
645            "prefix.",
646            "latency.milliseconds",
647            MetricValue::PackedUnsigned(vec![44, 45, 46]),
648        );
649        fmt.with_tag("user-type", "authenticated");
650        fmt.with_tag_value("source=search");
651
652        assert_eq!(
653            "prefix.latency.milliseconds:44:45:46|d|#user-type:authenticated,source=search",
654            &fmt.format()
655        );
656    }
657
658    #[test]
659    fn test_metric_formatter_set_no_tags() {
660        let fmt = MetricFormatter::set("prefix.", "users.uniques", MetricValue::Signed(44));
661
662        assert_eq!("prefix.users.uniques:44|s", &fmt.format());
663    }
664
665    #[test]
666    fn test_metric_formatter_set_with_tags() {
667        let mut fmt = MetricFormatter::set("prefix.", "users.uniques", MetricValue::Signed(44));
668        fmt.with_tag("user-type", "authenticated");
669        fmt.with_tag_value("source=search");
670
671        assert_eq!(
672            concat!(
673                "prefix.users.uniques:44|s|#",
674                "user-type:authenticated,",
675                "source=search"
676            ),
677            &fmt.format()
678        );
679    }
680
681    #[test]
682    fn test_metric_builder_send_success() {
683        let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
684        let client = StatsdClient::builder("prefix.", NopMetricSink)
685            .with_error_handler(|e| {
686                panic!("unexpected error sending metric: {}", e);
687            })
688            .build();
689
690        // if the send failed the test would have called the error handler and panicked
691        let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
692        builder.send();
693    }
694
695    #[test]
696    fn test_metric_builder_send_error() {
697        let errors = Arc::new(AtomicU64::new(0));
698        let errors_ref = errors.clone();
699
700        let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
701        let client = StatsdClient::builder("prefix.", ErrorMetricSink::always())
702            .with_error_handler(move |_e| {
703                errors_ref.fetch_add(1, Ordering::Release);
704            })
705            .build();
706
707        let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
708        builder.send();
709
710        assert_eq!(1, errors.load(Ordering::Acquire));
711    }
712
713    #[test]
714    fn test_metric_builder_try_send_success() {
715        let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
716        let client = StatsdClient::from_sink("prefix.", NopMetricSink);
717
718        let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
719        let res = builder.try_send();
720
721        assert!(res.is_ok(), "expected Ok result from try_send");
722    }
723
724    #[test]
725    fn test_metric_builder_try_send_error() {
726        let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
727        let client = StatsdClient::from_sink("prefix.", ErrorMetricSink::always());
728
729        let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
730        let res = builder.try_send();
731
732        assert!(res.is_err(), "expected Err result from try_send");
733    }
734}