# app/modules/monitoring/routes/api/admin_logs.py """ Log management endpoints for admin. Provides endpoints for: - Viewing database logs with filters - Reading file logs - Log statistics - Log settings management - Log cleanup operations """ import logging from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db from app.core.logging import reload_log_level from app.exceptions import ResourceNotFoundException from app.modules.core.services.admin_settings_service import admin_settings_service from app.modules.monitoring.services.admin_audit_service import admin_audit_service from app.modules.monitoring.services.log_service import log_service from app.modules.tenancy.exceptions import ConfirmationRequiredException from app.modules.tenancy.schemas.admin import ( ApplicationLogFilters, ApplicationLogListResponse, FileLogResponse, LogCleanupResponse, LogDeleteResponse, LogFileListResponse, LogSettingsResponse, LogSettingsUpdate, LogSettingsUpdateResponse, LogStatistics, ) from app.modules.tenancy.schemas.auth import UserContext admin_logs_router = APIRouter(prefix="/logs") logger = logging.getLogger(__name__) # ============================================================================ # DATABASE LOGS ENDPOINTS # ============================================================================ @admin_logs_router.get("/database", response_model=ApplicationLogListResponse) def get_database_logs( level: str | None = Query(None, description="Filter by log level"), logger_name: str | None = Query(None, description="Filter by logger name"), module: str | None = Query(None, description="Filter by module"), user_id: int | None = Query(None, description="Filter by user ID"), store_id: int | None = Query(None, description="Filter by store ID"), search: str | None = Query(None, description="Search in message"), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """ Get logs from database with filtering. Supports filtering by level, logger, module, user, store, and date range. Returns paginated results. """ filters = ApplicationLogFilters( level=level, logger_name=logger_name, module=module, user_id=user_id, store_id=store_id, search=search, skip=skip, limit=limit, ) return log_service.get_database_logs(db, filters) @admin_logs_router.get("/statistics", response_model=LogStatistics) def get_log_statistics( days: int = Query(7, ge=1, le=90, description="Number of days to analyze"), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """ Get log statistics for the last N days. Returns counts by level, module, and recent critical errors. """ return log_service.get_log_statistics(db, days) @admin_logs_router.delete("/database/cleanup", response_model=LogCleanupResponse) def cleanup_old_logs( retention_days: int = Query(30, ge=1, le=365), confirm: bool = Query(False, description="Must be true to confirm cleanup"), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """ Delete logs older than retention period. Requires confirmation parameter. """ if not confirm: raise ConfirmationRequiredException(operation="cleanup_logs") deleted_count = log_service.cleanup_old_logs(db, retention_days) # Log action admin_audit_service.log_action( db=db, admin_user_id=current_admin.id, action="cleanup_logs", target_type="application_logs", target_id="bulk", details={"retention_days": retention_days, "deleted_count": deleted_count}, ) return LogCleanupResponse( message=f"Deleted {deleted_count} log entries older than {retention_days} days", deleted_count=deleted_count, ) @admin_logs_router.delete("/database/{log_id}", response_model=LogDeleteResponse) def delete_log( log_id: int, db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Delete a specific log entry.""" message = log_service.delete_log(db, log_id) # Log action admin_audit_service.log_action( db=db, admin_user_id=current_admin.id, action="delete_log", target_type="application_log", target_id=str(log_id), details={}, ) return LogDeleteResponse(message=message) # ============================================================================ # FILE LOGS ENDPOINTS # ============================================================================ @admin_logs_router.get("/files", response_model=LogFileListResponse) def list_log_files( current_admin: UserContext = Depends(get_current_admin_api), ): """ List all available log files. Returns list of log files with size and modification date. """ return LogFileListResponse(files=log_service.list_log_files()) @admin_logs_router.get("/files/{filename}", response_model=FileLogResponse) def get_file_log( filename: str, lines: int = Query(500, ge=1, le=10000, description="Number of lines to read"), current_admin: UserContext = Depends(get_current_admin_api), ): """ Read log file content. Returns the last N lines from the specified log file. """ return log_service.get_file_logs(filename, lines) @admin_logs_router.get("/files/{filename}/download") def download_log_file( filename: str, current_admin: UserContext = Depends(get_current_admin_api), ): """ Download log file. Returns the entire log file for download. """ from pathlib import Path from fastapi.responses import FileResponse from app.core.config import settings # Determine log file path log_file_path = settings.log_file if log_file_path: log_file = Path(log_file_path).parent / filename else: log_file = Path("logs") / filename if not log_file.exists(): raise ResourceNotFoundException(resource_type="LogFile", identifier=filename) # Log action from app.core.database import get_db db_gen = get_db() db = next(db_gen) try: admin_audit_service.log_action( db=db, admin_user_id=current_admin.id, action="download_log_file", target_type="log_file", target_id=filename, details={"size_bytes": log_file.stat().st_size}, ) finally: db.close() return FileResponse( log_file, media_type="text/plain", filename=filename, headers={"Content-Disposition": f'attachment; filename="{filename}"'}, ) # ============================================================================ # LOG SETTINGS ENDPOINTS # ============================================================================ @admin_logs_router.get("/settings", response_model=LogSettingsResponse) def get_log_settings( db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Get current log configuration settings.""" log_level = admin_settings_service.get_setting_value(db, "log_level", "INFO") max_size_mb = admin_settings_service.get_setting_value( db, "log_file_max_size_mb", 10 ) backup_count = admin_settings_service.get_setting_value( db, "log_file_backup_count", 5 ) retention_days = admin_settings_service.get_setting_value( db, "db_log_retention_days", 30 ) file_enabled = admin_settings_service.get_setting_value( db, "file_logging_enabled", "true" ) db_enabled = admin_settings_service.get_setting_value( db, "db_logging_enabled", "true" ) return LogSettingsResponse( log_level=str(log_level), log_file_max_size_mb=int(max_size_mb), log_file_backup_count=int(backup_count), db_log_retention_days=int(retention_days), file_logging_enabled=str(file_enabled).lower() == "true", db_logging_enabled=str(db_enabled).lower() == "true", ) @admin_logs_router.put("/settings", response_model=LogSettingsUpdateResponse) def update_log_settings( settings_update: LogSettingsUpdate, db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """ Update log configuration settings. Changes are applied immediately without restart (for log level). File rotation settings require restart. """ from app.modules.tenancy.schemas.admin import AdminSettingUpdate updated = [] # Update log level if settings_update.log_level: admin_settings_service.update_setting( db, "log_level", AdminSettingUpdate(value=settings_update.log_level), current_admin.id, ) updated.append("log_level") # Reload log level immediately reload_log_level() # Update file rotation settings if settings_update.log_file_max_size_mb: admin_settings_service.update_setting( db, "log_file_max_size_mb", AdminSettingUpdate(value=str(settings_update.log_file_max_size_mb)), current_admin.id, ) updated.append("log_file_max_size_mb") if settings_update.log_file_backup_count is not None: admin_settings_service.update_setting( db, "log_file_backup_count", AdminSettingUpdate(value=str(settings_update.log_file_backup_count)), current_admin.id, ) updated.append("log_file_backup_count") # Update retention if settings_update.db_log_retention_days: admin_settings_service.update_setting( db, "db_log_retention_days", AdminSettingUpdate(value=str(settings_update.db_log_retention_days)), current_admin.id, ) updated.append("db_log_retention_days") # Log action admin_audit_service.log_action( db=db, admin_user_id=current_admin.id, action="update_log_settings", target_type="settings", target_id="logging", details={"updated_fields": updated}, ) return LogSettingsUpdateResponse( message="Log settings updated successfully", updated_fields=updated, note="Log level changes are applied immediately. File rotation settings require restart.", )