use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
tokio::task_local! {
static CURRENT_USER_ID: String;
}
pub async fn run_with_user<F>(user_id: &str, f: F) -> F::Output
where
F: Future,
{
CURRENT_USER_ID.scope(user_id.to_string(), f).await
}
pub fn get_current_user_id() -> Option<String> {
CURRENT_USER_ID.try_with(|id| id.clone()).ok()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
pub id: String,
pub timestamp: i64,
pub level: LogLevel,
pub event_type: String,
pub message: String,
pub user_id: Option<String>,
pub metadata: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl LogLevel {
pub fn as_str(&self) -> &str {
match self {
LogLevel::Trace => "TRACE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
}
}
}
#[async_trait]
pub trait Logger: Send + Sync {
async fn log(&self, entry: LogEntry) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
async fn query(
&self,
user_id: &str,
limit: Option<usize>,
from_timestamp: Option<i64>,
) -> Result<Vec<LogEntry>, Box<dyn std::error::Error + Send + Sync>> {
let _ = (user_id, limit, from_timestamp);
Ok(vec![])
}
}
pub struct NoOpLogger;
impl Default for NoOpLogger {
fn default() -> Self {
Self::new()
}
}
impl NoOpLogger {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Logger for NoOpLogger {
async fn log(&self, _entry: LogEntry) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
}
pub struct StdoutLogger;
impl Default for StdoutLogger {
fn default() -> Self {
Self::new()
}
}
impl StdoutLogger {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Logger for StdoutLogger {
async fn log(&self, entry: LogEntry) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let metadata_str = if let Some(meta) = &entry.metadata {
format!(" {:?}", meta)
} else {
String::new()
};
eprintln!(
"[{}] [{}] - {}{}",
entry.level.as_str(),
entry.event_type,
entry.message,
metadata_str
);
Ok(())
}
}
pub struct UserLogger {
user_id: String,
logger: Arc<dyn Logger>,
}
impl UserLogger {
pub fn new(user_id: String, logger: Arc<dyn Logger>) -> Self {
Self { user_id, logger }
}
pub fn user_id(&self) -> &str {
&self.user_id
}
pub async fn log(
&self,
level: LogLevel,
event_type: &str,
message: &str,
metadata: Option<HashMap<String, String>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64;
let entry = LogEntry {
id: uuid::Uuid::new_v4().to_string(),
timestamp,
level,
event_type: event_type.to_string(),
message: message.to_string(),
user_id: Some(self.user_id.clone()),
metadata,
};
self.logger.log(entry).await
}
pub async fn info(
&self,
event_type: &str,
message: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.log(LogLevel::Info, event_type, message, None).await
}
pub async fn error(
&self,
event_type: &str,
message: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.log(LogLevel::Error, event_type, message, None).await
}
pub async fn warn(
&self,
event_type: &str,
message: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.log(LogLevel::Warn, event_type, message, None).await
}
pub async fn debug(
&self,
event_type: &str,
message: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.log(LogLevel::Debug, event_type, message, None).await
}
pub async fn trace(
&self,
event_type: &str,
message: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.log(LogLevel::Trace, event_type, message, None).await
}
}
pub struct MultiAsyncLogger {
loggers: Vec<Arc<dyn Logger>>,
}
impl MultiAsyncLogger {
pub fn new(loggers: Vec<Arc<dyn Logger>>) -> Self {
Self { loggers }
}
}
#[async_trait]
impl Logger for MultiAsyncLogger {
async fn log(&self, entry: LogEntry) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
for logger in &self.loggers {
if let Err(e) = logger.log(entry.clone()).await {
eprintln!("Error in MultiAsyncLogger: {}", e);
}
}
Ok(())
}
async fn query(
&self,
user_id: &str,
limit: Option<usize>,
from_timestamp: Option<i64>,
) -> Result<Vec<LogEntry>, Box<dyn std::error::Error + Send + Sync>> {
for logger in &self.loggers {
match logger.query(user_id, limit, from_timestamp).await {
Ok(logs) if !logs.is_empty() => return Ok(logs),
Ok(_) => continue, Err(_) => continue,
}
}
Ok(vec![])
}
}
pub struct LogBridge {
logger: Arc<dyn Logger>,
handle: tokio::runtime::Handle,
default_user_id: Option<String>,
}
impl LogBridge {
pub fn new(logger: Arc<dyn Logger>, default_user_id: Option<String>) -> Self {
Self {
logger,
handle: tokio::runtime::Handle::current(),
default_user_id,
}
}
}
impl log::Log for LogBridge {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let level = match record.level() {
log::Level::Error => LogLevel::Error,
log::Level::Warn => LogLevel::Warn,
log::Level::Info => LogLevel::Info,
log::Level::Debug => LogLevel::Debug,
log::Level::Trace => LogLevel::Trace,
};
let entry = LogEntry {
id: uuid::Uuid::new_v4().to_string(),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64,
level,
event_type: record.target().to_string(),
message: record.args().to_string(),
user_id: get_current_user_id().or_else(|| self.default_user_id.clone()),
metadata: None,
};
let logger = self.logger.clone();
self.handle.spawn(async move {
let _ = logger.log(entry).await;
});
}
}
fn flush(&self) {}
}