1use crate::time::current_time_millis;
2use async_trait::async_trait;
3use serde::Serialize;
4use serde_repr::Serialize_repr;
5use std::fmt;
6
7const LIB_USER_AGENT: &str = concat![env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")];
8
9#[derive(Clone, Debug, Serialize_repr, PartialEq, PartialOrd)]
11#[repr(u8)]
12pub enum Severity {
13 Debug = 1,
15 Verbose = 2,
17 Info = 3,
19 Warning = 4,
21 Error = 5,
23 Critical = 6,
25}
26
27pub type LogLevel = Severity;
29
30impl Default for Severity {
31 fn default() -> Self {
32 Severity::Info
33 }
34}
35
36impl std::str::FromStr for Severity {
37 type Err = String;
38 fn from_str(s: &str) -> Result<Severity, Self::Err> {
39 match s {
40 "debug" | "Debug" | "DEBUG" => Ok(Severity::Debug),
41 "verbose" | "Verbose" | "VERBOSE" => Ok(Severity::Verbose),
42 "info" | "Info" | "INFO" => Ok(Severity::Info),
43 "warning" | "Warning" | "WARNING" => Ok(Severity::Warning),
44 "error" | "Error" | "ERROR" => Ok(Severity::Error),
45 "critical" | "Critical" | "CRITICAL" => Ok(Severity::Critical),
46 _ => Err(format!("Invalid severity: {}", s)),
47 }
48 }
49}
50
51impl fmt::Display for Severity {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(
54 f,
55 "{}",
56 match self {
57 Severity::Debug => "Debug",
58 Severity::Verbose => "Verbose",
59 Severity::Info => "Info",
60 Severity::Warning => "Warning",
61 Severity::Error => "Error",
62 Severity::Critical => "Critical",
63 }
64 )
65 }
66}
67
68#[derive(Debug, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub struct LogEntry {
72 pub timestamp: u64,
74 pub severity: Severity,
76 pub text: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub category: Option<String>,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub class_name: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub method_name: Option<String>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub thread_id: Option<String>,
91}
92
93impl fmt::Display for LogEntry {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 write!(f, "{} {} {}", self.timestamp, self.severity, self.text)
99 }
100}
101
102impl Default for LogEntry {
103 fn default() -> LogEntry {
104 LogEntry {
105 timestamp: current_time_millis(),
106 severity: Severity::Debug,
107 text: String::new(),
108 category: None,
109 class_name: None,
110 method_name: None,
111 thread_id: None,
112 }
113 }
114}
115
116#[derive(Serialize, Debug)]
118#[serde(rename_all = "camelCase")]
119struct CxLogMsg<'a> {
120 pub private_key: &'a str,
122 pub application_name: &'a str,
124 pub subsystem_name: &'a str,
126 pub log_entries: Vec<LogEntry>,
128}
129
130#[derive(Clone, Debug)]
131struct CxErr {
132 msg: String,
133}
134
135impl fmt::Display for CxErr {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "{}", &self.msg)
139 }
140}
141impl std::error::Error for CxErr {}
142
143#[derive(Debug)]
145pub struct LogQueue {
146 entries: Vec<LogEntry>,
147}
148
149impl Default for LogQueue {
150 fn default() -> Self {
151 Self {
152 entries: Vec::new(),
153 }
154 }
155}
156
157impl LogQueue {
158 pub fn new() -> Self {
160 Self::default()
161 }
162
163 pub fn from(entries: Vec<LogEntry>) -> Self {
165 Self { entries }
166 }
167
168 pub fn take(&mut self) -> Vec<LogEntry> {
170 let mut ve: Vec<LogEntry> = Vec::new();
171 ve.append(&mut self.entries);
172 ve
173 }
174
175 pub fn is_empty(&self) -> bool {
177 self.entries.is_empty()
178 }
179
180 pub fn clear(&mut self) {
182 self.entries.clear();
183 }
184
185 pub fn log(&mut self, e: LogEntry) {
187 self.entries.push(e)
188 }
189}
190
191impl fmt::Display for LogQueue {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 let mut buf = String::with_capacity(256);
194 for entry in self.entries.iter() {
195 if !buf.is_empty() {
196 buf.push('\n');
197 }
198 buf.push_str(&entry.to_string());
199 }
200 write!(f, "{}", buf)
201 }
202}
203
204#[async_trait(?Send)]
206pub trait Logger: Send {
207 async fn send(
209 &self,
210 sub: &'_ str,
211 entries: Vec<LogEntry>,
212 ) -> Result<(), Box<dyn std::error::Error>>;
213}
214
215#[doc(hidden)]
217struct BlackHoleLogger {}
218#[async_trait(?Send)]
219impl Logger for BlackHoleLogger {
220 async fn send(&self, _: &'_ str, _: Vec<LogEntry>) -> Result<(), Box<dyn std::error::Error>> {
221 Ok(())
222 }
223}
224
225#[doc(hidden)]
226pub fn silent_logger() -> Box<impl Logger> {
229 Box::new(BlackHoleLogger {})
230}
231
232#[derive(Debug)]
234pub struct CoralogixConfig<'config> {
235 pub api_key: &'config str,
237 pub application_name: &'config str,
239 pub endpoint: &'config str,
241}
242
243#[derive(Debug)]
245pub struct CoralogixLogger {
246 api_key: String,
247 application_name: String,
248 endpoint: String,
249 client: reqwest::Client,
250}
251
252impl CoralogixLogger {
253 pub fn init(config: CoralogixConfig) -> Result<Box<dyn Logger + Send>, reqwest::Error> {
255 use reqwest::header::{self, HeaderValue, CONTENT_TYPE, USER_AGENT};
256 let mut headers = header::HeaderMap::new();
257 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
259 headers.insert(USER_AGENT, HeaderValue::from_static(LIB_USER_AGENT));
262
263 let client = reqwest::Client::builder()
264 .default_headers(headers)
265 .build()?;
266 Ok(Box::new(Self {
267 api_key: config.api_key.to_string(),
268 application_name: config.application_name.to_string(),
269 endpoint: config.endpoint.to_string(),
270 client,
271 }))
272 }
273}
274
275#[async_trait(?Send)]
276impl Logger for CoralogixLogger {
277 async fn send(
280 &self,
281 sub: &'_ str,
282 entries: Vec<LogEntry>,
283 ) -> Result<(), Box<dyn std::error::Error>> {
284 if !entries.is_empty() {
285 let msg = CxLogMsg {
286 subsystem_name: sub,
287 log_entries: entries,
288 private_key: &self.api_key,
289 application_name: &self.application_name,
290 };
291 let resp = self
292 .client
293 .post(&self.endpoint)
294 .json(&msg)
295 .send()
296 .await
297 .map_err(|e| CxErr { msg: e.to_string() })?;
298 check_status(resp)
299 .await
300 .map_err(|e| CxErr { msg: e.to_string() })?;
301 }
302 Ok(())
303 }
304}
305
306#[derive(Default, Debug)]
313pub struct ConsoleLogger {}
314
315impl ConsoleLogger {
316 pub fn init() -> Box<dyn Logger + Send> {
318 Box::new(ConsoleLogger::default())
319 }
320}
321
322#[cfg(target_arch = "wasm32")]
323#[async_trait(?Send)]
324impl Logger for ConsoleLogger {
325 async fn send(
327 &self,
328 sub: &'_ str,
329 entries: Vec<LogEntry>,
330 ) -> Result<(), Box<dyn std::error::Error>> {
331 for e in entries.iter() {
332 let msg = format!("{} {} {} {}", e.timestamp, sub, e.severity, e.text);
333 web_sys::console::log_1(&wasm_bindgen::JsValue::from_str(&msg));
334 }
335 Ok(())
336 }
337}
338
339#[cfg(not(target_arch = "wasm32"))]
341#[async_trait(?Send)]
342impl Logger for ConsoleLogger {
343 async fn send(
345 &self,
346 sub: &'_ str,
347 entries: Vec<LogEntry>,
348 ) -> Result<(), Box<dyn std::error::Error>> {
349 for e in entries.iter() {
350 println!("{} {} {} {}", e.timestamp, sub, e.severity, e.text);
351 }
352 Ok(())
353 }
354}
355
356async fn check_status(resp: reqwest::Response) -> Result<(), Box<dyn std::error::Error>> {
360 let status = resp.status().as_u16();
361 if (200..300).contains(&status) {
362 Ok(())
363 } else {
364 let body = resp.text().await.unwrap_or_default();
365 Err(Box::new(Error::Cx(format!(
366 "Logging Error: status:{} {}",
367 status, body
368 ))))
369 }
370}
371
372#[derive(Debug)]
373enum Error {
374 Cx(String),
376}
377
378impl std::fmt::Display for Error {
379 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
380 write!(
381 f,
382 "{}",
383 match self {
384 Error::Cx(s) => s,
385 }
386 )
387 }
388}
389
390impl std::error::Error for Error {}