diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..0bafeed --- /dev/null +++ b/.mise.toml @@ -0,0 +1,2 @@ +[tools] +rust = "1.74" diff --git a/CHANGELOG.md b/CHANGELOG.md index 99fea04..3c640e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [0.9.0] - 2024-11-25 + + +### Changed + +- Bump `dogstatsd` to 0.12 +- Bumped MSRV to 1.74 +- `event!` macro can now accept `EventOptions` as last argument before tags, [see the original repo](https://github.com/mcasper/dogstatsd-rs) for more details + +--- + ## [0.8.0] - 2024-06-21 ## Changed @@ -151,7 +162,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 -[Unreleased]: https://github.com/primait/prima_datadog.rs/compare/0.8.0...HEAD + +[Unreleased]: https://github.com/primait/prima_datadog.rs/compare/0.9.0...HEAD +[0.9.0]: https://github.com/primait/prima_datadog.rs/compare/0.8.0...0.9.0 [0.8.0]: https://github.com/primait/prima_datadog.rs/compare/0.7.2...0.8.0 [0.7.2]: https://github.com/primait/prima_datadog.rs/compare/0.7.1...0.7.2 [0.7.1]: https://github.com/primait/prima_datadog.rs/compare/0.7.0...0.7.1 diff --git a/Cargo.toml b/Cargo.toml index 5d9b1ce..db9ccac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ description = "An opinionated library to share code and approach to Datadog logg edition = "2018" license = "MIT" name = "prima_datadog" -version = "0.8.0" +version = "0.9.0" [features] default = [] @@ -12,7 +12,7 @@ serde = ["dep:serde"] [dependencies] async-trait = "0.1" -dogstatsd = {version = "=0.12.0", default-features = false} +dogstatsd = {version = "=0.12.1", default-features = false} once_cell = {version = "1.9", default-features = false, features = ["std"]} thiserror = {version = "1.0", default-features = false} diff --git a/Dockerfile b/Dockerfile index dde3513..33e8c8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.72.0 +FROM rust:1.74.0 WORKDIR /code diff --git a/src/client.rs b/src/client.rs index 680edf6..c2a98db 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,7 @@ use std::future::Future; use async_trait::async_trait; +use dogstatsd::EventOptions; use crate::{ServiceCheckOptions, ServiceStatus, TagsProvider}; @@ -75,6 +76,11 @@ pub trait DogstatsdClient { fn event(&self, title: &str, text: &str, tags: impl TagsProvider) where S: AsRef; + + /// Send a custom event as a title and a body + fn event_with_options(&self, title: &str, text: &str, tags: impl TagsProvider, options: Option) + where + S: AsRef; } #[async_trait] @@ -172,4 +178,11 @@ impl DogstatsdClient for dogstatsd::Client { { let _ = self.event(title, text, tags.as_ref()); } + + fn event_with_options(&self, title: &str, text: &str, tags: impl TagsProvider, options: Option) + where + S: AsRef, + { + let _ = self.event_with_options(title, text, tags.as_ref(), options); + } } diff --git a/src/lib.rs b/src/lib.rs index 31265f3..1425391 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,8 @@ //! service_check!("test", ServiceStatus::OK, ServiceCheckOptions::default()); //! # event!("test", "test event"); //! event!("test", "test event"; "some" => "data"); +//! # event!("test", "test event", EventOptions::new()); +//! # event!("test", "test event", EventOptions::new(); "some" => "data"); //! ``` //! //! This is an example of a custom metric, in this case based on an enum type, but it can really be @@ -114,7 +116,7 @@ use std::future::Future; use configuration::Configuration; -pub use dogstatsd::{ServiceCheckOptions, ServiceStatus}; +pub use dogstatsd::{EventOptions, ServiceCheckOptions, ServiceStatus}; use once_cell::sync::OnceCell; pub use client::DogstatsdClient; @@ -295,6 +297,18 @@ impl Datadog { } } + /// Send a custom event as a title, a body and some options + pub fn event_with_options>( + metric: impl AsRef, + text: impl AsRef, + tags: impl TagsProvider, + options: Option, + ) { + if let Some(instance) = INSTANCE.get() { + instance.do_event_with_options(metric.as_ref(), text.as_ref(), tags, options); + } + } + /// Acquire a timing guard. /// When this guard is dropped, it will emit a timing metric for the duration it /// existed. The metric name is metric, and the tags are tags. @@ -455,4 +469,19 @@ impl Datadog { self.tag_tracker.track(&self.inner, metric.as_ref(), tags), ); } + + pub(crate) fn do_event_with_options>( + &self, + metric: impl AsRef, + text: impl AsRef, + tags: impl TagsProvider, + options: Option, + ) { + self.inner.event_with_options( + metric.as_ref(), + text.as_ref(), + self.tag_tracker.track(&self.inner, metric.as_ref(), tags), + options, + ); + } } diff --git a/src/macros/event.rs b/src/macros/event.rs index 4127901..700d191 100644 --- a/src/macros/event.rs +++ b/src/macros/event.rs @@ -5,19 +5,37 @@ macro_rules! event { ($stat:path, $text:expr) => { $crate::Datadog::event($stat.as_ref(), $text, $crate::EMPTY_TAGS); }; + ($stat:path, $text:expr, $options:expr) => { + $crate::Datadog::event_with_options($stat.as_ref(), $text, $crate::EMPTY_TAGS, Some($options)); + }; ($stat:expr, $text:expr) => { $crate::Datadog::event($stat, $text, $crate::EMPTY_TAGS); }; + ($stat:expr, $text:expr, $options:expr) => { + $crate::Datadog::event_with_options($stat, $text, $crate::EMPTY_TAGS, Some($options)); + }; ($stat:expr, $text:expr; $( $key:literal => $value:literal ), *) => { $crate::Datadog::event($stat, $text, &[$(::core::concat!($key, ":", $value)), *]); }; + ($stat:expr, $text:expr, $options:expr; $( $key:literal => $value:literal ), *) => { + $crate::Datadog::event_with_options($stat, $text, &[$(::core::concat!($key, ":", $value)), *], Some($options)); + }; ($stat:path, $text:expr; $( $key:literal => $value:literal ), *) => { $crate::Datadog::event($stat.as_ref(), $text, &[$(::core::concat!($key, ":", $value)), *]); }; + ($stat:path, $text:expr, $options:expr; $( $key:literal => $value:literal ), *) => { + $crate::Datadog::event_with_options($stat.as_ref(), $text, &[$(::core::concat!($key, ":", $value)), *], Some($options)); + }; ($stat:expr, $text:expr; $( $key:expr => $value:expr ), *) => { $crate::Datadog::event($stat, $text, &[$(::std::format!("{}:{}", $key, $value).as_str()), *]); }; + ($stat:expr, $text:expr, $options:expr; $( $key:expr => $value:expr ), *) => { + $crate::Datadog::event_with_options($stat, $text, &[$(::std::format!("{}:{}", $key, $value).as_str()), *], Some($options)); + }; ($stat:path, $text:expr; $( $key:expr => $value:expr ), *) => { $crate::Datadog::event($stat.as_ref(), $text, &[$(::std::format!("{}:{}", $key, $value).as_str()), *]); }; + ($stat:path, $text:expr, $options:expr; $( $key:expr => $value:expr ), *) => { + $crate::Datadog::event_with_options($stat.as_ref(), $text, &[$(::std::format!("{}:{}", $key, $value).as_str()), *], Some($options)); + }; } diff --git a/src/tests/event_with_options.rs b/src/tests/event_with_options.rs new file mode 100644 index 0000000..166aa9d --- /dev/null +++ b/src/tests/event_with_options.rs @@ -0,0 +1,83 @@ +use dogstatsd::EventAlertType; +use dogstatsd::EventOptions; +use dogstatsd::EventPriority; + +use crate::event; +use crate::tests::mocks; +use crate::tests::TestEvent; +use crate::Datadog; +use crate::TagTrackerConfiguration; +use crate::EMPTY_TAGS; + +#[test] +pub fn event_with_options_with_literal() { + let mock = mocks::event_with_options_mock("test", "test_value", &[], None); + Datadog::new(mock, TagTrackerConfiguration::new()).do_event_with_options("test", "test_value", EMPTY_TAGS, None); +} + +#[test] +pub fn event_with_options_with_type() { + let mock = mocks::event_with_options_mock("test1_event", "test_value", &[], None); + Datadog::new(mock, TagTrackerConfiguration::new()).do_event_with_options( + TestEvent::Test1, + "test_value", + EMPTY_TAGS, + None, + ); +} + +#[test] +pub fn event_with_options_with_literal_and_tags() { + let mock = mocks::event_with_options_mock("test", "test_value", &["added:tag", "env:test"], None); + Datadog::new(mock, TagTrackerConfiguration::new()).do_event_with_options( + "test", + "test_value", + vec!["added:tag".to_string()], + None, + ); +} + +#[test] +pub fn event_with_options_with_type_and_tags() { + let mock = mocks::event_with_options_mock("test1_event", "test_value", &["added:tag", "env:test"], None); + Datadog::new(mock, TagTrackerConfiguration::new()).do_event_with_options( + TestEvent::Test1, + "test_value", + vec!["added:tag".to_string()], + None, + ); +} + +#[test] +pub fn event_with_options_and_tags() { + let options = Some( + EventOptions::new() + .with_alert_type(EventAlertType::Info) + .with_priority(EventPriority::Low) + .with_aggregation_key("aggregation_key") + .with_source_type_name("source_type_name") + .with_hostname("hostname") + .with_timestamp(12341234), + ); + + let mock = mocks::event_with_options_mock("test1_event", "test_value", &["added:tag", "env:test"], options); + Datadog::new(mock, TagTrackerConfiguration::new()).do_event_with_options( + TestEvent::Test1, + "test_value", + vec!["added:tag".to_string()], + options, + ); +} + +#[test] +pub fn test_macro() { + let tag = String::from("tag"); + // no tags with options + event!("test", "test_value", EventOptions::new()); + // just literal tags with options + event!("test", "test_value", EventOptions::new(); "literal" => 1); + // just expression tags with options + event!("test", "test_value", EventOptions::new(); "expression" => tag); + // mixed tags with options + event!("test", "test_value", EventOptions::new(); "literal" => 1, "expression" => tag); +} diff --git a/src/tests/mocks/dogstatsd_client.rs b/src/tests/mocks/dogstatsd_client.rs index fe85789..c647239 100644 --- a/src/tests/mocks/dogstatsd_client.rs +++ b/src/tests/mocks/dogstatsd_client.rs @@ -18,6 +18,7 @@ pub(super) trait MockDogstatsdClient { fn set(&self, metric: &str, val: &str, tags: Vec); fn service_check(&self, metric: &str, val: ServiceStatus, tags: Vec, options: Option); fn event(&self, title: &str, text: &str, tags: Vec); + fn event_with_options(&self, title: &str, text: &str, tags: Vec, options: Option); } #[async_trait] @@ -143,4 +144,17 @@ impl DogstatsdClient for C { tags.as_ref().iter().map(|s| s.as_ref().to_string()).collect(), ) } + + fn event_with_options(&self, title: &str, text: &str, tags: impl TagsProvider, options: Option) + where + S: AsRef, + { + MockDogstatsdClient::event_with_options( + self, + title, + text, + tags.as_ref().iter().map(|s| s.as_ref().to_string()).collect(), + options, + ) + } } diff --git a/src/tests/mocks/mod.rs b/src/tests/mocks/mod.rs index 4e15bc1..5635725 100644 --- a/src/tests/mocks/mod.rs +++ b/src/tests/mocks/mod.rs @@ -48,6 +48,9 @@ mock! { /// Send a custom event as a title and a body fn event(&self, title: &str, text: &str, tags: Vec); + + /// Send a custom event as a title, a body and some options + fn event_with_options<'a>(&self, title: &str, text: &str, tags: Vec, options: Option>); } } @@ -236,6 +239,28 @@ pub fn event_mock(metric: &'static str, text: &'static str, tags: &'static [&str client_mock } +#[allow(dead_code)] +pub fn event_with_options_mock( + metric: &'static str, + text: &'static str, + tags: &'static [&str], + options: Option>, +) -> MockClient { + let mut client_mock = MockClient::new(); + client_mock + .expect_event_with_options() + .once() + .withf(move |called_title, called_text, called_tags, called_options| { + called_title == metric + && called_text == text + && called_tags.iter().all(|tag| tags.contains(&tag.as_str())) + && matches!((called_options, options), (Some(_), Some(_)) | (None, None)) + }) + .return_const(()); + + client_mock +} + pub fn expect_incr(mut mock: MockClient, metric: &'static str, tags: impl IntoIterator) -> MockClient { let tags = tags.into_iter().collect::>(); mock.expect_incr() diff --git a/src/tests/mod.rs b/src/tests/mod.rs index bd22192..8e74940 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -7,6 +7,7 @@ mod count; mod decr; mod distribution; mod event; +mod event_with_options; mod gauge; mod histogram; mod incr;