Skip to main content

logtail_rust/
lib.rs

1use crate::http_client::service;
2use crate::http_client::HttpClient;
3use crate::http_client::ReqwestClient;
4use crate::r#struct::env_config::{EnvConfig, EnvEnum};
5use crate::r#struct::log_level::LogLevel;
6// re-export LogSchema to make usable by consumer
7pub use crate::r#struct::log_schema::LogSchema;
8pub mod http_client;
9mod r#struct;
10
11pub struct Logger<C: HttpClient = ReqwestClient> {
12    env_config: EnvConfig,
13    client: C,
14}
15
16impl Default for Logger<ReqwestClient> {
17    fn default() -> Self {
18        let env_config = EnvConfig::default();
19        Self {
20            env_config,
21            client: ReqwestClient,
22        }
23    }
24}
25
26impl Logger<ReqwestClient> {
27    pub fn new(app_version: String, verbose: bool) -> Self {
28        let env_config = EnvConfig::new(app_version, verbose);
29        Self {
30            env_config,
31            client: ReqwestClient,
32        }
33    }
34}
35
36impl<C: HttpClient> Logger<C> {
37    #[cfg(test)]
38    pub(crate) fn with_client(env_config: EnvConfig, client: C) -> Self {
39        Self { env_config, client }
40    }
41
42    pub async fn info(&self, log: LogSchema) {
43        let env_config = &self.env_config;
44        let better_log = log.to_betterstack(env_config, LogLevel::Info);
45        if better_log.env != EnvEnum::Local {
46            let _result = service::push_log(&self.client, env_config, &better_log).await;
47        }
48        if env_config.verbose {
49            println!("{}", better_log);
50        }
51    }
52
53    pub async fn warn(&self, log: LogSchema) {
54        let env_config = &self.env_config;
55        let better_log = log.to_betterstack(&self.env_config, LogLevel::Warn);
56        if better_log.env != EnvEnum::Local {
57            let _result = service::push_log(&self.client, env_config, &better_log).await;
58        }
59        if self.env_config.verbose {
60            println!("{}", better_log);
61        }
62    }
63
64    pub async fn error(&self, log: LogSchema) {
65        let env_config = &self.env_config;
66        let better_log = log.to_betterstack(&self.env_config, LogLevel::Error);
67        if better_log.env != EnvEnum::Local {
68            let _result = service::push_log(&self.client, env_config, &better_log).await;
69        }
70        if self.env_config.verbose {
71            eprintln!("{}", better_log);
72        }
73    }
74
75    pub async fn debug(&self, log: LogSchema) {
76        let better_log = log.to_betterstack(&self.env_config, LogLevel::Debug);
77        println!("{}", better_log);
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::http_client::mock::MockHttpClient;
85    use crate::r#struct::env_config::EnvConfig;
86    use std::sync::atomic::Ordering;
87
88    fn qa_config() -> EnvConfig {
89        EnvConfig::from_values("1.0.0".to_string(), EnvEnum::QA, "token".to_string(), false)
90    }
91
92    fn local_config() -> EnvConfig {
93        EnvConfig::from_values(
94            "1.0.0".to_string(),
95            EnvEnum::Local,
96            "token".to_string(),
97            false,
98        )
99    }
100
101    fn test_log() -> LogSchema {
102        LogSchema {
103            message: "test".to_string(),
104            context: "ctx".to_string(),
105        }
106    }
107
108    #[tokio::test]
109    async fn info_sends_info_level() {
110        let mock = MockHttpClient::with_success(None);
111        let logger = Logger::with_client(qa_config(), mock);
112
113        logger.info(test_log()).await;
114
115        let body = logger.client.captured_body.lock().unwrap().clone().unwrap();
116        assert_eq!(body["level"], "Info");
117    }
118
119    #[tokio::test]
120    async fn warn_sends_warn_level() {
121        let mock = MockHttpClient::with_success(None);
122        let logger = Logger::with_client(qa_config(), mock);
123
124        logger.warn(test_log()).await;
125
126        let body = logger.client.captured_body.lock().unwrap().clone().unwrap();
127        assert_eq!(body["level"], "Warn");
128    }
129
130    #[tokio::test]
131    async fn error_sends_error_level() {
132        let mock = MockHttpClient::with_success(None);
133        let logger = Logger::with_client(qa_config(), mock);
134
135        logger.error(test_log()).await;
136
137        let body = logger.client.captured_body.lock().unwrap().clone().unwrap();
138        assert_eq!(body["level"], "Error");
139    }
140
141    #[tokio::test]
142    async fn debug_skips_http() {
143        let mock = MockHttpClient::with_success(None);
144        let logger = Logger::with_client(qa_config(), mock);
145
146        logger.debug(test_log()).await;
147
148        assert_eq!(logger.client.call_count.load(Ordering::SeqCst), 0);
149    }
150
151    #[tokio::test]
152    async fn local_env_skips_http() {
153        let mock = MockHttpClient::with_success(None);
154        let logger = Logger::with_client(local_config(), mock);
155
156        logger.info(test_log()).await;
157        logger.warn(test_log()).await;
158        logger.error(test_log()).await;
159
160        assert_eq!(logger.client.call_count.load(Ordering::SeqCst), 0);
161    }
162
163    #[tokio::test]
164    async fn non_local_env_sends_http() {
165        let mock = MockHttpClient::with_success(None);
166        let logger = Logger::with_client(qa_config(), mock);
167
168        logger.info(test_log()).await;
169
170        assert_eq!(logger.client.call_count.load(Ordering::SeqCst), 1);
171    }
172}