dmsc 0.1.9

Ri - A high-performance Rust middleware framework with modular architecture
//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
//!
//! This file is part of Ri.
//! The Ri project belongs to the Dunimd Team.
//!
//! Licensed under the Apache License, Version 2.0 (the "License");
//! You may not use this file except in compliance with the License.
//! You may obtain a copy of the License at
//!
//!     http://www.apache.org/licenses/LICENSE-2.0
//!
//! Unless required by applicable law or agreed to in writing, software
//! distributed under the License is distributed on an "AS IS" BASIS,
//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//! See the License for the specific language governing permissions and
//! limitations under the License.

#![allow(non_snake_case)]

//! # Log Analytics Module
//! 
//! This module provides logging analytics functionality for Ri, tracking hook events and generating
//! comprehensive analytics reports. It implements a service module that monitors the application
//! lifecycle and generates JSON reports with event statistics.
//! 
//! ## Key Components
//! 
//! - **RiLogAnalyticsModule**: Main analytics module that implements `ServiceModule`
//! - **AnalyticsState**: Internal struct for tracking analytics data
//! 
//! ## Design Principles
//! 
//! 1. **Non-Intrusive**: Operates by listening to hook events without modifying core functionality
//! 2. **Performance-Focused**: Uses efficient data structures for event tracking
//! 3. **Configurable**: Can be enabled/disabled through configuration
//! 4. **Comprehensive**: Tracks events by kind, phase, and module
//! 5. **Persistent**: Generates JSON reports that can be analyzed later
//! 6. **Non-Critical**: Fails gracefully if analytics operations encounter errors

use std::collections::HashMap as FxHashMap;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::core::{RiResult, RiServiceContext, RiError, ServiceModule};
use crate::hooks::{RiHookBus, RiHookEvent, RiHookKind};
use serde_json::json;

/// Internal analytics state struct.
/// 
/// This struct tracks various metrics about hook events, including:
/// - Total number of events
/// - Events per hook kind
/// - Events per module phase
/// - Events per module name
#[derive(Default)]
struct AnalyticsState {
    /// Total number of hook events processed
    total_events: u64,
    /// Number of events per hook kind
    per_kind: FxHashMap<String, u64>,
    /// Number of events per module phase
    per_phase: FxHashMap<String, u64>,
    /// Number of events per module name
    per_module: FxHashMap<String, u64>,
}

/// Log analytics module for Ri.
/// 
/// This module implements the `ServiceModule` trait and provides analytics functionality
/// by listening to hook events and generating comprehensive reports.
/// 
/// ## Usage
/// 
/// The module is automatically added by the `RiAppBuilder` and doesn't need to be explicitly
/// configured in most cases. It can be enabled/disabled through the configuration file.
/// 
/// ## Configuration
/// 
/// ```yaml
/// analytics:
///   enabled: true  # Enable or disable analytics
/// ```
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiLogAnalyticsModule {
    /// Shared analytics state protected by a mutex
    state: Arc<Mutex<AnalyticsState>>,
    /// Whether analytics is enabled
    enabled: bool,
}

impl Default for RiLogAnalyticsModule {
    fn default() -> Self {
        Self::new()
    }
}

impl RiLogAnalyticsModule {
    /// Creates a new instance of the log analytics module.
    /// 
    /// Returns a new `RiLogAnalyticsModule` with default settings.
    pub fn new() -> Self {
        RiLogAnalyticsModule {
            state: Arc::new(Mutex::new(AnalyticsState::default())),
            enabled: true,
        }
    }

    /// Returns all hook kinds that the analytics module tracks.
    /// 
    /// This method returns a static slice of all hook kinds that the analytics module
    /// registers handlers for.
    fn all_kinds() -> &'static [RiHookKind] {
        use RiHookKind::*;
        const KINDS: [RiHookKind; 9] = [
            Startup,
            Shutdown,
            BeforeModulesInit,
            AfterModulesInit,
            BeforeModulesStart,
            AfterModulesStart,
            BeforeModulesShutdown,
            AfterModulesShutdown,
            ConfigReload,
        ];
        &KINDS
    }

    /// Returns a string label for the given hook kind.
    /// 
    /// This method converts a `RiHookKind` enum variant to a human-readable string.
    /// 
    /// # Parameters
    /// 
    /// - `kind`: The hook kind to get a label for
    /// 
    /// # Returns
    /// 
    /// A static string label for the hook kind
    fn kind_label(kind: RiHookKind) -> &'static str {
        match kind {
            RiHookKind::Startup => "Startup",
            RiHookKind::Shutdown => "Shutdown",
            RiHookKind::BeforeModulesInit => "BeforeModulesInit",
            RiHookKind::AfterModulesInit => "AfterModulesInit",
            RiHookKind::BeforeModulesStart => "BeforeModulesStart",
            RiHookKind::AfterModulesStart => "AfterModulesStart",
            RiHookKind::BeforeModulesShutdown => "BeforeModulesShutdown",
            RiHookKind::AfterModulesShutdown => "AfterModulesShutdown",
            RiHookKind::ConfigReload => "ConfigReload",
        }
    }

    /// Registers hook handlers for all tracked hook kinds.
    /// 
    /// This method registers a handler for each hook kind that updates the analytics state
    /// whenever a hook event is triggered.
    /// 
    /// # Parameters
    /// 
    /// - `hooks`: The hook bus to register handlers with
    fn register_handlers(&self, hooks: &mut RiHookBus) {
        for kind in Self::all_kinds() {
            let state = self.state.clone();
            let id = format!("dms.analytics.{}", Self::kind_label(*kind));
            hooks.register(*kind, id, move |_ctx, event: &RiHookEvent| {
                let mut guard = state
                    .lock()
                    .map_err(|_| RiError::Other("analytics state poisoned".to_string()))?;
                guard.total_events = guard.total_events.saturating_add(1);
                let kind_label = Self::kind_label(event.kind).to_string();
                *guard.per_kind.entry(kind_label).or_insert(0) += 1;
                if let Some(phase) = &event.phase {
                    *guard.per_phase.entry(phase.as_str().to_string()).or_insert(0) += 1;
                }
                if let Some(module) = &event.module {
                    *guard.per_module.entry(module.clone()).or_insert(0) += 1;
                }
                Ok(())
            });
        }
    }

    /// Flushes the analytics summary to a JSON file.
    /// 
    /// This method generates a JSON summary of the analytics state and writes it to a file
    /// in the observability directory.
    /// 
    /// # Parameters
    /// 
    /// - `ctx`: The service context to use for file operations and logging
    /// 
    /// # Returns
    /// 
    /// A `RiResult` indicating success or failure
    fn flush_summary(&self, ctx: &mut RiServiceContext) -> RiResult<()> {
        let snapshot = {
            let guard = self
                .state
                .lock()
                .map_err(|_| RiError::Other("analytics state poisoned".to_string()))?;
            json!({
                "timestamp": SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .map(|d| d.as_secs())
                    .unwrap_or(0),
                "total_events": guard.total_events,
                "per_kind": guard.per_kind,
                "per_phase": guard.per_phase,
                "per_module": guard.per_module,
            })
        };

        let fs = ctx.fs();
        let output = fs.observability_dir().join("lifecycle_analytics.json");
        fs.write_json(&output, &snapshot)?;
        let logger = ctx.logger();
        let _ = logger.info("Ri.LogAnalytics", format!("summary_path={}", output.display()));
        Ok(())
    }
}

impl ServiceModule for RiLogAnalyticsModule {
    /// Returns the name of the analytics module.
    /// 
    /// This name is used for identification, logging, and dependency resolution.
    fn name(&self) -> &str {
        "Ri.LogAnalytics"
    }

    /// Indicates if the analytics module is critical to the operation of the system.
    /// 
    /// The analytics module is non-critical, meaning it can fail without causing the entire
    /// system to fail.
    fn is_critical(&self) -> bool {
        false
    }

    /// Initializes the analytics module.
    /// 
    /// This method:
    /// 1. Reads the analytics configuration from the service context
    /// 2. Enables or disables the module based on configuration
    /// 3. Registers hook handlers if the module is enabled
    /// 
    /// # Parameters
    /// 
    /// - `ctx`: The service context containing configuration and hooks
    /// 
    /// # Returns
    /// 
    /// A `RiResult` indicating success or failure
    fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
        let binding = ctx.config();
        let cfg = binding.config();
        self.enabled = cfg.get_bool("analytics.enabled").unwrap_or(true);
        if !self.enabled {
            return Ok(());
        }
        let hooks: &mut RiHookBus = ctx.hooks_mut();
        self.register_handlers(hooks);
        Ok(())
    }

    /// Flushes analytics data after the application has shutdown.
    /// 
    /// This method generates a final analytics report and writes it to a file after
    /// all modules have been shutdown.
    /// 
    /// # Parameters
    /// 
    /// - `ctx`: The service context containing file system and logging capabilities
    /// 
    /// # Returns
    /// 
    /// A `RiResult` indicating success or failure
    fn after_shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
        if !self.enabled {
            return Ok(());
        }
        self.flush_summary(ctx)
    }
}