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 $tag_name:expr => $tag_value:expr, $($rest:tt)*) => {
125 $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
126 $crate::create_datapoint!(@fields $point $($rest)*);
127 };
128 (@fields $point:ident $tag_name:expr => $tag_value:expr) => {
129 $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
130 };
131
132 (@fields $point:ident ($name:expr, $value:expr, $type:ident) , $($rest:tt)*) => {
134 $crate::create_datapoint!(@field $point $name, $value, $type);
135 $crate::create_datapoint!(@fields $point $($rest)*);
136 };
137 (@fields $point:ident ($name:expr, $value:expr, $type:ident)) => {
138 $crate::create_datapoint!(@field $point $name, $value, $type);
139 };
140
141 (@point $name:expr, $($fields:tt)+) => {
142 {
143 let mut point = $crate::datapoint::DataPoint::new(&$name);
144 $crate::create_datapoint!(@fields point $($fields)+);
145 point
146 }
147 };
148 (@point $name:expr) => {
149 $crate::datapoint::DataPoint::new(&$name)
150 };
151}
152
153#[macro_export]
154macro_rules! datapoint {
155 ($level:expr, $name:expr) => {
156 if log::log_enabled!($level) {
157 $crate::submit($crate::create_datapoint!(@point $name), $level);
158 }
159 };
160 ($level:expr, $name:expr, $($fields:tt)+) => {
161 if log::log_enabled!($level) {
162 $crate::submit($crate::create_datapoint!(@point $name, $($fields)+), $level);
163 }
164 };
165}
166#[macro_export]
167macro_rules! datapoint_error {
168 ($name:expr) => {
169 $crate::datapoint!(log::Level::Error, $name);
170 };
171 ($name:expr, $($fields:tt)+) => {
172 $crate::datapoint!(log::Level::Error, $name, $($fields)+);
173 };
174}
175
176#[macro_export]
177macro_rules! datapoint_warn {
178 ($name:expr) => {
179 $crate::datapoint!(log::Level::Warn, $name);
180 };
181 ($name:expr, $($fields:tt)+) => {
182 $crate::datapoint!(log::Level::Warn, $name, $($fields)+);
183 };
184}
185
186#[macro_export]
187macro_rules! datapoint_info {
188 ($name:expr) => {
189 $crate::datapoint!(log::Level::Info, $name);
190 };
191 ($name:expr, $($fields:tt)+) => {
192 $crate::datapoint!(log::Level::Info, $name, $($fields)+);
193 };
194}
195
196#[macro_export]
197macro_rules! datapoint_debug {
198 ($name:expr) => {
199 $crate::datapoint!(log::Level::Debug, $name);
200 };
201 ($name:expr, $($fields:tt)+) => {
202 $crate::datapoint!(log::Level::Debug, $name, $($fields)+);
203 };
204}
205
206#[macro_export]
207macro_rules! datapoint_trace {
208 ($name:expr) => {
209 $crate::datapoint!(log::Level::Trace, $name);
210 };
211 ($name:expr, $($fields:tt)+) => {
212 $crate::datapoint!(log::Level::Trace, $name, $($fields)+);
213 };
214}
215
216#[cfg(test)]
217mod test {
218 #[test]
219 fn test_datapoint() {
220 datapoint_debug!("name", ("field name", "test", String));
221 datapoint_info!("name", ("field name", 12.34_f64, f64));
222 datapoint_trace!("name", ("field name", true, bool));
223 datapoint_warn!("name", ("field name", 1, i64));
224 datapoint_error!("name", ("field name", 1, i64),);
225 datapoint!(
226 log::Level::Warn,
227 "name",
228 ("field1 name", 2, i64),
229 ("field2 name", 2, i64)
230 );
231 datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
232 datapoint_trace!(
233 "name",
234 ("field1 name", 2, i64),
235 ("field2 name", 2, i64),
236 ("field3 name", 3, i64)
237 );
238 datapoint!(
239 log::Level::Error,
240 "name",
241 ("field1 name", 2, i64),
242 ("field2 name", 2, i64),
243 ("field3 name", 3, i64),
244 );
245
246 let point = create_datapoint!(
247 @point "name",
248 ("i64", 1, i64),
249 ("String", "string space string", String),
250 ("f64", 12.34_f64, f64),
251 ("bool", true, bool)
252 );
253 assert_eq!(point.name, "name");
254 assert_eq!(point.tags.len(), 0);
255 assert_eq!(point.fields[0], ("i64", "1i".to_string()));
256 assert_eq!(
257 point.fields[1],
258 ("String", "\"string space string\"".to_string())
259 );
260 assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
261 assert_eq!(point.fields[3], ("bool", "true".to_string()));
262 }
263
264 #[test]
265 fn test_datapoint_with_tags() {
266 datapoint_debug!("name", "tag" => "tag-value", ("field name", "test", String));
267 datapoint_info!(
268 "name",
269 "tag" => "tag-value",
270 "tag2" => "tag-value-2",
271 ("field name", 12.34_f64, f64)
272 );
273 datapoint_trace!(
274 "name",
275 "tag" => "tag-value",
276 "tag2" => "tag-value-2",
277 "tag3" => "tag-value-3",
278 ("field name", true, bool)
279 );
280 datapoint_warn!("name", "tag" => "tag-value");
281 datapoint_error!("name", "tag" => "tag-value", ("field name", 1, i64),);
282 datapoint!(
283 log::Level::Warn,
284 "name",
285 "tag" => "tag-value",
286 ("field1 name", 2, i64),
287 ("field2 name", 2, i64)
288 );
289 datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
290 datapoint_trace!(
291 "name",
292 "tag" => "tag-value",
293 ("field1 name", 2, i64),
294 ("field2 name", 2, i64),
295 ("field3 name", 3, i64)
296 );
297 datapoint!(
298 log::Level::Error,
299 "name",
300 "tag" => "tag-value",
301 ("field1 name", 2, i64),
302 ("field2 name", 2, i64),
303 ("field3 name", 3, i64),
304 );
305
306 let point = create_datapoint!(
307 @point "name",
308 "tag1" => "tag-value-1",
309 "tag2" => "tag-value-2",
310 "tag3" => "tag-value-3",
311 ("i64", 1, i64),
312 ("String", "string space string", String),
313 ("f64", 12.34_f64, f64),
314 ("bool", true, bool)
315 );
316 assert_eq!(point.name, "name");
317 assert_eq!(point.fields[0], ("i64", "1i".to_string()));
318 assert_eq!(
319 point.fields[1],
320 ("String", "\"string space string\"".to_string())
321 );
322 assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
323 assert_eq!(point.fields[3], ("bool", "true".to_string()));
324 assert_eq!(point.tags[0], ("tag1", "tag-value-1".to_string()));
325 assert_eq!(point.tags[1], ("tag2", "tag-value-2".to_string()));
326 assert_eq!(point.tags[2], ("tag3", "tag-value-3".to_string()));
327 }
328}