Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the Tag trait #30

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 78 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ extern crate chrono;
use chrono::Utc;
use std::net::UdpSocket;
use std::borrow::Cow;
use std::fmt;
use std::io;

mod error;
pub use self::error::DogstatsdError;
Expand All @@ -87,6 +89,68 @@ pub use self::metrics::{ServiceStatus, ServiceCheckOptions};
/// A type alias for returning a unit type or an error
pub type DogstatsdResult = Result<(), DogstatsdError>;

/// This trait represents anything that can be turned into a tag.
///
/// There's a blanket implementation of Tag for AsRef<str>, for the most part you
/// can do make do just thinking of this trait as being equivalent to AsRef<str>.
///
/// What this trait does is allow you to have types that are not AsRef<str> but
/// can still be turned into tags. For example the TagTuple struct lets you turn
/// a tuple of AsRef<str> values into a key-value tag.
pub trait Tag {
/// Write the tag to the given writer
fn write_tag<W: io::Write>(&self, w: &mut W) -> io::Result<()>;
}

impl<T: AsRef<str>> Tag for T {
fn write_tag<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(self.as_ref().as_bytes())
}
}

/// A newtype around a (K, V) tuple that implements Tag.
///
/// This will let you do
/// ```
/// use dogstatsd::{TagTuple, Client, Options};
///
/// let client = Client::new(Options::default()).unwrap();
/// let tags = TagTuple::new("foo", "bar");
/// client.incr("my_counter", &[tags]);
/// ```
/// This will add the tag `foo:bar`.
///
/// Do note that it isn't checked whether the key contains `:`. No character is
/// escaped. If you submit `("ab:cd", "ef")` you'll just end up with `"ab:cd:ef"`.
#[derive(Debug)]
#[repr(transparent)]
pub struct TagTuple<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug>((K, V));
impl<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug> Tag for &TagTuple<K, V> {
fn write_tag<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
let (key, value) = &self.0;
w.write_all(key.as_ref().as_bytes())?;
w.write_all(b":")?;
w.write_all(value.as_ref().as_bytes())?;
Ok(())
}
}
impl<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug> Tag for TagTuple<K, V> {
fn write_tag<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
(&self).write_tag(w)
}
}
impl<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug> From<(K, V)> for TagTuple<K, V> {
fn from(tag: (K, V)) -> Self {
TagTuple(tag)
}
}
impl<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug> TagTuple<K, V> {
/// Create a new tag tuple from a key and a value
pub fn new(k: K, v: V) -> Self {
TagTuple((k, v))
}
}

/// The struct that represents the options available for the Dogstatsd client.
#[derive(Debug, PartialEq)]
pub struct Options {
Expand Down Expand Up @@ -197,7 +261,7 @@ impl Client {
pub fn incr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&CountMetric::Incr(stat.into().as_ref()), tags)
}
Expand All @@ -216,7 +280,7 @@ impl Client {
pub fn decr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&CountMetric::Decr(stat.into().as_ref()), tags)
}
Expand All @@ -235,7 +299,7 @@ impl Client {
pub fn count<'a, I, S, T>(&self, stat: S, count: i64, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&CountMetric::Arbitrary(stat.into().as_ref(), count), tags)
}
Expand All @@ -258,7 +322,7 @@ impl Client {
where F: FnOnce(),
I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
let start_time = Utc::now();
block();
Expand All @@ -280,7 +344,7 @@ impl Client {
pub fn timing<'a, I, S, T>(&self, stat: S, ms: i64, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&TimingMetric::new(stat.into().as_ref(), ms), tags)
}
Expand All @@ -300,7 +364,7 @@ impl Client {
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&GaugeMetric::new(stat.into().as_ref(), val.into().as_ref()), tags)
}
Expand All @@ -320,7 +384,7 @@ impl Client {
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&HistogramMetric::new(stat.into().as_ref(), val.into().as_ref()), tags)
}
Expand All @@ -340,7 +404,7 @@ impl Client {
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&DistributionMetric::new(stat.into().as_ref(), val.into().as_ref()), tags)
}
Expand All @@ -360,7 +424,7 @@ impl Client {
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&SetMetric::new(stat.into().as_ref(), val.into().as_ref()), tags)
}
Expand Down Expand Up @@ -394,7 +458,7 @@ impl Client {
pub fn service_check<'a, I, S, T>(&self, stat: S, val: ServiceStatus, tags: I, options: Option<ServiceCheckOptions>) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
let unwrapped_options = options.unwrap_or_default();
self.send(&ServiceCheck::new(stat.into().as_ref(), val, unwrapped_options), tags)
Expand All @@ -415,15 +479,15 @@ impl Client {
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
T: Tag,
{
self.send(&Event::new(title.into().as_ref(), text.into().as_ref()), tags)
}

fn send<I, M, S>(&self, metric: &M, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=S>,
fn send<I, M, T>(&self, metric: &M, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
M: Metric,
S: AsRef<str>,
T: Tag,
{
let formatted_metric = format_for_send(metric, &self.namespace, tags);
self.socket.send_to(formatted_metric.as_slice(), &self.to_addr)?;
Expand Down
19 changes: 15 additions & 4 deletions src/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::Tag;

use chrono::{DateTime, Utc};

pub fn format_for_send<M, I, S>(in_metric: &M, in_namespace: &str, tags: I) -> Vec<u8>
pub fn format_for_send<M, I, T>(in_metric: &M, in_namespace: &str, tags: I) -> Vec<u8>
where M: Metric,
I: IntoIterator<Item=S>,
S: AsRef<str>,
I: IntoIterator<Item=T>,
T: Tag,
{
let metric = in_metric.metric_type_format();
let namespace = if in_metric.uses_namespace() {
Expand All @@ -28,7 +30,7 @@ pub fn format_for_send<M, I, S>(in_metric: &M, in_namespace: &str, tags: I) -> V
}

while next_tag.is_some() {
buf.extend_from_slice(next_tag.unwrap().as_ref().as_bytes());
next_tag.unwrap().write_tag(&mut buf).unwrap();

next_tag = tags_iter.next();

Expand Down Expand Up @@ -418,6 +420,15 @@ mod tests {
)
}

#[test]
fn test_format_for_send_tuple_label() {
let labels: crate::TagTuple<_, _> = ("abc", "def").into();
assert_eq!(
&*b"foo:1|c|#abc:def",
&*format_for_send(&CountMetric::Incr("foo"), "", &[labels])
)
}

#[test]
fn test_count_incr_metric() {
let metric = CountMetric::Incr("incr".into());
Expand Down