use crate::time::current_time_millis;
use async_trait::async_trait;
use serde::Serialize;
use serde_repr::Serialize_repr;
use std::{fmt, rc::Rc, sync::Mutex};
#[derive(Debug, Serialize_repr, PartialEq, PartialOrd)]
#[repr(u8)]
pub enum Severity {
Debug = 1,
Verbose = 2,
Info = 3,
Warning = 4,
Error = 5,
Critical = 6,
}
pub type LogLevel = Severity;
impl Default for Severity {
fn default() -> Self {
Severity::Info
}
}
impl std::str::FromStr for Severity {
type Err = String;
fn from_str(s: &str) -> Result<Severity, Self::Err> {
match s {
"debug" | "Debug" | "DEBUG" => Ok(Severity::Debug),
"verbose" | "Verbose" | "VERBOSE" => Ok(Severity::Verbose),
"info" | "Info" | "INFO" => Ok(Severity::Info),
"warning" | "Warning" | "WARNING" => Ok(Severity::Warning),
"error" | "Error" | "ERROR" => Ok(Severity::Error),
"critical" | "Critical" | "CRITICAL" => Ok(Severity::Critical),
_ => Err(format!("Invalid severity: {}", s)),
}
}
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Severity::Debug => "Debug",
Severity::Verbose => "Verbose",
Severity::Info => "Info",
Severity::Warning => "Warning",
Severity::Error => "Error",
Severity::Critical => "Critical",
}
)
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LogEntry {
pub timestamp: u64,
pub severity: Severity,
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub class_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_id: Option<String>,
}
impl fmt::Display for LogEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.timestamp, self.severity, self.text)
}
}
impl Default for LogEntry {
fn default() -> LogEntry {
LogEntry {
timestamp: current_time_millis(),
severity: Severity::Debug,
text: String::from(""),
category: None,
class_name: None,
method_name: None,
thread_id: None,
}
}
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct CxLogMsg<'a> {
pub private_key: &'a str,
pub application_name: &'a str,
pub subsystem_name: &'a str,
pub log_entries: Vec<LogEntry>,
}
#[derive(Debug)]
pub struct LogQueue {
entries: Vec<LogEntry>,
}
impl Default for LogQueue {
fn default() -> Self {
Self {
entries: Vec::new(),
}
}
}
impl LogQueue {
pub fn new() -> Self {
Self::default()
}
pub fn take(&mut self) -> Vec<LogEntry> {
let mut ve: Vec<LogEntry> = Vec::new();
ve.append(&mut self.entries);
ve
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
impl AppendsLog for LogQueue {
fn log(&mut self, e: LogEntry) {
self.entries.push(e)
}
}
pub trait AppendsLog {
fn log(&mut self, e: LogEntry);
}
pub trait AppendsLogInnerMut {
fn log(&self, e: LogEntry);
}
impl AppendsLogInnerMut for Rc<Mutex<LogQueue>> {
fn log(&self, e: LogEntry) {
let mut queue = self.lock().unwrap();
queue.log(e);
}
}
impl fmt::Display for LogQueue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = String::with_capacity(256);
for entry in self.entries.iter() {
if !buf.is_empty() {
buf.push('\n');
}
buf.push_str(&entry.to_string());
}
write!(f, "{}", buf)
}
}
#[async_trait(?Send)]
pub trait Logger {
async fn send(
&self,
sub: &'static str,
entries: Vec<LogEntry>,
) -> Result<(), Box<dyn std::error::Error>>;
}
pub struct CoralogixConfig {
pub api_key: &'static str,
pub application_name: &'static str,
pub endpoint: &'static str,
}
pub struct CoralogixLogger {
config: CoralogixConfig,
client: reqwest::Client,
}
unsafe impl Send for CoralogixLogger {}
impl CoralogixLogger {
pub fn init(config: CoralogixConfig) -> Result<Box<dyn Logger>, reqwest::Error> {
use reqwest::header::{self, HeaderValue, CONNECTION, CONTENT_TYPE};
let mut headers = header::HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(CONNECTION, HeaderValue::from_static("close"));
let client = reqwest::Client::builder()
.default_headers(headers)
.build()?;
Ok(Box::new(Self { config, client }))
}
}
#[async_trait(?Send)]
impl Logger for CoralogixLogger {
async fn send(
&self,
sub: &'static str,
entries: Vec<LogEntry>,
) -> Result<(), Box<dyn std::error::Error>> {
let msg = CxLogMsg {
subsystem_name: sub,
log_entries: entries,
private_key: self.config.api_key,
application_name: self.config.application_name,
};
let resp = self
.client
.post(self.config.endpoint)
.json(&msg)
.send()
.await?;
check_status(resp).await?;
Ok(())
}
}
#[cfg(any(doc, target_arch = "wasm32"))]
#[derive(Default, Debug)]
pub struct ConsoleLogger {}
#[cfg(any(doc, target_arch = "wasm32"))]
impl ConsoleLogger {
pub fn init() -> Box<dyn Logger + Send> {
Box::new(ConsoleLogger::default())
}
}
#[cfg(any(doc, target_arch = "wasm32"))]
#[async_trait(?Send)]
impl Logger for ConsoleLogger {
async fn send(
&self,
sub: &'static str,
entries: Vec<LogEntry>,
) -> Result<(), Box<dyn std::error::Error>> {
for e in entries.iter() {
let msg = format!("{} {} {} {}", e.timestamp, sub, e.severity, e.text);
web_sys::console::log_1(&wasm_bindgen::JsValue::from_str(&msg));
}
Ok(())
}
}
async fn check_status(resp: reqwest::Response) -> Result<(), Box<dyn std::error::Error>> {
match resp.status().is_success() {
true => Ok(()),
false => {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
Err(Box::new(Error::Cx(format!(
"Logging Error: status:{} {}",
status, body
))))
}
}
}
#[derive(Debug)]
enum Error {
Cx(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Error::Cx(s) => s,
}
)
}
}
impl std::error::Error for Error {}