mezmo/
lib.rs

1use serde::Serialize;
2use std::collections::HashMap;
3use std::vec::Vec;
4
5use gethostname::gethostname;
6use reqwest;
7
8const BASE_URL: &str = "https://logs.logdna.com/logs/ingest";
9
10type Line = HashMap<String, String>;
11type Lines = Vec<Line>;
12
13#[derive(Serialize)]
14struct Body {
15    lines: Lines,
16}
17
18pub struct Logger {
19    blocking_client: reqwest::blocking::Client,
20    client: reqwest::Client,
21    apikey: String,
22    hostname: String,
23    tags: String,
24    app: String,
25}
26
27impl Logger {
28    // Create a new logger.
29    pub fn new(apikey: String, tags: String, app: String) -> Self {
30        let blocking_client = reqwest::blocking::Client::new();
31        let client = reqwest::Client::new();
32        let hostname = gethostname().into_string().unwrap();
33
34        Self {
35            blocking_client,
36            client,
37            apikey,
38            hostname,
39            tags,
40            app,
41        }
42    }
43
44    // Sends a log line to Mezmo. This is a blocking call.
45    pub fn blocking_log(
46        &self,
47        log_line: String,
48        level: String,
49    ) -> Result<reqwest::blocking::Response, reqwest::Error> {
50        let timestamp = std::time::SystemTime::now()
51            .duration_since(std::time::UNIX_EPOCH)
52            .unwrap()
53            .as_millis();
54
55        let query = format!(
56            "hostname={}&timestamp={}&tags={}",
57            self.hostname, timestamp, self.tags
58        );
59
60        let url = format!("{}?{}", BASE_URL, query);
61
62        let mut lines = Lines::new();
63
64        let mut line = Line::new();
65
66        let app = self.app.clone();
67
68        line.insert("line".to_string(), log_line);
69        line.insert("app".to_string(), app);
70        line.insert("level".to_string(), level);
71        line.insert("timestamp".to_string(), timestamp.to_string());
72
73        lines.push(line);
74
75        let body = Body { lines };
76
77        let res = self
78            .blocking_client
79            .post(&url)
80            .header("Content-Type", "application/json")
81            .header("apikey", &self.apikey)
82            .json(&body)
83            .send();
84
85        return res;
86    }
87
88    // Sends a log line to Mezmo. This is an async call.
89    pub async fn log(
90        &self,
91        log_line: String,
92        level: String,
93    ) -> Result<reqwest::Response, reqwest::Error> {
94        // get the current unix timestamp in ms
95        let timestamp = std::time::SystemTime::now()
96            .duration_since(std::time::UNIX_EPOCH)
97            .unwrap()
98            .as_millis();
99
100        let query = format!(
101            "hostname={}&timestamp={}&tags={}",
102            self.hostname, timestamp, self.tags
103        );
104
105        let url = format!("{}?{}", BASE_URL, query);
106
107        let mut lines = Lines::new();
108
109        let mut line = Line::new();
110
111        // copy the app name into the current fn
112        let app = self.app.clone();
113
114        line.insert("line".to_string(), log_line);
115        line.insert("app".to_string(), app);
116        line.insert("level".to_string(), level);
117        line.insert("timestamp".to_string(), timestamp.to_string());
118
119        lines.push(line);
120
121        let body = Body { lines };
122
123        let res = self
124            .client
125            .post(&url)
126            .header("Content-Type", "application/json")
127            .header("apikey", &self.apikey)
128            .json(&body)
129            .send()
130            .await;
131
132        return res;
133    }
134}
135
136#[cfg(test)]
137mod tests {
138
139    use std::env;
140
141    use crate::Logger;
142
143    fn getenv(key: &str) -> String {
144        match env::var(key) {
145            Ok(val) => val,
146            Err(_) => panic!("{} is not set", key),
147        }
148    }
149
150    #[test]
151    fn test_constructor() {
152        let apikey = getenv("LOGDNA_APIKEY");
153        let tags = getenv("LOGDNA_TAGS");
154        Logger::new(apikey, tags, "test".to_string());
155    }
156
157    macro_rules! aw {
158        ($e:expr) => {
159            tokio_test::block_on($e)
160        };
161    }
162
163    #[test]
164    fn test_log() {
165        let apikey = getenv("LOGDNA_APIKEY");
166        let tags = getenv("LOGDNA_TAGS");
167        let logger = Logger::new(apikey, tags, "test".to_string());
168
169        let res = logger.log("test log".to_string(), "info".to_string());
170
171        assert!(aw!(res).is_ok());
172    }
173
174    #[test]
175    fn test_blocking_log() {
176        let apikey = getenv("LOGDNA_APIKEY");
177        let tags = getenv("LOGDNA_TAGS");
178        let logger = Logger::new(apikey, tags, "test".to_string());
179
180        let res = logger.blocking_log("test blocking log".to_string(), "info".to_string());
181
182        assert!(res.is_ok());
183    }
184}