use std::sync::RwLock;
use super::LogLevel;
use super::interface::{Indexed, LogStorage, LogWriter, Loggable};
use super::memory_logger::MemoryLogger;
use super::nop_logger::NopLogger;
use super::stdout_logger::StdOutLogger;
use crate::app_types::{ApplicationName, PdpID};
use crate::bootstrap_config::log_config::{LogConfig, LogTypeConfig};
use crate::lock::LockService;
use crate::lock::health_registry::HealthRegistry;
use crate::log::BaseLogEntry;
use crate::log::loggable_fn::LoggableFn;
use serde::Serialize;
pub(crate) struct LogStrategy {
logger: LogStrategyLogger,
pdp_id: PdpID,
app_name: Option<ApplicationName>,
lock_service: RwLock<Option<LockService>>,
log_level: Option<LogLevel>,
}
pub(crate) enum LogStrategyLogger {
Off(NopLogger),
MemoryLogger(Box<MemoryLogger>),
StdOut(StdOutLogger),
}
impl LogStrategy {
pub(crate) fn new(
config: &LogConfig,
pdp_id: PdpID,
app_name: Option<ApplicationName>,
) -> Self {
let logger = match &config.log_type {
LogTypeConfig::Off => LogStrategyLogger::Off(NopLogger),
LogTypeConfig::Memory(memory_config) => LogStrategyLogger::MemoryLogger(Box::new(
MemoryLogger::new(*memory_config, config.log_level, pdp_id, app_name.clone()),
)),
LogTypeConfig::StdOut(stdout_config) => {
let logger = StdOutLogger::new(config.log_level, *stdout_config);
LogStrategyLogger::StdOut(logger)
},
};
Self {
logger,
pdp_id,
app_name,
lock_service: RwLock::new(None),
log_level: Some(config.log_level),
}
}
pub(super) fn new_with_logger(
logger: LogStrategyLogger,
pdp_id: PdpID,
app_name: Option<ApplicationName>,
lock_service: Option<LockService>,
) -> Self {
Self {
logger,
pdp_id,
app_name,
lock_service: RwLock::new(lock_service),
log_level: None,
}
}
#[cfg(test)]
pub(super) fn logger(&self) -> &LogStrategyLogger {
&self.logger
}
fn would_log(&self, entry_level: Option<LogLevel>) -> bool {
let has_lock = self
.lock_service
.read()
.expect("obtain lock_service read lock")
.is_some();
if has_lock {
return true;
}
if matches!(self.logger, LogStrategyLogger::Off(_)) {
return false;
}
if let Some(logger_level) = &self.log_level
&& let Some(entry_log_level) = entry_level
{
return *logger_level <= entry_log_level;
}
true
}
fn log_entry<T: Loggable>(&self, entry: T) {
let entry =
LogEntryWithClientInfo::from_loggable(entry, self.pdp_id, self.app_name.clone());
if let Some(lock_service) = self
.lock_service
.read()
.expect("obtain lock_service read lock")
.as_ref()
{
lock_service.log_any(entry.clone());
}
match &self.logger {
LogStrategyLogger::Off(log) => log.log_any(entry),
LogStrategyLogger::MemoryLogger(memory_logger) => memory_logger.log_any(entry),
LogStrategyLogger::StdOut(std_out_logger) => std_out_logger.log_any(entry),
}
}
fn log_fn<F, R>(&self, log_fn: LoggableFn<F>)
where
R: Loggable + Indexed,
for<'a> F: Fn(BaseLogEntry) -> R,
{
if !self.would_log(log_fn.get_log_level()) {
return;
}
if let Some(logger_level) = &self.log_level {
if log_fn.can_log(*logger_level) {
let entry = log_fn.build();
self.log_entry(entry);
}
} else {
let entry = log_fn.build();
self.log_entry(entry);
}
}
pub(crate) fn set_lock_service(&self, lock_service: LockService) {
*self
.lock_service
.write()
.expect("obtain lock_service write lock") = Some(lock_service);
}
pub(crate) fn health_registry(&self) -> Option<HealthRegistry> {
self.lock_service
.read()
.expect("obtain lock_service read lock")
.as_ref()
.and_then(|ls| ls.health_registry())
.cloned()
}
pub(crate) async fn shut_down(&self) {
let lock = self
.lock_service
.write()
.expect("obtain lock_service write lock")
.take();
if let Some(mut lock_service) = lock {
lock_service.shut_down().await;
}
}
}
#[derive(Serialize, PartialEq, Clone)]
pub(crate) struct LogEntryWithClientInfo<Entry: Loggable> {
#[serde(flatten)]
entry: Entry,
pdp_id: PdpID,
#[serde(rename = "application_id", skip_serializing_if = "Option::is_none")]
app_name: Option<ApplicationName>,
}
impl<Entry: Loggable> Indexed for LogEntryWithClientInfo<Entry> {
fn get_id(&self) -> uuid7::Uuid {
self.entry.get_id()
}
fn get_additional_ids(&self) -> Vec<uuid7::Uuid> {
self.entry.get_additional_ids()
}
fn get_tags(&self) -> Vec<&str> {
self.entry.get_tags()
}
}
impl<Entry: Loggable + Indexed> Loggable for LogEntryWithClientInfo<Entry> {
fn get_log_level(&self) -> Option<super::LogLevel> {
self.entry.get_log_level()
}
fn get_log_kind(&self) -> Option<super::LogType> {
self.entry.get_log_kind()
}
}
impl LogWriter for LogStrategy {
fn log_any<T: Loggable>(&self, entry: T) {
if !self.would_log(entry.get_log_level()) {
return;
}
self.log_entry(entry);
}
fn log_fn<F, R>(&self, log_fn: LoggableFn<F>)
where
R: Loggable,
F: Fn(super::BaseLogEntry) -> R,
{
self.log_fn(log_fn);
}
}
impl<E: Loggable> LogEntryWithClientInfo<E> {
pub(crate) fn from_loggable(
entry: E,
pdp_id: PdpID,
app_name: Option<ApplicationName>,
) -> Self {
Self {
entry,
pdp_id,
app_name,
}
}
}
impl LogStorage for LogStrategy {
fn pop_logs(&self) -> Vec<serde_json::Value> {
match &self.logger {
LogStrategyLogger::MemoryLogger(memory_logger) => memory_logger.pop_logs(),
_ => Vec::new(),
}
}
fn get_log_by_id(&self, id: &str) -> Option<serde_json::Value> {
match &self.logger {
LogStrategyLogger::MemoryLogger(memory_logger) => memory_logger.get_log_by_id(id),
_ => None,
}
}
fn get_log_ids(&self) -> Vec<String> {
match &self.logger {
LogStrategyLogger::MemoryLogger(memory_logger) => memory_logger.get_log_ids(),
_ => Vec::new(),
}
}
fn get_logs_by_tag(&self, tag: &str) -> Vec<serde_json::Value> {
match &self.logger {
LogStrategyLogger::MemoryLogger(memory_logger) => memory_logger.get_logs_by_tag(tag),
_ => Vec::new(),
}
}
fn get_logs_by_request_id(&self, request_id: &str) -> Vec<serde_json::Value> {
match &self.logger {
LogStrategyLogger::MemoryLogger(memory_logger) => {
memory_logger.get_logs_by_request_id(request_id)
},
_ => Vec::new(),
}
}
fn get_logs_by_request_id_and_tag(&self, id: &str, tag: &str) -> Vec<serde_json::Value> {
match &self.logger {
LogStrategyLogger::MemoryLogger(memory_logger) => {
memory_logger.get_logs_by_request_id_and_tag(id, tag)
},
_ => Vec::new(),
}
}
}