use parking_lot::RwLock;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::Duration;
use super::entry::{LogLevel, QueryLogEntry, QueryStats};
use super::format::{format_debug, format_error, format_slow};
static LOGGER_ENABLED: AtomicBool = AtomicBool::new(false);
static LOGGER_TIMING: AtomicBool = AtomicBool::new(true);
static SLOW_QUERY_THRESHOLD_MS: AtomicU64 = AtomicU64::new(100);
static QUERY_COUNT: AtomicU64 = AtomicU64::new(0);
static SLOW_QUERY_COUNT: AtomicU64 = AtomicU64::new(0);
static TOTAL_QUERY_TIME_MS: AtomicU64 = AtomicU64::new(0);
static LOG_LEVEL: RwLock<LogLevel> = RwLock::new(LogLevel::Off);
static QUERY_HISTORY: RwLock<VecDeque<QueryLogEntry>> = RwLock::new(VecDeque::new());
static HISTORY_LIMIT: RwLock<usize> = RwLock::new(100);
pub struct QueryLogger;
impl QueryLogger {
pub fn global() -> QueryLoggerBuilder {
QueryLoggerBuilder::new()
}
pub fn enable() {
LOGGER_ENABLED.store(true, Ordering::SeqCst);
}
pub fn disable() {
LOGGER_ENABLED.store(false, Ordering::SeqCst);
}
pub fn is_enabled() -> bool {
LOGGER_ENABLED.load(Ordering::SeqCst)
}
pub(super) fn timing_enabled() -> bool {
LOGGER_TIMING.load(Ordering::SeqCst)
}
pub fn level() -> LogLevel {
*LOG_LEVEL.read()
}
pub fn set_level(level: LogLevel) {
*LOG_LEVEL.write() = level;
}
pub fn log(entry: QueryLogEntry) {
if !Self::is_enabled() {
return;
}
let level = Self::level();
if level == LogLevel::Off {
return;
}
QUERY_COUNT.fetch_add(1, Ordering::SeqCst);
if let Some(duration) = entry.duration {
TOTAL_QUERY_TIME_MS.fetch_add(duration.as_millis() as u64, Ordering::SeqCst);
}
let threshold = SLOW_QUERY_THRESHOLD_MS.load(Ordering::SeqCst);
let is_slow = entry.is_slow(threshold);
if is_slow {
SLOW_QUERY_COUNT.fetch_add(1, Ordering::SeqCst);
}
{
let mut history = QUERY_HISTORY.write();
let limit = *HISTORY_LIMIT.read();
if limit == 0 {
history.clear();
} else {
while history.len() >= limit {
history.pop_front();
}
history.push_back(entry.clone());
}
}
let should_log = match level {
LogLevel::Off => false,
LogLevel::Error => !entry.success,
LogLevel::Warn => !entry.success || is_slow,
LogLevel::Info => !entry.success || is_slow,
LogLevel::Debug => true,
LogLevel::Trace => true,
};
if should_log {
let output_entry = if Self::timing_enabled() {
entry.clone()
} else {
let mut output_entry = entry.clone();
output_entry.duration = None;
output_entry
};
let output = if level == LogLevel::Trace {
output_entry.format_console()
} else if level >= LogLevel::Debug {
format_debug(&output_entry)
} else if is_slow {
format_slow(&output_entry, threshold)
} else {
format_error(&output_entry)
};
eprintln!("{}", output);
}
}
pub fn log_timed(sql: impl Into<String>, duration: Duration) {
if !Self::is_enabled() {
return;
}
let entry = QueryLogEntry::new(sql).with_duration(duration);
Self::log(entry);
}
pub fn log_error(sql: impl Into<String>, error: impl Into<String>) {
if !Self::is_enabled() {
return;
}
let entry = QueryLogEntry::new(sql).with_error(error);
Self::log(entry);
}
pub fn stats() -> QueryStats {
QueryStats {
total_queries: QUERY_COUNT.load(Ordering::SeqCst),
slow_queries: SLOW_QUERY_COUNT.load(Ordering::SeqCst),
total_time_ms: TOTAL_QUERY_TIME_MS.load(Ordering::SeqCst),
threshold_ms: SLOW_QUERY_THRESHOLD_MS.load(Ordering::SeqCst),
}
}
pub fn reset_stats() {
QUERY_COUNT.store(0, Ordering::SeqCst);
SLOW_QUERY_COUNT.store(0, Ordering::SeqCst);
TOTAL_QUERY_TIME_MS.store(0, Ordering::SeqCst);
}
pub fn history() -> Vec<QueryLogEntry> {
QUERY_HISTORY.read().iter().cloned().collect()
}
pub fn clear_history() {
QUERY_HISTORY.write().clear();
}
pub fn slow_queries() -> Vec<QueryLogEntry> {
let threshold = SLOW_QUERY_THRESHOLD_MS.load(Ordering::SeqCst);
QUERY_HISTORY
.read()
.iter()
.filter(|entry| entry.is_slow(threshold))
.cloned()
.collect()
}
pub fn init_from_env() {
if let Ok(val) = std::env::var("TIDE_LOG_QUERIES") {
if val == "1" || val.to_lowercase() == "true" {
LOGGER_ENABLED.store(true, Ordering::SeqCst);
}
}
if let Ok(val) = std::env::var("TIDE_LOG_LEVEL") {
let level = LogLevel::parse_str(&val);
*LOG_LEVEL.write() = level;
if level != LogLevel::Off {
LOGGER_ENABLED.store(true, Ordering::SeqCst);
}
}
if let Ok(val) = std::env::var("TIDE_SLOW_QUERY_MS") {
if let Ok(ms) = val.parse::<u64>() {
SLOW_QUERY_THRESHOLD_MS.store(ms, Ordering::SeqCst);
}
}
}
}
pub struct QueryLoggerBuilder {
level: Option<LogLevel>,
timing: Option<bool>,
threshold_ms: Option<u64>,
history_limit: Option<usize>,
}
impl QueryLoggerBuilder {
fn new() -> Self {
Self {
level: None,
timing: None,
threshold_ms: None,
history_limit: None,
}
}
pub fn set_level(mut self, level: LogLevel) -> Self {
self.level = Some(level);
self
}
pub fn enable_timing(mut self, enable: bool) -> Self {
self.timing = Some(enable);
self
}
pub fn set_slow_query_threshold_ms(mut self, ms: u64) -> Self {
self.threshold_ms = Some(ms);
self
}
pub fn set_history_limit(mut self, limit: usize) -> Self {
self.history_limit = Some(limit);
self
}
pub fn enable(self) {
if let Some(level) = self.level {
*LOG_LEVEL.write() = level;
}
if let Some(timing) = self.timing {
LOGGER_TIMING.store(timing, Ordering::SeqCst);
}
if let Some(ms) = self.threshold_ms {
SLOW_QUERY_THRESHOLD_MS.store(ms, Ordering::SeqCst);
}
if let Some(limit) = self.history_limit {
*HISTORY_LIMIT.write() = limit;
}
LOGGER_ENABLED.store(true, Ordering::SeqCst);
}
pub fn disable(self) {
LOGGER_ENABLED.store(false, Ordering::SeqCst);
}
}