[go: up one dir, main page]

cadence/
client.rs

1// Cadence - An extensible Statsd client for Rust!
2//
3// Copyright 2015-2021 Nick Pillitteri
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11use crate::builder::{MetricBuilder, MetricFormatter, MetricValue};
12use crate::sealed::Sealed;
13use crate::sinks::MetricSink;
14use crate::types::{
15    Counter, Distribution, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer,
16};
17use std::fmt;
18use std::panic::RefUnwindSafe;
19use std::time::Duration;
20
21/// Conversion trait for valid values for counters
22///
23/// This trait must be implemented for any types that are used as counter
24/// values (currently `i64`, `i32`, `u64`, and `u32`). This trait is internal to how values are
25/// formatted as part of metrics but is exposed publicly for documentation
26/// purposes.
27///
28/// Typical use of Cadence shouldn't require interacting with this trait.
29pub trait ToCounterValue {
30    fn try_to_value(self) -> MetricResult<MetricValue>;
31}
32
33impl ToCounterValue for i64 {
34    fn try_to_value(self) -> MetricResult<MetricValue> {
35        Ok(MetricValue::Signed(self))
36    }
37}
38
39impl ToCounterValue for i32 {
40    fn try_to_value(self) -> MetricResult<MetricValue> {
41        Ok(MetricValue::Signed(self.into()))
42    }
43}
44
45impl ToCounterValue for u64 {
46    fn try_to_value(self) -> MetricResult<MetricValue> {
47        Ok(MetricValue::Unsigned(self))
48    }
49}
50
51impl ToCounterValue for u32 {
52    fn try_to_value(self) -> MetricResult<MetricValue> {
53        Ok(MetricValue::Unsigned(self.into()))
54    }
55}
56
57/// Conversion trait for valid values for timers
58///
59/// This trait must be implemented for any types that are used as timer
60/// values (currently `u64`, `Duration`, and `Vec`s of those types).
61/// This trait is internal to how values are formatted as part of metrics
62/// but is exposed publicly for documentation purposes.
63///
64/// Typical use of Cadence shouldn't require interacting with this trait.
65pub trait ToTimerValue {
66    fn try_to_value(self) -> MetricResult<MetricValue>;
67}
68
69impl ToTimerValue for u64 {
70    fn try_to_value(self) -> MetricResult<MetricValue> {
71        Ok(MetricValue::Unsigned(self))
72    }
73}
74
75impl ToTimerValue for Vec<u64> {
76    fn try_to_value(self) -> MetricResult<MetricValue> {
77        Ok(MetricValue::PackedUnsigned(self))
78    }
79}
80
81impl ToTimerValue for Duration {
82    fn try_to_value(self) -> MetricResult<MetricValue> {
83        let as_millis = self.as_millis();
84        if as_millis > u64::MAX as u128 {
85            Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
86        } else {
87            Ok(MetricValue::Unsigned(as_millis as u64))
88        }
89    }
90}
91
92impl ToTimerValue for Vec<Duration> {
93    fn try_to_value(self) -> MetricResult<MetricValue> {
94        if self.iter().any(|x| x.as_millis() > u64::MAX as u128) {
95            Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
96        } else {
97            Ok(MetricValue::PackedUnsigned(
98                self.iter().map(|x| x.as_millis() as u64).collect(),
99            ))
100        }
101    }
102}
103
104/// Conversion trait for valid values for gauges
105///
106/// This trait must be implemented for any types that are used as gauge
107/// values (currently `u64` and `f64`). This trait is internal to how values
108/// are formatted as part of metrics but is exposed publicly for documentation
109/// purposes.
110///
111/// Typical use of Cadence shouldn't require interacting with this trait.
112pub trait ToGaugeValue {
113    fn try_to_value(self) -> MetricResult<MetricValue>;
114}
115
116impl ToGaugeValue for u64 {
117    fn try_to_value(self) -> MetricResult<MetricValue> {
118        Ok(MetricValue::Unsigned(self))
119    }
120}
121
122impl ToGaugeValue for f64 {
123    fn try_to_value(self) -> MetricResult<MetricValue> {
124        Ok(MetricValue::Float(self))
125    }
126}
127
128/// Conversion trait for valid values for meters
129///
130/// This trait must be implemented for any types that are used as meter
131/// values (currently only `u64`). This trait is internal to how values are
132/// formatted as part of metrics but is exposed publicly for documentation
133/// purposes.
134///
135/// Typical use of Cadence shouldn't require interacting with this trait.
136pub trait ToMeterValue {
137    fn try_to_value(self) -> MetricResult<MetricValue>;
138}
139
140impl ToMeterValue for u64 {
141    fn try_to_value(self) -> MetricResult<MetricValue> {
142        Ok(MetricValue::Unsigned(self))
143    }
144}
145
146/// Conversion trait for valid values for histograms
147///
148/// This trait must be implemented for any types that are used as histogram
149/// values (currently `u64`, `f64`, `Duration`, and `Vec`s of those types).
150/// This trait is internal to how values are formatted as part of metrics
151/// but is exposed publicly for documentation purposes.
152///
153/// Typical use of Cadence shouldn't require interacting with this trait.
154pub trait ToHistogramValue {
155    fn try_to_value(self) -> MetricResult<MetricValue>;
156}
157
158impl ToHistogramValue for u64 {
159    fn try_to_value(self) -> MetricResult<MetricValue> {
160        Ok(MetricValue::Unsigned(self))
161    }
162}
163
164impl ToHistogramValue for f64 {
165    fn try_to_value(self) -> MetricResult<MetricValue> {
166        Ok(MetricValue::Float(self))
167    }
168}
169
170impl ToHistogramValue for Duration {
171    fn try_to_value(self) -> MetricResult<MetricValue> {
172        let as_nanos = self.as_nanos();
173        if as_nanos > u64::MAX as u128 {
174            Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
175        } else {
176            Ok(MetricValue::Unsigned(as_nanos as u64))
177        }
178    }
179}
180
181impl ToHistogramValue for Vec<u64> {
182    fn try_to_value(self) -> MetricResult<MetricValue> {
183        Ok(MetricValue::PackedUnsigned(self))
184    }
185}
186
187impl ToHistogramValue for Vec<f64> {
188    fn try_to_value(self) -> MetricResult<MetricValue> {
189        Ok(MetricValue::PackedFloat(self))
190    }
191}
192
193impl ToHistogramValue for Vec<Duration> {
194    fn try_to_value(self) -> MetricResult<MetricValue> {
195        if self.iter().any(|x| x.as_nanos() > u64::MAX as u128) {
196            Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
197        } else {
198            Ok(MetricValue::PackedUnsigned(
199                self.iter().map(|x| x.as_nanos() as u64).collect(),
200            ))
201        }
202    }
203}
204
205/// Conversion trait for valid values for distributions
206///
207/// This trait must be implemented for any types that are used as distribution
208/// values (currently `u64`, `f64`, and `Vec`s of those types). This trait is
209/// internal to how values are formatted as part of metrics but is exposed
210/// publicly for documentation purposes.
211///
212/// Typical use of Cadence shouldn't require interacting with this trait.
213pub trait ToDistributionValue {
214    fn try_to_value(self) -> MetricResult<MetricValue>;
215}
216
217impl ToDistributionValue for u64 {
218    fn try_to_value(self) -> MetricResult<MetricValue> {
219        Ok(MetricValue::Unsigned(self))
220    }
221}
222
223impl ToDistributionValue for f64 {
224    fn try_to_value(self) -> MetricResult<MetricValue> {
225        Ok(MetricValue::Float(self))
226    }
227}
228
229impl ToDistributionValue for Vec<u64> {
230    fn try_to_value(self) -> MetricResult<MetricValue> {
231        Ok(MetricValue::PackedUnsigned(self))
232    }
233}
234
235impl ToDistributionValue for Vec<f64> {
236    fn try_to_value(self) -> MetricResult<MetricValue> {
237        Ok(MetricValue::PackedFloat(self))
238    }
239}
240
241/// Conversion trait for valid values for sets
242///
243/// This trait must be implemented for any types that are used as counter
244/// values (currently only `i64`). This trait is internal to how values are
245/// formatted as part of metrics but is exposed publicly for documentation
246/// purposes.
247///
248/// Typical use of Cadence shouldn't require interacting with this trait.
249pub trait ToSetValue {
250    fn try_to_value(self) -> MetricResult<MetricValue>;
251}
252
253impl ToSetValue for i64 {
254    fn try_to_value(self) -> MetricResult<MetricValue> {
255        Ok(MetricValue::Signed(self))
256    }
257}
258
259/// Trait for incrementing and decrementing counters.
260///
261/// Counters are simple values incremented or decremented by a client. The
262/// rates at which these events occur or average values will be determined
263/// by the server receiving them. Examples of counter uses include number
264/// of logins to a system or requests received.
265///
266/// The following types are valid for counters:
267/// * `i64`
268///
269/// See the [Statsd spec](https://github.com/b/statsd_spec) for more
270/// information.
271///
272/// Note that tags are a [Datadog](https://docs.datadoghq.com/developers/dogstatsd/)
273/// extension to Statsd and may not be supported by your server.
274pub trait Counted<T>
275where
276    T: ToCounterValue,
277{
278    /// Increment or decrement the counter by the given amount
279    fn count(&self, key: &str, count: T) -> MetricResult<Counter> {
280        self.count_with_tags(key, count).try_send()
281    }
282
283    /// Increment or decrement the counter by the given amount and return
284    /// a `MetricBuilder` that can be used to add tags to the metric.
285    fn count_with_tags<'a>(&'a self, key: &'a str, count: T) -> MetricBuilder<'a, 'a, Counter>;
286}
287
288/// Trait for convenience methods for counters
289///
290/// This trait specifically implements increment and decrement convenience
291/// methods for counters with `i64` types.
292pub trait CountedExt: Counted<i64> {
293    /// Increment the counter by 1
294    fn incr(&self, key: &str) -> MetricResult<Counter> {
295        self.incr_with_tags(key).try_send()
296    }
297
298    /// Increment the counter by 1 and return a `MetricBuilder` that can
299    /// be used to add tags to the metric.
300    fn incr_with_tags<'a>(&'a self, key: &'a str) -> MetricBuilder<'a, 'a, Counter> {
301        self.count_with_tags(key, 1)
302    }
303
304    /// Decrement the counter by 1
305    fn decr(&self, key: &str) -> MetricResult<Counter> {
306        self.decr_with_tags(key).try_send()
307    }
308
309    /// Decrement the counter by 1 and return a `MetricBuilder` that can
310    /// be used to add tags to the metric.
311    fn decr_with_tags<'a>(&'a self, key: &'a str) -> MetricBuilder<'a, 'a, Counter> {
312        self.count_with_tags(key, -1)
313    }
314}
315
316/// Trait for recording timings in milliseconds.
317///
318/// Timings are a positive number of milliseconds between a start and end
319/// time. Examples include time taken to render a web page or time taken
320/// for a database call to return. `Duration` values are converted to
321/// milliseconds before being recorded.
322///
323/// The following types are valid for timers:
324/// * `u64`
325/// * `Duration`
326///
327/// See the [Statsd spec](https://github.com/b/statsd_spec) for more
328/// information.
329///
330/// Note that tags are a [Datadog](https://docs.datadoghq.com/developers/dogstatsd/)
331/// extension to Statsd and may not be supported by your server.
332pub trait Timed<T>
333where
334    T: ToTimerValue,
335{
336    /// Record a timing in milliseconds with the given key
337    fn time(&self, key: &str, time: T) -> MetricResult<Timer> {
338        self.time_with_tags(key, time).try_send()
339    }
340
341    /// Record a timing in milliseconds with the given key and return a
342    /// `MetricBuilder` that can be used to add tags to the metric.
343    fn time_with_tags<'a>(&'a self, key: &'a str, time: T) -> MetricBuilder<'a, 'a, Timer>;
344}
345
346/// Trait for recording gauge values.
347///
348/// Gauge values are an instantaneous measurement of a value determined
349/// by the client. They do not change unless changed by the client. Examples
350/// include things like load average or how many connections are active.
351///
352/// The following types are valid for gauges:
353/// * `u64`
354/// * `f64`
355///
356/// See the [Statsd spec](https://github.com/b/statsd_spec) for more
357/// information.
358///
359/// Note that tags are a [Datadog](https://docs.datadoghq.com/developers/dogstatsd/)
360/// extension to Statsd and may not be supported by your server.
361pub trait Gauged<T>
362where
363    T: ToGaugeValue,
364{
365    /// Record a gauge value with the given key
366    fn gauge(&self, key: &str, value: T) -> MetricResult<Gauge> {
367        self.gauge_with_tags(key, value).try_send()
368    }
369
370    /// Record a gauge value with the given key and return a `MetricBuilder`
371    /// that can be used to add tags to the metric.
372    fn gauge_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Gauge>;
373}
374
375/// Trait for recording meter values.
376///
377/// Meter values measure the rate at which events occur. These rates are
378/// determined by the server, the client simply indicates when they happen.
379/// Meters can be thought of as increment-only counters. Examples include
380/// things like number of requests handled or number of times something is
381/// flushed to disk.
382///
383/// The following types are valid for meters:
384/// * `u64`
385///
386/// See the [Statsd spec](https://github.com/b/statsd_spec) for more
387/// information.
388///
389/// Note that tags are a [Datadog](https://docs.datadoghq.com/developers/dogstatsd/)
390/// extension to Statsd and may not be supported by your server.
391pub trait Metered<T>
392where
393    T: ToMeterValue,
394{
395    /// Record a meter value with the given key
396    fn meter(&self, key: &str, value: T) -> MetricResult<Meter> {
397        self.meter_with_tags(key, value).try_send()
398    }
399
400    /// Record a meter value with the given key and return a `MetricBuilder`
401    /// that can be used to add tags to the metric.
402    fn meter_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Meter>;
403}
404
405/// Trait for recording histogram values.
406///
407/// Histogram values are positive values that can represent anything, whose
408/// statistical distribution is calculated by the server. The values can be
409/// timings, amount of some resource consumed, size of HTTP responses in
410/// some application, etc. Histograms can be thought of as a more general
411/// form of timers. `Duration` values are converted to nanoseconds before
412/// being emitted.
413///
414/// The following types are valid for histograms:
415/// * `u64`
416/// * `f64`
417/// * `Duration`
418///
419/// See the [Statsd spec](https://github.com/b/statsd_spec) for more
420/// information.
421///
422/// Note that tags and histograms are a
423/// [Datadog](https://docs.datadoghq.com/developers/dogstatsd/) extension to
424/// Statsd and may not be supported by your server.
425pub trait Histogrammed<T>
426where
427    T: ToHistogramValue,
428{
429    /// Record a single histogram value with the given key
430    fn histogram(&self, key: &str, value: T) -> MetricResult<Histogram> {
431        self.histogram_with_tags(key, value).try_send()
432    }
433
434    /// Record a single histogram value with the given key and return a
435    /// `MetricBuilder` that can be used to add tags to the metric.
436    fn histogram_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Histogram>;
437}
438
439/// Trait for recording distribution values.
440///
441/// Similar to histograms, but applies globally. A distribution can be used to
442/// instrument logical objects, like services, independently from the underlying
443/// hosts.
444///
445/// The following types are valid for distributions:
446/// * `u64`
447/// * `f64`
448///
449/// See the [Datadog docs](https://docs.datadoghq.com/developers/metrics/types/?tab=distribution#definition)
450/// for more information.
451///
452/// Note that tags and distributions are a
453/// [Datadog](https://docs.datadoghq.com/developers/dogstatsd/) extension to
454/// Statsd and may not be supported by your server.
455pub trait Distributed<T>
456where
457    T: ToDistributionValue,
458{
459    /// Record a single distribution value with the given key
460    fn distribution(&self, key: &str, value: T) -> MetricResult<Distribution> {
461        self.distribution_with_tags(key, value).try_send()
462    }
463
464    /// Record a single distribution value with the given key and return a
465    /// `MetricBuilder` that can be used to add tags to the metric.
466    fn distribution_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Distribution>;
467}
468
469/// Trait for recording set values.
470///
471/// Sets count the number of unique elements in a group. You can use them to,
472/// for example, count the unique visitors to your site.
473///
474/// The following types are valid for sets:
475/// * `i64`
476///
477/// See the [Statsd spec](https://github.com/b/statsd_spec) for more
478/// information.
479pub trait Setted<T>
480where
481    T: ToSetValue,
482{
483    /// Record a single set value with the given key
484    fn set(&self, key: &str, value: T) -> MetricResult<Set> {
485        self.set_with_tags(key, value).try_send()
486    }
487
488    /// Record a single set value with the given key and return a
489    /// `MetricBuilder` that can be used to add tags to the metric.
490    fn set_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Set>;
491}
492
493/// Trait that encompasses all other traits for sending metrics.
494///
495/// If you wish to use `StatsdClient` with a generic type or place a
496/// `StatsdClient` instance behind a pointer (such as a `Box`) this will allow
497/// you to reference all the implemented methods for recording metrics, while
498/// using a single trait. An example of this is shown below.
499///
500/// ```
501/// use std::time::Duration;
502/// use cadence::{MetricClient, StatsdClient, NopMetricSink};
503///
504/// let client: Box<dyn MetricClient> = Box::new(StatsdClient::from_sink(
505///     "prefix", NopMetricSink));
506///
507/// client.count("some.counter", 1).unwrap();
508/// client.count("some.counter", 2i32).unwrap();
509/// client.count("some.counter", 4u64).unwrap();
510/// client.count("some.counter", 8u32).unwrap();
511/// client.time("some.timer", 42).unwrap();
512/// client.time("some.timer", Duration::from_millis(42)).unwrap();
513/// client.time("some.timer", vec![42]).unwrap();
514/// client.time("some.timer", vec![Duration::from_millis(42)]).unwrap();
515/// client.gauge("some.gauge", 8).unwrap();
516/// client.meter("some.meter", 13).unwrap();
517/// client.histogram("some.histogram", 4).unwrap();
518/// client.histogram("some.histogram", Duration::from_nanos(4)).unwrap();
519/// client.histogram("some.histogram", vec![4]).unwrap();
520/// client.histogram("some.histogram", vec![Duration::from_nanos(4)]).unwrap();
521/// client.distribution("some.distribution", 4).unwrap();
522/// client.distribution("some.distribution", vec![4]).unwrap();
523/// client.set("some.set", 5).unwrap();
524/// ```
525pub trait MetricClient:
526    Counted<i64>
527    + Counted<i32>
528    + Counted<u64>
529    + Counted<u32>
530    + CountedExt
531    + Timed<u64>
532    + Timed<Duration>
533    + Timed<Vec<u64>>
534    + Timed<Vec<Duration>>
535    + Gauged<u64>
536    + Gauged<f64>
537    + Metered<u64>
538    + Histogrammed<u64>
539    + Histogrammed<f64>
540    + Histogrammed<Duration>
541    + Histogrammed<Vec<u64>>
542    + Histogrammed<Vec<f64>>
543    + Histogrammed<Vec<Duration>>
544    + Distributed<u64>
545    + Distributed<f64>
546    + Distributed<Vec<u64>>
547    + Distributed<Vec<f64>>
548    + Setted<i64>
549{
550}
551
552/// Typically internal client methods for sending metrics and handling errors.
553///
554/// This trait exposes methods of the client that would normally be internal
555/// but may be useful for consumers of the library to extend it in unforseen
556/// ways. Most consumers of the library shouldn't need to make use of this
557/// extension point.
558///
559/// This trait is not exposed in the `prelude` module since it isn't required
560/// to use the client for sending metrics. It is only exposed in the `ext`
561/// module which is used to encompass advanced extension points for the library.
562///
563/// NOTE: This is a sealed trait and so it cannot be implemented outside of the
564/// library.
565///
566/// # Example
567///
568/// ```
569/// use cadence::{Metric, MetricResult, StatsdClient, NopMetricSink};
570/// use cadence::ext::MetricBackend;
571///
572/// struct CustomMetric {
573///     repr: String,
574/// }
575///
576/// impl Metric for CustomMetric {
577///     fn as_metric_str(&self) -> &str {
578///         &self.repr
579///     }
580/// }
581///
582/// impl From<String> for CustomMetric {
583///     fn from(v: String) -> Self {
584///         CustomMetric { repr: v }
585///     }
586/// }
587///
588/// struct MyCustomClient {
589///     prefix: String,
590///     wrapped: StatsdClient,
591/// }
592///
593/// impl MyCustomClient {
594///     fn new(prefix: &str, client: StatsdClient) -> Self {
595///         MyCustomClient {
596///             prefix: prefix.to_string(),
597///             wrapped: client,
598///         }
599///     }
600///
601///     fn send_event(&self, key: &str, val: i64) -> MetricResult<CustomMetric> {
602///         let metric = CustomMetric::from(format!("{}.{}:{}|e", self.prefix, key, val));
603///         self.wrapped.send_metric(&metric)?;
604///         Ok(metric)
605///     }
606///
607///     fn send_event_quietly(&self, key: &str, val: i64) {
608///         if let Err(e) = self.send_event(key, val) {
609///             self.wrapped.consume_error(e);
610///         }
611///     }
612/// }
613///
614/// let prefix = "some.prefix";
615/// let inner = StatsdClient::from_sink(&prefix, NopMetricSink);
616/// let custom = MyCustomClient::new(&prefix, inner);
617///
618/// custom.send_event("some.event", 123).unwrap();
619/// custom.send_event_quietly("some.event", 456);
620/// ```
621pub trait MetricBackend: Sealed {
622    /// Send a full formed `Metric` implementation via the underlying `MetricSink`
623    ///
624    /// Obtain a `&str` representation of a metric, encode it as UTF-8 bytes, and
625    /// send it to the underlying `MetricSink`, verbatim. Note that the metric is
626    /// expected to be full formed already, including any prefix or tags.
627    ///
628    /// Note that if you simply want to emit standard metrics, you don't need to
629    /// use this method. This is only useful if you are extending Cadence with a
630    /// custom metric type or something similar.
631    fn send_metric<M>(&self, metric: &M) -> MetricResult<()>
632    where
633        M: Metric;
634
635    /// Consume a possible error from attempting to send a metric.
636    ///
637    /// When callers have elected to quietly send metrics via the `MetricBuilder::send()`
638    /// method, this method will be invoked if an error is encountered. By default the
639    /// handler is a no-op, meaning that errors are discarded.
640    ///
641    /// Note that if you simply want to emit standard metrics, you don't need to
642    /// use this method. This is only useful if you are extending Cadence with a
643    /// custom metric type or something similar.
644    fn consume_error(&self, err: MetricError);
645}
646
647/// Builder for creating and customizing `StatsdClient` instances.
648///
649/// Instances of the builder should be created by calling the `::builder()`
650/// method on the `StatsClient` struct.
651///
652/// # Example
653///
654/// ```
655/// use cadence::prelude::*;
656/// use cadence::{MetricError, StatsdClient, NopMetricSink};
657///
658/// fn my_error_handler(err: MetricError) {
659///     println!("Metric error! {}", err);
660/// }
661///
662/// let client = StatsdClient::builder("prefix", NopMetricSink)
663///     .with_error_handler(my_error_handler)
664///     .with_tag("environment", "production")
665///     .with_tag_value("rust")
666///     .build();
667///
668/// client.count("something", 123);
669/// client.count_with_tags("some.counter", 42)
670///     .with_tag("region", "us-east-2")
671///     .send();
672/// ```
673pub struct StatsdClientBuilder {
674    prefix: String,
675    sink: Box<dyn MetricSink + Sync + Send + RefUnwindSafe>,
676    errors: Box<dyn Fn(MetricError) + Sync + Send + RefUnwindSafe>,
677    tags: Vec<(Option<String>, String)>,
678    container_id: Option<String>,
679}
680
681impl StatsdClientBuilder {
682    // Set the required fields and defaults for optional fields
683    fn new<T>(prefix: &str, sink: T) -> Self
684    where
685        T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
686    {
687        StatsdClientBuilder {
688            // required
689            prefix: Self::formatted_prefix(prefix),
690            sink: Box::new(sink),
691
692            // optional with defaults
693            errors: Box::new(nop_error_handler),
694            tags: Vec::new(),
695            container_id: None,
696        }
697    }
698
699    /// Set an error handler to use for metrics sent via `MetricBuilder::send()`
700    ///
701    /// The error handler is only invoked when metrics are not able to be sent
702    /// correctly. Either due to invalid input, I/O errors encountered when trying
703    /// to send them via a `MetricSink`, or some other reason.
704    ///
705    /// The error handler should consume the error without panicking. The error
706    /// may be logged, printed to stderr, discarded, etc. - this is up to the
707    /// implementation.
708    pub fn with_error_handler<F>(mut self, errors: F) -> Self
709    where
710        F: Fn(MetricError) + Sync + Send + RefUnwindSafe + 'static,
711    {
712        self.errors = Box::new(errors);
713        self
714    }
715
716    /// Add a default tag with key and value to every metric published by the
717    /// built [StatsdClient].
718    pub fn with_tag<K, V>(mut self, key: K, value: V) -> Self
719    where
720        K: ToString,
721        V: ToString,
722    {
723        self.tags.push((Some(key.to_string()), value.to_string()));
724        self
725    }
726
727    /// Add a default tag with only a value to every metric published by the built
728    /// [StatsdClient].
729    pub fn with_tag_value<K>(mut self, value: K) -> Self
730    where
731        K: ToString,
732    {
733        self.tags.push((None, value.to_string()));
734        self
735    }
736
737    /// Add a default container ID to every metric published by the built
738    /// [StatsdClient].
739    pub fn with_container_id<K>(mut self, container_id: K) -> Self
740    where
741        K: ToString,
742    {
743        self.container_id = Some(container_id.to_string());
744        self
745    }
746
747    /// Construct a new `StatsdClient` instance based on current settings.
748    pub fn build(self) -> StatsdClient {
749        StatsdClient::from_builder(self)
750    }
751
752    fn formatted_prefix(prefix: &str) -> String {
753        if prefix.is_empty() {
754            String::new()
755        } else {
756            format!("{}.", prefix.trim_end_matches('.'))
757        }
758    }
759}
760
761/// Client for Statsd that implements various traits to record metrics.
762///
763/// # Traits
764///
765/// The client is the main entry point for users of this library. It supports
766/// several traits for recording metrics of different types.
767///
768/// * `Counted` for emitting counters.
769/// * `Timed` for emitting timings.
770/// * `Gauged` for emitting gauge values.
771/// * `Metered` for emitting meter values.
772/// * `Histogrammed` for emitting histogram values.
773/// * `Distributed` for emitting distribution values.
774/// * `Setted` for emitting set values.
775/// * `MetricClient` for a combination of all of the above.
776///
777/// For more information about the uses for each type of metric, see the
778/// documentation for each mentioned trait.
779///
780/// # Sinks
781///
782/// The client uses some implementation of a `MetricSink` to emit the metrics.
783///
784/// In simple use cases when performance isn't critical, the `UdpMetricSink`
785/// is an acceptable choice since it is the simplest to use and understand.
786///
787/// When performance is more important, users will want to use the
788/// `BufferedUdpMetricSink` in combination with the `QueuingMetricSink` for
789/// maximum isolation between the sending of metrics and your application as well
790/// as minimum overhead when sending metrics.
791///
792/// # Threading
793///
794/// The `StatsdClient` is designed to work in a multithreaded application. All
795/// parts of the client can be shared between threads (i.e. it is `Send` and
796/// `Sync`). An example of how to use the client in a multithreaded environment
797/// is given below.
798///
799/// In the following example, we create a struct `MyRequestHandler` that has a
800/// single method that spawns a thread to do some work and emit a metric.
801///
802/// ## Wrapping With An `Arc`
803///
804/// In order to share a client between multiple threads, you'll need to wrap it
805/// with an atomic reference counting pointer (`std::sync::Arc`). You should refer
806/// to the client by the trait of all its methods for recording metrics
807/// (`MetricClient`) as well as the `Send` and `Sync` traits since the idea is to
808/// share this between threads.
809///
810/// ``` no_run
811/// use std::panic::RefUnwindSafe;
812/// use std::net::UdpSocket;
813/// use std::sync::Arc;
814/// use std::thread;
815/// use cadence::prelude::*;
816/// use cadence::{StatsdClient, BufferedUdpMetricSink, DEFAULT_PORT};
817///
818/// struct MyRequestHandler {
819///     metrics: Arc<dyn MetricClient + Send + Sync + RefUnwindSafe>,
820/// }
821///
822/// impl MyRequestHandler {
823///     fn new() -> MyRequestHandler {
824///         let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
825///         let host = ("localhost", DEFAULT_PORT);
826///         let sink = BufferedUdpMetricSink::from(host, socket).unwrap();
827///         MyRequestHandler {
828///             metrics: Arc::new(StatsdClient::from_sink("some.prefix", sink))
829///         }
830///     }
831///
832///     fn handle_some_request(&self) -> Result<(), String> {
833///         let metric_ref = self.metrics.clone();
834///         let _t = thread::spawn(move || {
835///             println!("Hello from the thread!");
836///             metric_ref.count("request.handler", 1);
837///         });
838///
839///         Ok(())
840///     }
841/// }
842/// ```
843pub struct StatsdClient {
844    prefix: String,
845    sink: Box<dyn MetricSink + Sync + Send + RefUnwindSafe>,
846    errors: Box<dyn Fn(MetricError) + Sync + Send + RefUnwindSafe>,
847    tags: Vec<(Option<String>, String)>,
848    container_id: Option<String>,
849}
850
851impl StatsdClient {
852    /// Create a new client instance that will use the given prefix for
853    /// all metrics emitted to the given `MetricSink` implementation.
854    ///
855    /// Note that this client will discard errors encountered when
856    /// sending metrics via the `MetricBuilder::send()` method.
857    ///
858    /// # No-op Example
859    ///
860    /// ```
861    /// use cadence::{StatsdClient, NopMetricSink};
862    ///
863    /// let prefix = "my.stats";
864    /// let client = StatsdClient::from_sink(prefix, NopMetricSink);
865    /// ```
866    ///
867    /// # UDP Socket Example
868    ///
869    /// ```
870    /// use std::net::UdpSocket;
871    /// use cadence::{StatsdClient, UdpMetricSink, DEFAULT_PORT};
872    ///
873    /// let prefix = "my.stats";
874    /// let host = ("127.0.0.1", DEFAULT_PORT);
875    ///
876    /// let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
877    /// socket.set_nonblocking(true).unwrap();
878    ///
879    /// let sink = UdpMetricSink::from(host, socket).unwrap();
880    /// let client = StatsdClient::from_sink(prefix, sink);
881    /// ```
882    ///
883    /// # Buffered UDP Socket Example
884    ///
885    /// ```
886    /// use std::net::UdpSocket;
887    /// use cadence::{StatsdClient, BufferedUdpMetricSink, DEFAULT_PORT};
888    ///
889    /// let prefix = "my.stats";
890    /// let host = ("127.0.0.1", DEFAULT_PORT);
891    ///
892    /// let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
893    ///
894    /// let sink = BufferedUdpMetricSink::from(host, socket).unwrap();
895    /// let client = StatsdClient::from_sink(prefix, sink);
896    /// ```
897    pub fn from_sink<T>(prefix: &str, sink: T) -> Self
898    where
899        T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
900    {
901        Self::builder(prefix, sink).build()
902    }
903
904    /// Create a new builder with the provided prefix and metric sink.
905    ///
906    /// A prefix and a metric sink are required to create a new client
907    /// instance. All other optional customizations can be set by calling
908    /// methods on the returned builder. Any customizations that aren't
909    /// set by the caller will use defaults.
910    ///
911    /// Note, though a metric prefix is required, you may pass an empty
912    /// string as a prefix. In this case, the metrics emitted will use only
913    /// the bare keys supplied when you call the various methods to emit
914    /// metrics.
915    ///
916    /// General defaults:
917    ///
918    /// * A no-op error handler will be used by default. Note that this
919    ///   only affects errors encountered when using the `MetricBuilder::send()`
920    ///   method (as opposed to `.try_send()` or any other method for sending
921    ///   metrics).
922    ///
923    /// # Example
924    ///
925    /// ```
926    /// use cadence::prelude::*;
927    /// use cadence::{StatsdClient, MetricError, NopMetricSink};
928    ///
929    /// fn my_handler(err: MetricError) {
930    ///     println!("Metric error: {}", err);
931    /// }
932    ///
933    /// let client = StatsdClient::builder("some.prefix", NopMetricSink)
934    ///     .with_error_handler(my_handler)
935    ///     .build();
936    ///
937    /// client.gauge_with_tags("some.key", 7)
938    ///    .with_tag("region", "us-west-1")
939    ///    .send();
940    /// ```
941    pub fn builder<T>(prefix: &str, sink: T) -> StatsdClientBuilder
942    where
943        T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
944    {
945        StatsdClientBuilder::new(prefix, sink)
946    }
947
948    /// Flush the underlying metric sink.
949    ///
950    /// This is helpful for when you'd like to buffer metrics
951    /// but still want strong control over when to emit them.
952    /// For example, you are using a BufferedUdpMetricSink and
953    /// have just emitted some time-sensitive metrics, but you
954    /// aren't sure if the buffer is full or not. Thus, you can
955    /// use `flush` to force the sink to flush your metrics now.
956    ///
957    /// # Buffered UDP Socket Example
958    ///
959    /// ```
960    /// use std::net::UdpSocket;
961    /// use cadence::prelude::*;
962    /// use cadence::{StatsdClient, BufferedUdpMetricSink, DEFAULT_PORT};
963    ///
964    /// let prefix = "my.stats";
965    /// let host = ("127.0.0.1", DEFAULT_PORT);
966    ///
967    /// let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
968    ///
969    /// let sink = BufferedUdpMetricSink::from(host, socket).unwrap();
970    /// let client = StatsdClient::from_sink(prefix, sink);
971    ///
972    /// client.count("time-sensitive.keyA", 1);
973    /// client.count("time-sensitive.keyB", 2);
974    /// client.count("time-sensitive.keyC", 3);
975    /// // Any number of time-sensitive metrics ...
976    /// client.flush();
977    /// ```
978    pub fn flush(&self) -> MetricResult<()> {
979        Ok(self.sink.flush()?)
980    }
981
982    // Create a new StatsdClient by consuming the builder
983    fn from_builder(builder: StatsdClientBuilder) -> Self {
984        StatsdClient {
985            prefix: builder.prefix,
986            sink: builder.sink,
987            errors: builder.errors,
988            tags: builder.tags,
989            container_id: builder.container_id,
990        }
991    }
992
993    fn tags(&self) -> impl IntoIterator<Item = (Option<&str>, &str)> {
994        self.tags.iter().map(|(k, v)| (k.as_deref(), v.as_str()))
995    }
996}
997
998impl Sealed for StatsdClient {}
999
1000impl MetricBackend for StatsdClient {
1001    fn send_metric<M>(&self, metric: &M) -> MetricResult<()>
1002    where
1003        M: Metric,
1004    {
1005        let metric_string = metric.as_metric_str();
1006        self.sink.emit(metric_string)?;
1007        Ok(())
1008    }
1009
1010    fn consume_error(&self, err: MetricError) {
1011        (self.errors)(err);
1012    }
1013}
1014
1015impl fmt::Debug for StatsdClient {
1016    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017        write!(
1018            f,
1019            "StatsdClient {{ prefix: {:?}, sink: ..., errors: ..., tags: {:?} }}",
1020            self.prefix, self.tags,
1021        )
1022    }
1023}
1024
1025impl<T> Counted<T> for StatsdClient
1026where
1027    T: ToCounterValue,
1028{
1029    fn count_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Counter> {
1030        match value.try_to_value() {
1031            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::counter(&self.prefix, key, v), self)
1032                .with_tags(self.tags())
1033                .with_container_id_opt(self.container_id.as_deref()),
1034            Err(e) => MetricBuilder::from_error(e, self),
1035        }
1036    }
1037}
1038
1039impl CountedExt for StatsdClient {}
1040
1041impl<T> Timed<T> for StatsdClient
1042where
1043    T: ToTimerValue,
1044{
1045    fn time_with_tags<'a>(&'a self, key: &'a str, time: T) -> MetricBuilder<'a, 'a, Timer> {
1046        match time.try_to_value() {
1047            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::timer(&self.prefix, key, v), self)
1048                .with_tags(self.tags())
1049                .with_container_id_opt(self.container_id.as_deref()),
1050            Err(e) => MetricBuilder::from_error(e, self),
1051        }
1052    }
1053}
1054
1055impl<T> Gauged<T> for StatsdClient
1056where
1057    T: ToGaugeValue,
1058{
1059    fn gauge_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Gauge> {
1060        match value.try_to_value() {
1061            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::gauge(&self.prefix, key, v), self)
1062                .with_tags(self.tags())
1063                .with_container_id_opt(self.container_id.as_deref()),
1064            Err(e) => MetricBuilder::from_error(e, self),
1065        }
1066    }
1067}
1068
1069impl<T> Metered<T> for StatsdClient
1070where
1071    T: ToMeterValue,
1072{
1073    fn meter_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Meter> {
1074        match value.try_to_value() {
1075            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::meter(&self.prefix, key, v), self)
1076                .with_tags(self.tags())
1077                .with_container_id_opt(self.container_id.as_deref()),
1078            Err(e) => MetricBuilder::from_error(e, self),
1079        }
1080    }
1081}
1082
1083impl<T> Histogrammed<T> for StatsdClient
1084where
1085    T: ToHistogramValue,
1086{
1087    fn histogram_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Histogram> {
1088        match value.try_to_value() {
1089            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::histogram(&self.prefix, key, v), self)
1090                .with_tags(self.tags())
1091                .with_container_id_opt(self.container_id.as_deref()),
1092            Err(e) => MetricBuilder::from_error(e, self),
1093        }
1094    }
1095}
1096
1097impl<T> Distributed<T> for StatsdClient
1098where
1099    T: ToDistributionValue,
1100{
1101    fn distribution_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Distribution> {
1102        match value.try_to_value() {
1103            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::distribution(&self.prefix, key, v), self)
1104                .with_tags(self.tags())
1105                .with_container_id_opt(self.container_id.as_deref()),
1106            Err(e) => MetricBuilder::from_error(e, self),
1107        }
1108    }
1109}
1110
1111impl<T> Setted<T> for StatsdClient
1112where
1113    T: ToSetValue,
1114{
1115    fn set_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Set> {
1116        match value.try_to_value() {
1117            Ok(v) => MetricBuilder::from_fmt(MetricFormatter::set(&self.prefix, key, v), self)
1118                .with_tags(self.tags())
1119                .with_container_id_opt(self.container_id.as_deref()),
1120            Err(e) => MetricBuilder::from_error(e, self),
1121        }
1122    }
1123}
1124
1125impl MetricClient for StatsdClient {}
1126
1127#[allow(clippy::needless_pass_by_value)]
1128fn nop_error_handler(_err: MetricError) {
1129    // nothing
1130}
1131
1132#[cfg(test)]
1133mod tests {
1134    use super::{
1135        Counted, CountedExt, Distributed, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient,
1136        StatsdClientBuilder, Timed,
1137    };
1138    use crate::sinks::{MetricSink, NopMetricSink, QueuingMetricSink, SpyMetricSink};
1139    use crate::types::{ErrorKind, Metric, MetricError};
1140    use std::io;
1141    use std::panic::RefUnwindSafe;
1142    use std::sync::atomic::{AtomicUsize, Ordering};
1143    use std::sync::Arc;
1144    use std::time::Duration;
1145
1146    #[test]
1147    fn test_statsd_client_empty_prefix() {
1148        let client = StatsdClient::from_sink("", NopMetricSink);
1149        let res = client.count("some.method", 1);
1150
1151        assert_eq!("some.method:1|c", res.unwrap().as_metric_str());
1152    }
1153
1154    #[test]
1155    fn test_statsd_client_with_container_id() {
1156        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1157            .with_container_id("1234")
1158            .build();
1159        let res = client.count("some.method", 1);
1160
1161        assert_eq!("prefix.some.method:1|c|c:1234", res.unwrap().as_metric_str());
1162    }
1163
1164    #[test]
1165    fn test_statsd_client_merging_default_tags_with_tags() {
1166        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1167            .with_tag("hello", "world")
1168            .with_tag_value("production")
1169            .build();
1170        let res = client
1171            .count_with_tags("some.counter", 3)
1172            .with_tag("foo", "bar")
1173            .with_tag_value("fizz")
1174            .with_tag("bucket", "123")
1175            .try_send();
1176
1177        assert_eq!(
1178            "prefix.some.counter:3|c|#hello:world,production,foo:bar,fizz,bucket:123",
1179            res.unwrap().as_metric_str()
1180        );
1181    }
1182
1183    #[test]
1184    fn test_statsd_client_count_with_tags() {
1185        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1186        let res = client
1187            .count_with_tags("some.counter", 3)
1188            .with_tag("foo", "bar")
1189            .try_send();
1190
1191        assert_eq!("prefix.some.counter:3|c|#foo:bar", res.unwrap().as_metric_str());
1192    }
1193
1194    #[test]
1195    fn test_statsd_client_count_with_default_tags() {
1196        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1197            .with_tag("hello", "world")
1198            .build();
1199        let res = client.count_with_tags("some.counter", 3).try_send();
1200
1201        assert_eq!("prefix.some.counter:3|c|#hello:world", res.unwrap().as_metric_str());
1202    }
1203
1204    #[test]
1205    fn test_statsd_client_incr_with_tags() {
1206        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1207        let res = client.incr_with_tags("some.counter").with_tag("foo", "bar").try_send();
1208
1209        assert_eq!("prefix.some.counter:1|c|#foo:bar", res.unwrap().as_metric_str());
1210    }
1211
1212    #[test]
1213    fn test_statsd_client_incr_with_default_tags() {
1214        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1215            .with_tag("foo", "bar")
1216            .build();
1217        let res = client.incr_with_tags("some.counter").try_send();
1218
1219        assert_eq!("prefix.some.counter:1|c|#foo:bar", res.unwrap().as_metric_str());
1220    }
1221
1222    #[test]
1223    fn test_statsd_client_decr_with_tags() {
1224        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1225        let res = client.decr_with_tags("some.counter").with_tag("foo", "bar").try_send();
1226
1227        assert_eq!("prefix.some.counter:-1|c|#foo:bar", res.unwrap().as_metric_str());
1228    }
1229
1230    #[test]
1231    fn test_statsd_client_decr_with_default_tags() {
1232        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1233            .with_tag("foo", "bar")
1234            .build();
1235        let res = client.decr_with_tags("some.counter").try_send();
1236
1237        assert_eq!("prefix.some.counter:-1|c|#foo:bar", res.unwrap().as_metric_str());
1238    }
1239
1240    #[test]
1241    fn test_statsd_client_gauge_with_tags() {
1242        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1243        let res = client
1244            .gauge_with_tags("some.gauge", 4)
1245            .with_tag("bucket", "A")
1246            .with_tag_value("file-server")
1247            .try_send();
1248
1249        assert_eq!(
1250            "prefix.some.gauge:4|g|#bucket:A,file-server",
1251            res.unwrap().as_metric_str()
1252        );
1253    }
1254
1255    #[test]
1256    fn test_statsd_client_gauge_with_default_tags() {
1257        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1258            .with_tag("foo", "bar")
1259            .build();
1260        let res = client.gauge_with_tags("some.gauge", 4).try_send();
1261
1262        assert_eq!("prefix.some.gauge:4|g|#foo:bar", res.unwrap().as_metric_str());
1263    }
1264
1265    #[test]
1266    fn test_statsd_client_gauge_with_timestamp() {
1267        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1268        let res = client
1269            .gauge_with_tags("some.gauge", 4)
1270            .with_timestamp(1234567890)
1271            .try_send();
1272
1273        assert_eq!("prefix.some.gauge:4|g|T1234567890", res.unwrap().as_metric_str());
1274    }
1275
1276    #[test]
1277    fn test_statsd_client_time_duration() {
1278        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1279        let res = client.time("key", Duration::from_millis(157));
1280
1281        assert_eq!("prefix.key:157|ms", res.unwrap().as_metric_str());
1282    }
1283
1284    #[test]
1285    fn test_statsd_client_time_multiple_durations() {
1286        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1287        let durations = vec![
1288            Duration::from_millis(157),
1289            Duration::from_millis(158),
1290            Duration::from_millis(159),
1291        ];
1292        let res = client.time("key", durations);
1293
1294        assert_eq!("prefix.key:157:158:159|ms", res.unwrap().as_metric_str());
1295    }
1296
1297    #[test]
1298    fn test_statsd_client_time_duration_with_overflow() {
1299        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1300        let res = client.time("key", Duration::from_secs(u64::MAX));
1301
1302        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind())
1303    }
1304
1305    #[test]
1306    fn test_statsd_client_time_multiple_durations_with_overflow() {
1307        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1308        let durations = vec![
1309            Duration::from_millis(157),
1310            Duration::from_secs(u64::MAX),
1311            Duration::from_millis(159),
1312        ];
1313        let res = client.time("key", durations);
1314
1315        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind())
1316    }
1317
1318    #[test]
1319    fn test_statsd_client_time_duration_with_tags() {
1320        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1321        let res = client
1322            .time_with_tags("key", Duration::from_millis(157))
1323            .with_tag("foo", "bar")
1324            .with_tag_value("quux")
1325            .try_send();
1326
1327        assert_eq!("prefix.key:157|ms|#foo:bar,quux", res.unwrap().as_metric_str());
1328    }
1329
1330    #[test]
1331    fn test_statsd_client_time_duration_with_default_tags() {
1332        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1333            .with_tag("foo", "bar")
1334            .build();
1335        let res = client.time("key", Duration::from_millis(157));
1336
1337        assert_eq!("prefix.key:157|ms|#foo:bar", res.unwrap().as_metric_str());
1338    }
1339
1340    #[test]
1341    fn test_statsd_client_time_multiple_durations_with_tags() {
1342        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1343        let durations = vec![
1344            Duration::from_millis(157),
1345            Duration::from_millis(158),
1346            Duration::from_millis(159),
1347        ];
1348        let res = client
1349            .time_with_tags("key", durations)
1350            .with_tag("foo", "bar")
1351            .with_tag_value("quux")
1352            .try_send();
1353
1354        assert_eq!("prefix.key:157:158:159|ms|#foo:bar,quux", res.unwrap().as_metric_str());
1355    }
1356
1357    #[test]
1358    fn test_statsd_client_time_duration_with_tags_with_overflow() {
1359        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1360        let res = client
1361            .time_with_tags("key", Duration::from_secs(u64::MAX))
1362            .with_tag("foo", "bar")
1363            .with_tag_value("quux")
1364            .try_send();
1365
1366        assert!(res.is_err());
1367        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1368    }
1369
1370    #[test]
1371    fn test_statsd_client_time_multiple_durations_with_tags_with_overflow() {
1372        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1373        let durations = vec![
1374            Duration::from_millis(157),
1375            Duration::from_secs(u64::MAX),
1376            Duration::from_millis(159),
1377        ];
1378        let res = client
1379            .time_with_tags("key", durations)
1380            .with_tag("foo", "bar")
1381            .with_tag_value("quux")
1382            .try_send();
1383
1384        assert!(res.is_err());
1385        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1386    }
1387
1388    #[test]
1389    fn test_statsd_client_meter_with_tags() {
1390        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1391        let res = client
1392            .meter_with_tags("some.meter", 64)
1393            .with_tag("segment", "142")
1394            .with_tag_value("beta")
1395            .try_send();
1396
1397        assert_eq!("prefix.some.meter:64|m|#segment:142,beta", res.unwrap().as_metric_str());
1398    }
1399
1400    #[test]
1401    fn test_statsd_client_meter_with_default_tags() {
1402        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1403            .with_tag("foo", "bar")
1404            .build();
1405        let res = client.meter_with_tags("some.meter", 64).try_send();
1406
1407        assert_eq!("prefix.some.meter:64|m|#foo:bar", res.unwrap().as_metric_str());
1408    }
1409
1410    #[test]
1411    fn test_statsd_client_histogram_with_tags() {
1412        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1413        let res = client
1414            .histogram_with_tags("some.histo", 27)
1415            .with_tag("host", "www03.example.com")
1416            .with_tag_value("rc1")
1417            .try_send();
1418
1419        assert_eq!(
1420            "prefix.some.histo:27|h|#host:www03.example.com,rc1",
1421            res.unwrap().as_metric_str()
1422        );
1423    }
1424
1425    #[test]
1426    fn test_statsd_client_histogram_with_default_tags() {
1427        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1428            .with_tag("foo", "bar")
1429            .build();
1430        let res = client.histogram_with_tags("some.histo", 27).try_send();
1431
1432        assert_eq!("prefix.some.histo:27|h|#foo:bar", res.unwrap().as_metric_str());
1433    }
1434
1435    #[test]
1436    fn test_statsd_client_histogram_with_multiple_values() {
1437        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1438        let res = client.histogram_with_tags("some.histo", vec![27, 28, 29]).try_send();
1439
1440        assert_eq!("prefix.some.histo:27:28:29|h", res.unwrap().as_metric_str());
1441    }
1442
1443    #[test]
1444    fn test_statsd_client_histogram_duration() {
1445        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1446        let res = client.histogram("key", Duration::from_nanos(210));
1447
1448        assert_eq!("prefix.key:210|h", res.unwrap().as_metric_str());
1449    }
1450
1451    #[test]
1452    fn test_statsd_client_histogram_multiple_durations() {
1453        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1454        let durations = vec![
1455            Duration::from_nanos(210),
1456            Duration::from_nanos(211),
1457            Duration::from_nanos(212),
1458        ];
1459        let res = client.histogram("key", durations);
1460
1461        assert_eq!("prefix.key:210:211:212|h", res.unwrap().as_metric_str());
1462    }
1463
1464    #[test]
1465    fn test_statsd_client_histogram_duration_with_overflow() {
1466        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1467        let res = client.histogram("key", Duration::from_secs(u64::MAX));
1468
1469        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1470    }
1471
1472    #[test]
1473    fn test_statsd_client_histogram_multiple_durations_with_overflow() {
1474        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1475        let durations = vec![
1476            Duration::from_nanos(210),
1477            Duration::from_secs(u64::MAX),
1478            Duration::from_nanos(212),
1479        ];
1480
1481        let res = client.histogram("key", durations);
1482
1483        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1484    }
1485
1486    #[test]
1487    fn test_statsd_client_histogram_duration_with_tags() {
1488        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1489        let res = client
1490            .histogram_with_tags("key", Duration::from_nanos(4096))
1491            .with_tag("foo", "bar")
1492            .with_tag_value("beta")
1493            .try_send();
1494
1495        assert_eq!("prefix.key:4096|h|#foo:bar,beta", res.unwrap().as_metric_str());
1496    }
1497
1498    #[test]
1499    fn test_statsd_client_histogram_duration_with_default_tags() {
1500        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1501            .with_tag("foo", "bar")
1502            .build();
1503        let res = client.histogram_with_tags("key", Duration::from_nanos(4096)).try_send();
1504
1505        assert_eq!("prefix.key:4096|h|#foo:bar", res.unwrap().as_metric_str());
1506    }
1507
1508    #[test]
1509    fn test_statsd_client_histogram_duration_with_tags_with_overflow() {
1510        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1511        let res = client
1512            .histogram_with_tags("key", Duration::from_millis(u64::MAX))
1513            .with_tag("foo", "bar")
1514            .with_tag_value("beta")
1515            .try_send();
1516
1517        assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1518    }
1519
1520    #[test]
1521    fn test_statsd_client_distribution_with_tags() {
1522        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1523        let res = client
1524            .distribution_with_tags("some.distr", 27)
1525            .with_tag("host", "www03.example.com")
1526            .with_tag_value("rc1")
1527            .try_send();
1528
1529        assert_eq!(
1530            "prefix.some.distr:27|d|#host:www03.example.com,rc1",
1531            res.unwrap().as_metric_str()
1532        );
1533    }
1534
1535    #[test]
1536    fn test_statsd_client_distribution_with_default_tags() {
1537        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1538            .with_tag("foo", "bar")
1539            .build();
1540        let res = client
1541            .distribution_with_tags("some.distr", 27)
1542            .with_tag("host", "www03.example.com")
1543            .with_tag_value("rc1")
1544            .try_send();
1545
1546        assert_eq!(
1547            "prefix.some.distr:27|d|#foo:bar,host:www03.example.com,rc1",
1548            res.unwrap().as_metric_str()
1549        );
1550    }
1551
1552    #[test]
1553    fn test_statsd_client_distribution_multiple_values_with_tags() {
1554        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1555        let res = client
1556            .distribution_with_tags("some.distr", vec![27, 28, 29])
1557            .with_tag("host", "www03.example.com")
1558            .with_tag_value("rc1")
1559            .try_send();
1560
1561        assert_eq!(
1562            "prefix.some.distr:27:28:29|d|#host:www03.example.com,rc1",
1563            res.unwrap().as_metric_str()
1564        );
1565    }
1566
1567    #[test]
1568    fn test_statsd_client_distribution_with_sampling_rate() {
1569        let client = StatsdClient::from_sink("prefix", NopMetricSink);
1570        let res = client
1571            .distribution_with_tags("some.distr", 4)
1572            .with_sampling_rate(0.5)
1573            .try_send();
1574
1575        assert_eq!("prefix.some.distr:4|d|@0.5", res.unwrap().as_metric_str());
1576    }
1577
1578    #[test]
1579    fn test_statsd_client_set_with_tags() {
1580        let client = StatsdClient::from_sink("myapp", NopMetricSink);
1581        let res = client.set_with_tags("some.set", 3).with_tag("foo", "bar").try_send();
1582
1583        assert_eq!("myapp.some.set:3|s|#foo:bar", res.unwrap().as_metric_str());
1584    }
1585
1586    #[test]
1587    fn test_statsd_client_set_with_default_tags() {
1588        let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1589            .with_tag("foo", "bar")
1590            .build();
1591        let res = client.set_with_tags("some.set", 3).try_send();
1592
1593        assert_eq!("prefix.some.set:3|s|#foo:bar", res.unwrap().as_metric_str());
1594    }
1595
1596    #[test]
1597    fn test_statsd_client_with_tags_send_success() {
1598        let (rx, sink) = SpyMetricSink::new();
1599        let client = StatsdClient::from_sink("prefix", sink);
1600
1601        client.count_with_tags("some.key", 1).with_tag("test", "a").send();
1602        let sent = rx.recv().unwrap();
1603
1604        assert_eq!("prefix.some.key:1|c|#test:a", String::from_utf8(sent).unwrap());
1605    }
1606
1607    #[test]
1608    fn test_statsd_client_with_tags_send_error() {
1609        struct ErrorSink;
1610
1611        impl MetricSink for ErrorSink {
1612            fn emit(&self, _metric: &str) -> io::Result<usize> {
1613                Err(io::Error::from(io::ErrorKind::Other))
1614            }
1615        }
1616
1617        let count = Arc::new(AtomicUsize::new(0));
1618        let count_ref = count.clone();
1619
1620        let handler = move |_err: MetricError| {
1621            count_ref.fetch_add(1, Ordering::Release);
1622        };
1623
1624        let client = StatsdClient::builder("prefix", ErrorSink)
1625            .with_error_handler(handler)
1626            .build();
1627
1628        client.count_with_tags("some.key", 1).with_tag("tier", "web").send();
1629
1630        assert_eq!(1, count.load(Ordering::Acquire));
1631    }
1632
1633    // The following tests really just ensure that we've actually
1634    // implemented all the traits we're supposed to correctly. If
1635    // we hadn't, this wouldn't compile.
1636
1637    #[test]
1638    fn test_statsd_client_as_counted_i64() {
1639        let client: Box<dyn Counted<i64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1640
1641        client.count("some.counter", 5).unwrap();
1642    }
1643
1644    #[test]
1645    fn test_statsd_client_as_counted_i32() {
1646        let client: Box<dyn Counted<i32>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1647
1648        client.count("some.counter", 10i32).unwrap();
1649    }
1650
1651    #[test]
1652    fn test_statsd_client_as_counted_u64() {
1653        let client: Box<dyn Counted<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1654
1655        client.count("some.counter", 20u64).unwrap();
1656    }
1657
1658    #[test]
1659    fn test_statsd_client_as_counted_u32() {
1660        let client: Box<dyn Counted<u32>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1661
1662        client.count("some.counter", 40u32).unwrap();
1663    }
1664
1665    #[test]
1666    fn test_statsd_client_as_countedext() {
1667        let client: Box<dyn CountedExt> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1668
1669        client.incr("some.counter").unwrap();
1670    }
1671
1672    #[test]
1673    fn test_statsd_client_as_timed_u64() {
1674        let client: Box<dyn Timed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1675
1676        client.time("some.timer", 20).unwrap();
1677    }
1678
1679    #[test]
1680    fn test_statsd_client_as_timed_duration() {
1681        let client: Box<dyn Timed<Duration>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1682
1683        client.time("some.timer", Duration::from_millis(20)).unwrap();
1684    }
1685
1686    #[test]
1687    fn test_statsd_client_as_timed_packed_duration() {
1688        let client: Box<dyn Timed<Vec<Duration>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1689        let durations = vec![Duration::from_millis(20), Duration::from_millis(21)];
1690
1691        client.time("some.timer", durations).unwrap();
1692    }
1693
1694    #[test]
1695    fn test_statsd_client_as_gauged_u64() {
1696        let client: Box<dyn Gauged<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1697
1698        client.gauge("some.gauge", 32).unwrap();
1699    }
1700
1701    #[test]
1702    fn test_statsd_client_as_gauged_f64() {
1703        let client: Box<dyn Gauged<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1704
1705        client.gauge("some.gauge", 3.2).unwrap();
1706    }
1707
1708    #[test]
1709    fn test_statsd_client_as_metered() {
1710        let client: Box<dyn Metered<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1711
1712        client.meter("some.meter", 9).unwrap();
1713    }
1714
1715    #[test]
1716    fn test_statsd_client_as_histogrammed_u64() {
1717        let client: Box<dyn Histogrammed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1718
1719        client.histogram("some.histogram", 4).unwrap();
1720    }
1721
1722    #[test]
1723    fn test_statsd_client_as_histogrammed_packed_u64() {
1724        let client: Box<dyn Histogrammed<Vec<u64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1725
1726        client.histogram("some.histogram", vec![4, 5, 6]).unwrap();
1727    }
1728
1729    #[test]
1730    fn test_statsd_client_as_histogrammed_f64() {
1731        let client: Box<dyn Histogrammed<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1732
1733        client.histogram("some.histogram", 4.0).unwrap();
1734    }
1735
1736    #[test]
1737    fn test_statsd_client_as_histogrammed_packed_f64() {
1738        let client: Box<dyn Histogrammed<Vec<f64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1739
1740        client.histogram("some.histogram", vec![4.0, 5.0, 6.0]).unwrap();
1741    }
1742
1743    #[test]
1744    fn test_statsd_client_as_histogrammed_duration() {
1745        let client: Box<dyn Histogrammed<Duration>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1746
1747        client.histogram("some.histogram", Duration::from_nanos(4)).unwrap();
1748    }
1749
1750    #[test]
1751    fn test_statsd_client_as_histogrammed_packed_duration() {
1752        let client: Box<dyn Histogrammed<Vec<Duration>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1753        let durations = vec![Duration::from_nanos(4), Duration::from_nanos(5)];
1754
1755        client.histogram("some.histogram", durations).unwrap();
1756    }
1757
1758    #[test]
1759    fn test_statsd_client_as_distributed_u64() {
1760        let client: Box<dyn Distributed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1761
1762        client.distribution("some.distribution", 33).unwrap();
1763    }
1764
1765    #[test]
1766    fn test_statsd_client_as_distributed_packed_u64() {
1767        let client: Box<dyn Distributed<Vec<u64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1768
1769        client.distribution("some.distribution", vec![33, 34]).unwrap();
1770    }
1771
1772    #[test]
1773    fn test_statsd_client_as_distributed_f64() {
1774        let client: Box<dyn Distributed<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1775
1776        client.distribution("some.distribution", 33.0).unwrap();
1777    }
1778
1779    #[test]
1780    fn test_statsd_client_as_distributed_packed_f64() {
1781        let client: Box<dyn Distributed<Vec<f64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1782
1783        client.distribution("some.distribution", vec![33.0, 34.0]).unwrap();
1784    }
1785
1786    #[test]
1787    fn test_statsd_client_as_setted() {
1788        let client: Box<dyn Setted<i64>> = Box::new(StatsdClient::from_sink("myapp", NopMetricSink));
1789
1790        client.set("some.set", 5).unwrap();
1791    }
1792
1793    #[test]
1794    fn test_statsd_client_as_thread_and_panic_safe() {
1795        let client: Box<dyn MetricClient + Send + Sync + RefUnwindSafe> = Box::new(StatsdClient::from_sink(
1796            "prefix",
1797            QueuingMetricSink::from(NopMetricSink),
1798        ));
1799
1800        client.count("some.counter", 3).unwrap(); // this defaults to i64
1801        client.count("some.counter", 6i32).unwrap();
1802        client.count("some.counter", 12u64).unwrap();
1803        client.count("some.counter", 24u32).unwrap();
1804        client.time("some.timer", 198).unwrap();
1805        client.time("some.timer", Duration::from_millis(198)).unwrap();
1806        client.time("some.timer", vec![198]).unwrap();
1807        client.time("some.timer", vec![Duration::from_millis(198)]).unwrap();
1808        client.gauge("some.gauge", 4).unwrap();
1809        client.gauge("some.gauge", 4.0).unwrap();
1810        client.meter("some.meter", 29).unwrap();
1811        client.histogram("some.histogram", 32).unwrap();
1812        client.histogram("some.histogram", 32.0).unwrap();
1813        client.histogram("some.histogram", Duration::from_nanos(32)).unwrap();
1814        client.histogram("some.histogram", vec![32]).unwrap();
1815        client.histogram("some.histogram", vec![32.0]).unwrap();
1816        client
1817            .histogram("some.histogram", vec![Duration::from_nanos(32)])
1818            .unwrap();
1819        client.distribution("some.distribution", 248).unwrap();
1820        client.distribution("some.distribution", 248.0).unwrap();
1821        client.distribution("some.distribution", vec![248]).unwrap();
1822        client.distribution("some.distribution", vec![248.0]).unwrap();
1823        client.set("some.set", 5).unwrap();
1824    }
1825}