logtail_rust/http_client/
service.rs1use super::HttpClient;
2use crate::r#struct::betterstack_log_schema::BetterStackLogSchema;
3use crate::r#struct::env_config::EnvConfig;
4use reqwest::header::{HeaderMap, HeaderValue};
5use serde_json::Value;
6
7pub async fn push_log(
20 client: &impl HttpClient,
21 config: &EnvConfig,
22 log: &BetterStackLogSchema,
23) -> Option<Value> {
24 let logs_url = "https://in.logs.betterstack.com";
25 let bearer_header = bearer_headers(config);
26 let body = serde_json::to_value(log).expect("Failed to serialize log to JSON");
27
28 let http_result = client.post_json(logs_url, &body, Some(bearer_header)).await;
29
30 match http_result {
31 Err(err) => {
32 println!("!!! Error sending log : {}", err);
33 None
36 }
37 Ok(continuation_value) => Some(continuation_value?),
38 }
39}
40
41fn bearer_headers(config: &EnvConfig) -> HeaderMap {
50 let logs_source_token = config.logs_source_token.as_str();
51 let bearer_value_str = format!("Bearer {}", logs_source_token);
52 let bearer_value = &bearer_value_str;
53
54 let mut headers = HeaderMap::new();
55
56 headers.insert(
57 "Authorization",
58 HeaderValue::from_str(bearer_value).unwrap(),
59 );
60 headers.insert(
61 "Content-Type",
62 HeaderValue::from_str("application/json").unwrap(),
63 );
64
65 headers
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::http_client::mock::MockHttpClient;
72 use crate::r#struct::env_config::{EnvConfig, EnvEnum};
73 use crate::r#struct::log_level::LogLevel;
74 use std::sync::atomic::Ordering;
75
76 fn test_config() -> EnvConfig {
77 EnvConfig::from_values(
78 "1.0.0".to_string(),
79 EnvEnum::QA,
80 "test-source-token".to_string(),
81 false,
82 )
83 }
84
85 fn test_log() -> BetterStackLogSchema {
86 BetterStackLogSchema {
87 env: EnvEnum::QA,
88 message: "test message".to_string(),
89 context: "test context".to_string(),
90 level: LogLevel::Info,
91 app_version: "1.0.0".to_string(),
92 }
93 }
94
95 #[tokio::test]
96 async fn calls_correct_url() {
97 let mock = MockHttpClient::with_success(None);
98 push_log(&mock, &test_config(), &test_log()).await;
99
100 let url = mock.captured_url.lock().unwrap().clone().unwrap();
101 assert_eq!(url, "https://in.logs.betterstack.com");
102 }
103
104 #[tokio::test]
105 async fn sends_bearer_header() {
106 let mock = MockHttpClient::with_success(None);
107 push_log(&mock, &test_config(), &test_log()).await;
108
109 let headers = mock.captured_headers.lock().unwrap().clone().unwrap();
110 assert_eq!(
111 headers.get("Authorization").unwrap().to_str().unwrap(),
112 "Bearer test-source-token"
113 );
114 }
115
116 #[tokio::test]
117 async fn sends_content_type_json() {
118 let mock = MockHttpClient::with_success(None);
119 push_log(&mock, &test_config(), &test_log()).await;
120
121 let headers = mock.captured_headers.lock().unwrap().clone().unwrap();
122 assert_eq!(
123 headers.get("Content-Type").unwrap().to_str().unwrap(),
124 "application/json"
125 );
126 }
127
128 #[tokio::test]
129 async fn sends_serialized_log_body() {
130 let mock = MockHttpClient::with_success(None);
131 push_log(&mock, &test_config(), &test_log()).await;
132
133 let body = mock.captured_body.lock().unwrap().clone().unwrap();
134 assert_eq!(body["message"], "test message");
135 assert_eq!(body["context"], "test context");
136 assert_eq!(body["level"], "Info");
137 assert_eq!(body["env"], "QA");
138 assert_eq!(body["app_version"], "1.0.0");
139 }
140
141 #[tokio::test]
142 async fn returns_some_on_success() {
143 let response = serde_json::json!({"status": "ok"});
144 let mock = MockHttpClient::with_success(Some(response.clone()));
145
146 let result = push_log(&mock, &test_config(), &test_log()).await;
147 assert_eq!(result.unwrap(), response);
148 }
149
150 #[tokio::test]
151 async fn returns_none_on_error() {
152 let mock = MockHttpClient::with_error("connection refused");
153
154 let result = push_log(&mock, &test_config(), &test_log()).await;
155 assert!(result.is_none());
156 }
157
158 #[tokio::test]
159 async fn returns_none_on_empty_body() {
160 let mock = MockHttpClient::with_success(None);
161
162 let result = push_log(&mock, &test_config(), &test_log()).await;
163 assert!(result.is_none());
164 assert_eq!(mock.call_count.load(Ordering::SeqCst), 1);
165 }
166}