1use std::{fmt, time::SystemTime};
43
44#[derive(Clone, Debug)]
45pub struct DataPoint {
46 pub name: &'static str,
47 pub timestamp: SystemTime,
48 pub tags: Vec<(&'static str, String)>,
50 pub fields: Vec<(&'static str, String)>,
51}
52
53impl DataPoint {
54 pub fn new(name: &'static str) -> Self {
55 DataPoint {
56 name,
57 timestamp: SystemTime::now(),
58 tags: vec![],
59 fields: vec![],
60 }
61 }
62
63 pub fn add_tag(&mut self, name: &'static str, value: &str) -> &mut Self {
64 self.tags.push((name, value.to_string()));
65 self
66 }
67
68 pub fn add_field_str(&mut self, name: &'static str, value: &str) -> &mut Self {
69 self.fields
70 .push((name, format!("\"{}\"", value.replace('\"', "\\\""))));
71 self
72 }
73
74 pub fn add_field_bool(&mut self, name: &'static str, value: bool) -> &mut Self {
75 self.fields.push((name, value.to_string()));
76 self
77 }
78
79 pub fn add_field_i64(&mut self, name: &'static str, value: i64) -> &mut Self {
80 self.fields.push((name, value.to_string() + "i"));
81 self
82 }
83
84 pub fn add_field_f64(&mut self, name: &'static str, value: f64) -> &mut Self {
85 self.fields.push((name, value.to_string()));
86 self
87 }
88}
89
90impl fmt::Display for DataPoint {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "datapoint: {}", self.name)?;
93 for tag in &self.tags {
94 write!(f, ",{}={}", tag.0, tag.1)?;
95 }
96 for field in &self.fields {
97 write!(f, " {}={}", field.0, field.1)?;
98 }
99 Ok(())
100 }
101}
102
103#[macro_export]
104macro_rules! create_datapoint {
105 (@field $point:ident $name:expr, $string:expr, String) => {
106 $point.add_field_str($name, &$string);
107 };
108 (@field $point:ident $name:expr, $value:expr, i64) => {
109 $point.add_field_i64($name, $value as i64);
110 };
111 (@field $point:ident $name:expr, $value:expr, f64) => {
112 $point.add_field_f64($name, $value as f64);
113 };
114 (@field $point:ident $name:expr, $value:expr, bool) => {
115 $point.add_field_bool($name, $value as bool);
116 };
117 (@tag $point:ident $tag_name:expr, $tag_value:expr) => {
118 $point.add_tag($tag_name, &$tag_value);
119 };
120
121 (@fields $point:ident) => {};
122
123 (@fields $point:ident ($name:expr, $value:expr, Option<$type:ident>) , $($rest:tt)*) => {
125 if let Some(value) = $value {
126 $crate::create_datapoint!(@field $point $name, value, $type);
127 }
128 $crate::create_datapoint!(@fields $point $($rest)*);
129 };
130 (@fields $point:ident ($name:expr, $value:expr, Option<$type:ident>) $(,)?) => {
131 if let Some(value) = $value {
132 $crate::create_datapoint!(@field $point $name, value, $type);
133 }
134 };
135
136 (@fields $point:ident $tag_name:expr => $tag_value:expr, $($rest:tt)*) => {
138 $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
139 $crate::create_datapoint!(@fields $point $($rest)*);
140 };
141 (@fields $point:ident $tag_name:expr => $tag_value:expr $(,)?) => {
142 $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
143 };
144
145 (@fields $point:ident ($name:expr, $value:expr, $type:ident) , $($rest:tt)*) => {
147 $crate::create_datapoint!(@field $point $name, $value, $type);
148 $crate::create_datapoint!(@fields $point $($rest)*);
149 };
150 (@fields $point:ident ($name:expr, $value:expr, $type:ident) $(,)?) => {
151 $crate::create_datapoint!(@field $point $name, $value, $type);
152 };
153
154 (@point $name:expr, $($fields:tt)+) => {
155 {
156 let mut point = $crate::datapoint::DataPoint::new(&$name);
157 $crate::create_datapoint!(@fields point $($fields)+);
158 point
159 }
160 };
161}
162
163#[macro_export]
164macro_rules! datapoint {
165 ($level:expr, $name:expr, $($fields:tt)+) => {
166 if log::log_enabled!($level) {
167 $crate::submit($crate::create_datapoint!(@point $name, $($fields)+), $level);
168 }
169 };
170}
171#[macro_export]
172macro_rules! datapoint_error {
173 ($name:expr, $($fields:tt)+) => {
174 $crate::datapoint!(log::Level::Error, $name, $($fields)+);
175 };
176}
177
178#[macro_export]
179macro_rules! datapoint_warn {
180 ($name:expr, $($fields:tt)+) => {
181 $crate::datapoint!(log::Level::Warn, $name, $($fields)+);
182 };
183}
184
185#[macro_export]
186macro_rules! datapoint_info {
187 ($name:expr, $($fields:tt)+) => {
188 $crate::datapoint!(log::Level::Info, $name, $($fields)+);
189 };
190}
191
192#[macro_export]
193macro_rules! datapoint_debug {
194 ($name:expr, $($fields:tt)+) => {
195 $crate::datapoint!(log::Level::Debug, $name, $($fields)+);
196 };
197}
198
199#[macro_export]
200macro_rules! datapoint_trace {
201 ($name:expr, $($fields:tt)+) => {
202 $crate::datapoint!(log::Level::Trace, $name, $($fields)+);
203 };
204}
205
206#[cfg(test)]
207mod test {
208 #[test]
209 fn test_datapoint() {
210 datapoint_debug!("name", ("field name", "test", String));
211 datapoint_info!("name", ("field name", 12.34_f64, f64));
212 datapoint_trace!("name", ("field name", true, bool));
213 datapoint_warn!("name", ("field name", 1, i64));
214 datapoint_error!("name", ("field name", 1, i64),);
215 datapoint!(
216 log::Level::Warn,
217 "name",
218 ("field1 name", 2, i64),
219 ("field2 name", 2, i64)
220 );
221 datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
222 datapoint_trace!(
223 "name",
224 ("field1 name", 2, i64),
225 ("field2 name", 2, i64),
226 ("field3 name", 3, i64)
227 );
228 datapoint!(
229 log::Level::Error,
230 "name",
231 ("field1 name", 2, i64),
232 ("field2 name", 2, i64),
233 ("field3 name", 3, i64),
234 );
235
236 let point = create_datapoint!(
237 @point "name",
238 ("i64", 1, i64),
239 ("String", "string space string", String),
240 ("f64", 12.34_f64, f64),
241 ("bool", true, bool)
242 );
243 assert_eq!(point.name, "name");
244 assert_eq!(point.tags.len(), 0);
245 assert_eq!(point.fields[0], ("i64", "1i".to_string()));
246 assert_eq!(
247 point.fields[1],
248 ("String", "\"string space string\"".to_string())
249 );
250 assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
251 assert_eq!(point.fields[3], ("bool", "true".to_string()));
252 }
253
254 #[test]
255 fn test_optional_datapoint() {
256 datapoint_debug!("name", ("field name", Some("test"), Option<String>));
257 datapoint_info!("name", ("field name", Some(12.34_f64), Option<f64>));
258 datapoint_trace!("name", ("field name", Some(true), Option<bool>));
259 datapoint_warn!("name", ("field name", Some(1), Option<i64>));
260 datapoint_error!("name", ("field name", Some(1), Option<i64>),);
261 datapoint_debug!("name", ("field name", None::<String>, Option<String>));
262 datapoint_info!("name", ("field name", None::<f64>, Option<f64>));
263 datapoint_trace!("name", ("field name", None::<bool>, Option<bool>));
264 datapoint_warn!("name", ("field name", None::<i64>, Option<i64>));
265 datapoint_error!("name", ("field name", None::<i64>, Option<i64>),);
266
267 let point = create_datapoint!(
268 @point "name",
269 ("some_i64", Some(1), Option<i64>),
270 ("no_i64", None::<i64>, Option<i64>),
271 ("some_String", Some("string space string"), Option<String>),
272 ("no_String", None::<String>, Option<String>),
273 ("some_f64", Some(12.34_f64), Option<f64>),
274 ("no_f64", None::<f64>, Option<f64>),
275 ("some_bool", Some(true), Option<bool>),
276 ("no_bool", None::<bool>, Option<bool>),
277 );
278 assert_eq!(point.name, "name");
279 assert_eq!(point.tags.len(), 0);
280 assert_eq!(point.fields[0], ("some_i64", "1i".to_string()));
281 assert_eq!(
282 point.fields[1],
283 ("some_String", "\"string space string\"".to_string())
284 );
285 assert_eq!(point.fields[2], ("some_f64", "12.34".to_string()));
286 assert_eq!(point.fields[3], ("some_bool", "true".to_string()));
287 assert_eq!(point.fields.len(), 4);
288 }
289
290 #[test]
291 fn test_datapoint_with_tags() {
292 datapoint_debug!("name", "tag" => "tag-value", ("field name", "test", String));
293 datapoint_info!(
294 "name",
295 "tag" => "tag-value",
296 "tag2" => "tag-value-2",
297 ("field name", 12.34_f64, f64)
298 );
299 datapoint_trace!(
300 "name",
301 "tag" => "tag-value",
302 "tag2" => "tag-value-2",
303 "tag3" => "tag-value-3",
304 ("field name", true, bool)
305 );
306 datapoint_warn!("name", "tag" => "tag-value");
307 datapoint_error!("name", "tag" => "tag-value", ("field name", 1, i64),);
308 datapoint!(
309 log::Level::Warn,
310 "name",
311 "tag" => "tag-value",
312 ("field1 name", 2, i64),
313 ("field2 name", 2, i64)
314 );
315 datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
316 datapoint_trace!(
317 "name",
318 "tag" => "tag-value",
319 ("field1 name", 2, i64),
320 ("field2 name", 2, i64),
321 ("field3 name", 3, i64)
322 );
323 datapoint!(
324 log::Level::Error,
325 "name",
326 "tag" => "tag-value",
327 ("field1 name", 2, i64),
328 ("field2 name", 2, i64),
329 ("field3 name", 3, i64),
330 );
331
332 let point = create_datapoint!(
333 @point "name",
334 "tag1" => "tag-value-1",
335 "tag2" => "tag-value-2",
336 "tag3" => "tag-value-3",
337 ("i64", 1, i64),
338 ("String", "string space string", String),
339 ("f64", 12.34_f64, f64),
340 ("bool", true, bool)
341 );
342 assert_eq!(point.name, "name");
343 assert_eq!(point.fields[0], ("i64", "1i".to_string()));
344 assert_eq!(
345 point.fields[1],
346 ("String", "\"string space string\"".to_string())
347 );
348 assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
349 assert_eq!(point.fields[3], ("bool", "true".to_string()));
350 assert_eq!(point.tags[0], ("tag1", "tag-value-1".to_string()));
351 assert_eq!(point.tags[1], ("tag2", "tag-value-2".to_string()));
352 assert_eq!(point.tags[2], ("tag3", "tag-value-3".to_string()));
353 }
354}