cedarling 0.0.49

The Cedarling: a high-performance local authorization service powered by the Rust Cedar Engine.
Documentation
// This software is available under the Apache-2.0 license.
// See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
//
// Copyright (c) 2024, Gluu, Inc.

//! Log interface
//! Contains the interface for logging. And getting log information from storage.

use std::sync::{Arc, Weak};

use super::{LogLevel, LogStrategy};
use crate::lock::AuditPayload;
use crate::log::{
    BaseLogEntry,
    loggable_fn::LoggableFn,
    stdout_logger::{StdOutLogger, StdOutLoggerMode},
};
use uuid7::Uuid;

/// Log Writer
/// interface for logging events
pub(crate) trait LogWriter {
    /// log any serializable entry that not suitable for [`LogEntry`]
    fn log_any<T: Loggable>(&self, entry: T);

    fn log_fn<F, R>(&self, log_fn: LoggableFn<F>)
    where
        R: Loggable,
        F: Fn(BaseLogEntry) -> R;
}

impl LogWriter for Option<Arc<LogStrategy>> {
    fn log_any<T: Loggable>(&self, entry: T) {
        if let Some(logger) = self.as_ref() {
            logger.log_any(entry);
        }
    }

    fn log_fn<F, R>(&self, log_fn: LoggableFn<F>)
    where
        R: Loggable,
        F: Fn(BaseLogEntry) -> R,
    {
        if let Some(logger) = self.as_ref() {
            logger.log_fn(log_fn);
        }
    }
}

impl LogWriter for Option<&Arc<LogStrategy>> {
    fn log_any<T: Loggable>(&self, entry: T) {
        if let Some(logger) = self.as_ref() {
            logger.log_any(entry);
        }
    }

    fn log_fn<F, R>(&self, log_fn: LoggableFn<F>)
    where
        R: Loggable,
        F: Fn(BaseLogEntry) -> R,
    {
        if let Some(logger) = self.as_ref() {
            logger.log_fn(log_fn);
        }
    }
}

impl LogWriter for Option<Weak<LogStrategy>> {
    fn log_any<T: Loggable>(&self, entry: T) {
        if let Some(log_strategy) = self.as_ref().and_then(std::sync::Weak::upgrade) {
            log_strategy.as_ref().log_any(entry);
            return;
        }

        // we log the error manually to stdout if the logger is gone
        StdOutLogger::new(LogLevel::INFO, StdOutLoggerMode::Immediate).log_any(entry);
    }

    fn log_fn<F, R>(&self, log_fn: LoggableFn<F>)
    where
        R: Loggable,
        F: Fn(BaseLogEntry) -> R,
    {
        if let Some(log_strategy) = self.as_ref().and_then(std::sync::Weak::upgrade) {
            log_strategy.as_ref().log_fn(log_fn);
            return;
        }

        let entry = log_fn.build();
        // we log the error manually to stdout if the logger is gone
        StdOutLogger::new(LogLevel::INFO, StdOutLoggerMode::Immediate).log_any(entry);
    }
}

const SEPARATOR: &str = "__";

pub(crate) fn composite_key(id: &str, tag: &str) -> String {
    [id, tag].join(SEPARATOR)
}

pub(crate) trait Indexed {
    /// Get unique ID of entity
    //  Is used in memory logger
    fn get_id(&self) -> Uuid;

    /// List of additional ids that entity can be related
    //  Is used in memory logger
    fn get_additional_ids(&self) -> Vec<Uuid>;

    /// List of `tags` that entity can be related
    //  Is used in memory logger
    fn get_tags(&self) -> Vec<&str>;

    fn get_index_keys(&self) -> Vec<String> {
        let tags = self.get_tags();

        let additional_ids = self
            .get_additional_ids()
            .into_iter()
            .map(|v| v.to_string())
            .collect::<Vec<String>>();

        let additional_id_and_tag = additional_ids
            .iter()
            .flat_map(|id| tags.iter().map(move |tag| composite_key(id, tag)))
            .collect::<Vec<String>>();

        let tags_iter = tags
            .into_iter()
            .map(Into::<String>::into)
            .collect::<Vec<String>>();

        let mut result = Vec::with_capacity(
            additional_ids.len() + additional_id_and_tag.len() + tags_iter.len(),
        );

        result.extend(additional_ids);
        result.extend(additional_id_and_tag);
        result.extend(tags_iter);

        result
    }
}

// static means that entities owns value or has reference with 'static lifetime
pub(crate) trait Loggable:
    serde::Serialize + Indexed + Clone + Send + Sync + Sized + 'static
{
    /// get log level for entity
    /// not all log entities have log level, only when `log_kind` == `System`
    fn get_log_level(&self) -> Option<LogLevel>;

    /// check if entry can log to logger
    // default implementation of method
    // is used to avoid boilerplate code
    fn can_log(&self, logger_level: LogLevel) -> bool {
        can_log(self.get_log_level(), logger_level)
    }

    /// Convert into an [`AuditPayload`] for Lock Server dispatch.
    /// Override for types that should be forwarded to the Lock Server
    /// The default returns `None` (no dispatch)
    fn to_audit_payload(&self) -> Option<AuditPayload> {
        None
    }
}

/// check if entry can log to logger
// default implementation of method
// is used to avoid boilerplate code
pub(super) fn can_log(entity_level: Option<LogLevel>, logger_level: LogLevel) -> bool {
    if let Some(entry_log_level) = entity_level {
        // higher level is more important, ie closer to fatal
        logger_level <= entry_log_level
    } else {
        // if `.get_log_level` return None
        // it means that `log_kind` != `System` and we should log it
        true
    }
}

/// Log Storage
/// interface for getting log entries from the storage
pub trait LogStorage {
    /// Return logs and remove them from the storage
    fn pop_logs(&self) -> Vec<serde_json::Value>;

    /// Get specific log entry
    fn get_log_by_id(&self, id: &str) -> Option<serde_json::Value>;

    /// Returns a list of all log ids
    fn get_log_ids(&self) -> Vec<String>;

    /// Get logs by tag, like `log_kind` or `log level`.
    /// Tag can be `log_kind`, `log_level`.
    fn get_logs_by_tag(&self, tag: &str) -> Vec<serde_json::Value>;

    /// Get logs by `request_id`.
    /// Return log entries that match the given `request_id`.
    fn get_logs_by_request_id(&self, request_id: &str) -> Vec<serde_json::Value>;

    /// Get log by `request_id` and tag, like composite key `request_id` + `log_kind`.
    /// Tag can be `log_kind`, `log_level`.
    /// Return log entries that match the given `request_id` and tag.
    fn get_logs_by_request_id_and_tag(&self, request_id: &str, tag: &str)
    -> Vec<serde_json::Value>;
}