Files
AIPass/src/aipass/prax/apps/modules/logger.py
T
d714535ad3 feat(prax): introspection 100%, dead module cleanup, stale logs removed (#93)
* feat(flow): feat(flow): template registry system, introspection/help overhaul, no-fallback errors

Co-Authored-By: @flow <flow@aipass>

* feat(prax): monitor restoration phases 1-3, dead module cleanup, help cleanup

Co-Authored-By: @prax <prax@aipass>

* feat(prax): introspection 100%, dead module cleanup, stale logs removed

Co-Authored-By: @prax <prax@aipass>

---------

Co-authored-by: @flow <flow@aipass>
Co-authored-by: @prax <prax@aipass>
2026-03-18 20:37:26 -07:00

300 lines
11 KiB
Python
Executable File

# =================== AIPass ====================
# Name: logger.py
# Description: PRAX Public API
# Version: 1.0.0
# Created: 2025-11-15
# Modified: 2026-03-09
# =============================================
"""
PRAX Logger - Public API
This is the main entry point for PRAX logging system.
Other branches import from here:
from aipass.prax.apps.modules.prax_logger import system_logger
Provides:
- system_logger: Auto-routing logger for all modules
- get_direct_logger / direct_log: Event-pipeline-bypass logging for infrastructure
- Lifecycle functions: initialize, shutdown
- Status and control functions
"""
__all__ = [
"system_logger",
"get_system_logger",
"SystemLogger",
"get_direct_logger",
"direct_log",
"DirectLogger",
"initialize_logging_system",
"shutdown_logging_system",
"start_continuous_logging",
"get_system_status",
"enable_terminal_output",
"disable_terminal_output",
"print_introspection",
"handle_command",
"MODULE_NAME",
"DATA_FILE",
]
import sys
from typing import Dict, Any
# NOTE: CLI imports are done lazily inside functions to avoid circular dependency.
# CLI imports prax logger, so prax logger must not import CLI at module level.
# Import from handlers - internal implementation
from aipass.prax.apps.handlers.logging.setup import (
setup_individual_logger,
get_captured_loggers_count,
enable_terminal_output as _enable_terminal,
disable_terminal_output as _disable_terminal,
)
from aipass.prax.apps.handlers.logging.introspection import get_calling_module
from aipass.prax.apps.handlers.logging.override import (
is_override_active
)
from aipass.prax.apps.handlers.discovery.watcher import (
start_file_watcher,
is_file_watcher_active
)
from aipass.prax.apps.handlers.registry.load import load_module_registry
from aipass.prax.apps.handlers.config.load import get_system_logs_dir, get_module_logs_dir, PRAX_JSON_DIR
from aipass.prax.apps.handlers.logging.direct import (
get_direct_logger,
direct_log,
DirectLogger
)
from aipass.prax.apps.handlers.json import json_handler
# Module constants
MODULE_NAME = "prax_logger"
DATA_FILE = PRAX_JSON_DIR / f"{MODULE_NAME}_data.json"
# =============================================
# SYSTEM LOGGER - THE MAIN EXPORT
# =============================================
def get_system_logger():
"""Get logger that automatically routes to correct module log file"""
module_name = get_calling_module()
return setup_individual_logger(module_name)
class SystemLogger:
"""Auto-routing logger that writes to calling module's log file"""
_watcher_started = False
def _ensure_watcher(self):
"""Lazy-start file watchers on first logger use"""
if not SystemLogger._watcher_started:
# Set flag FIRST to prevent recursion: trigger.fire() uses logger
# internally, which would re-enter _ensure_watcher() before we return
SystemLogger._watcher_started = True
# Start prax watcher (Python file discovery)
# Wrapped in try/except - inotify may be maxed by VS Code
if not is_file_watcher_active():
try:
start_file_watcher()
except OSError:
pass # inotify limit reached, continue without watcher
# Fire startup event (trigger auto-initializes handlers)
try:
from aipass.trigger.apps.modules.core import trigger
trigger.fire('startup')
except (ImportError, OSError):
pass # Trigger not available or inotify full, silent fallback
def info(self, message, *args, **kwargs):
"""Log info message to calling module's log file"""
self._ensure_watcher()
logger = get_system_logger()
logger.info(message, *args, **kwargs)
def warning(self, message, *args, **kwargs):
"""Log warning message to calling module's log file"""
self._ensure_watcher()
logger = get_system_logger()
logger.warning(message, *args, **kwargs)
def error(self, message, *args, **kwargs):
"""Log error message to calling module's log file"""
self._ensure_watcher()
logger = get_system_logger()
logger.error(message, *args, **kwargs)
# Export the logger object - this is what other branches import
system_logger = SystemLogger()
# =============================================
# LIFECYCLE FUNCTIONS
# =============================================
def initialize_logging_system():
"""Initialize the complete logging system
Steps:
1. Create config file if missing
2. Discover all Python modules
3. Save module registry
4. Setup system logger
5. Install logger override
6. Start file watcher
MODULE orchestration pattern: Thin wrapper that delegates to handler.
"""
from aipass.cli.apps.modules import console
from aipass.prax.apps.handlers.logging.lifecycle import run_initialize
console.print(f"[{MODULE_NAME}] Initializing system-wide logging...")
result = run_initialize(MODULE_NAME)
console.print(
f"[{MODULE_NAME}] System initialized - "
f"{result['modules_count']} modules, individual logging"
)
def shutdown_logging_system():
"""Shutdown logging system cleanly
Steps:
1. Stop file watcher
2. Restore original logger
3. Log shutdown operation
MODULE orchestration pattern: Thin wrapper that delegates to handler.
"""
from aipass.cli.apps.modules import console
from aipass.prax.apps.handlers.logging.lifecycle import run_shutdown
console.print(f"[{MODULE_NAME}] Shutting down logging system...")
run_shutdown(MODULE_NAME)
console.print(f"[{MODULE_NAME}] Shutdown complete")
def start_continuous_logging():
"""Start continuous logging in background mode with live terminal output
Enables terminal output and runs until Ctrl+C.
Displays status updates every 5 minutes.
MODULE orchestration pattern: Thin wrapper that delegates to handler.
"""
from aipass.prax.apps.handlers.logging.monitoring import run_monitoring_loop
from aipass.cli.apps.modules import console
console.print(f"[{MODULE_NAME}] Starting continuous logging mode with terminal output...")
sys.stdout.flush()
# Enable terminal output for live debugging
enable_terminal_output()
# Initialize the logging system
initialize_logging_system()
# Delegate to handler for monitoring loop
try:
run_monitoring_loop(
status_callback=get_system_status,
interval=5,
status_interval=300
)
except KeyboardInterrupt:
# Handler re-raises KeyboardInterrupt, we handle cleanup here
disable_terminal_output()
shutdown_logging_system()
console.print(f"[{MODULE_NAME}] Logger capture stopped.")
sys.stdout.flush()
# =============================================
# STATUS AND CONTROL
# =============================================
def get_system_status() -> Dict[str, Any]:
"""Get current logging system status
Returns:
Dict with system status information:
- total_modules: Number of discovered modules
- individual_loggers: Number of active loggers
- module_logs_dir: Path to prax module logs
- registry_file: Path to module registry
- file_watcher_active: Watcher status
- logger_override_active: Override status
"""
modules = load_module_registry()
return {
"total_modules": len(modules),
"individual_loggers": get_captured_loggers_count(),
"system_logs_dir": str(get_system_logs_dir()),
"module_logs_dir": str(get_module_logs_dir("prax")),
"registry_file": str(DATA_FILE),
"file_watcher_active": is_file_watcher_active(),
"logger_override_active": is_override_active()
}
def enable_terminal_output():
"""Enable terminal output for all future loggers"""
_enable_terminal()
def disable_terminal_output():
"""Disable terminal output"""
_disable_terminal()
def print_introspection():
"""Display module introspection info."""
try:
from aipass.cli.apps.modules.display import console
except ImportError:
from rich.console import Console
console = Console()
console.print()
console.print("logger Module")
console.print("Public API for PRAX system-wide logging with auto-routing and lifecycle management")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/logging/")
console.print(" - setup.py (setup_individual_logger — creates per-module log files)")
console.print(" - setup.py (get_captured_loggers_count — returns active logger count)")
console.print(" - setup.py (enable_terminal_output — enables live terminal log output)")
console.print(" - setup.py (disable_terminal_output — disables terminal log output)")
console.print(" - introspection.py (get_calling_module — resolves caller module name)")
console.print(" - override.py (is_override_active — checks logger override status)")
console.print(" - direct.py (get_direct_logger — bypasses event pipeline for infrastructure logging)")
console.print(" - direct.py (direct_log — shorthand for direct logging calls)")
console.print(" - direct.py (DirectLogger — direct logger class)")
console.print(" handlers/discovery/")
console.print(" - watcher.py (start_file_watcher — starts filesystem watcher for module discovery)")
console.print(" - watcher.py (stop_file_watcher — stops the filesystem watcher)")
console.print(" - watcher.py (is_file_watcher_active — checks watcher status)")
console.print(" handlers/registry/")
console.print(" - load.py (load_module_registry — loads discovered module registry)")
console.print(" handlers/config/")
console.print(" - load.py (get_system_logs_dir — returns system logs directory path)")
console.print(" - load.py (get_module_logs_dir — returns per-module logs directory path)")
console.print(" - load.py (PRAX_JSON_DIR — base path for prax JSON data files)")
console.print()
def handle_command(_command: str, args: list) -> bool:
"""Handle commands routed by the entry point.
Logger is a service module with no user-facing commands.
All interaction happens through the system_logger API.
"""
json_handler.log_operation("logger_handle_command", {"args": args})
if not args:
print_introspection()
return True
if args[0] in ('--help', '-h', 'help'):
print_introspection() # Logger has no user commands — introspection IS the help
return True
return False