diff --git a/app/api/v1/endpoints/logs.py b/app/api/v1/endpoints/logs.py index e8e55db..e4afef5 100644 --- a/app/api/v1/endpoints/logs.py +++ b/app/api/v1/endpoints/logs.py @@ -17,6 +17,7 @@ import docker from app.core.auth import get_current_user from app.core.docker import docker_client, DEFAULT_TAIL +from app.core.logger import api_logger router = APIRouter() @@ -64,7 +65,7 @@ def api_logs_stats(container_id: str, current_user: str = Depends(get_current_us ) except Exception as e: - print(f"Error getting log stats for {container_id}: {e}") + api_logger.error(f"Error getting log stats for {container_id}: {e}") return JSONResponse({"error": str(e)}, status_code=500) @router.get("/{container_id}") @@ -193,7 +194,7 @@ def api_logs( ) except Exception as e: - print(f"Error getting logs for {container_id}: {e}") + api_logger.error(f"Error getting logs for {container_id}: {e}") return JSONResponse({"error": str(e)}, status_code=500) @router.post("/snapshot") diff --git a/app/api/v1/endpoints/websocket.py b/app/api/v1/endpoints/websocket.py index 5c0d092..38b91fa 100644 --- a/app/api/v1/endpoints/websocket.py +++ b/app/api/v1/endpoints/websocket.py @@ -14,6 +14,7 @@ from fastapi.responses import JSONResponse from app.core.auth import verify_token, get_current_user from app.core.docker import docker_client, DEFAULT_TAIL +from app.core.logger import websocket_logger from datetime import datetime router = APIRouter() @@ -93,30 +94,30 @@ async def ws_logs(ws: WebSocket, container_id: str, tail: int = DEFAULT_TAIL, to # Получаем логи (только последние строки, без follow) try: - print(f"Getting logs for container {container.name} (ID: {container.id[:12]})") + websocket_logger.info(f"Getting logs for container {container.name} (ID: {container.id[:12]})") logs = container.logs(tail=tail).decode(errors="ignore") if logs: await ws.send_text(logs) else: await ws.send_text("No logs available") except Exception as e: - print(f"Error getting logs for {container.name}: {e}") + websocket_logger.error(f"Error getting logs for {container.name}: {e}") await ws.send_text(f"ERROR getting logs: {e}") # Простое WebSocket соединение - только отправляем логи один раз - print(f"WebSocket connection established for {container.name}") + websocket_logger.info(f"WebSocket connection established for {container.name}") except WebSocketDisconnect: - print(f"WebSocket client disconnected for container {container.name}") + websocket_logger.info(f"WebSocket client disconnected for container {container.name}") except Exception as e: - print(f"WebSocket error for {container.name}: {e}") + websocket_logger.error(f"WebSocket error for {container.name}: {e}") try: await ws.send_text(f"ERROR: {e}") except: pass finally: try: - print(f"Closing WebSocket connection for container {container.name}") + websocket_logger.info(f"Closing WebSocket connection for container {container.name}") await ws.close() except: pass diff --git a/app/app.py b/app/app.py index c79648e..4c8108e 100644 --- a/app/app.py +++ b/app/app.py @@ -11,8 +11,9 @@ from fastapi import FastAPI, Request, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates -from app.core.config import DEBUG_MODE, SNAP_DIR, STATIC_DIR +from app.core.config import DEBUG_MODE, SNAP_DIR, STATIC_DIR, APP_PORT from app.api.v1.router import api_router, pages_router +from app.core.logger import app_logger # Инициализация FastAPI app = FastAPI( @@ -81,11 +82,11 @@ app.include_router(pages_router) if __name__ == "__main__": import uvicorn - print(f"LogBoard+ http://0.0.0.0:{APP_PORT}") - print(f"Debug mode: {'ON' if DEBUG_MODE else 'OFF'}") + app_logger.info(f"LogBoard+ http://0.0.0.0:{APP_PORT}") + app_logger.info(f"Debug mode: {'ON' if DEBUG_MODE else 'OFF'}") if DEBUG_MODE: - print("Swagger UI: http://0.0.0.0:{}/docs".format(APP_PORT)) - print("ReDoc: http://0.0.0.0:{}/redoc".format(APP_PORT)) + app_logger.info("Swagger UI: http://0.0.0.0:{}/docs".format(APP_PORT)) + app_logger.info("ReDoc: http://0.0.0.0:{}/redoc".format(APP_PORT)) uvicorn.run( app, host="0.0.0.0", diff --git a/app/core/docker.py b/app/core/docker.py index 3d7eed5..bdb9346 100644 --- a/app/core/docker.py +++ b/app/core/docker.py @@ -18,6 +18,7 @@ from app.core.config import ( DEFAULT_PROJECTS, SKIP_UNHEALTHY ) +from app.core.logger import docker_logger # Инициализация Docker клиента docker_client = docker.from_env() @@ -33,13 +34,13 @@ def load_excluded_containers() -> List[str]: data = json.load(f) return data.get("excluded_containers", []) except FileNotFoundError: - print("⚠️ Файл app/excluded_containers.json не найден, используем пустой список") + docker_logger.warning("Файл app/excluded_containers.json не найден, используем пустой список") return [] except json.JSONDecodeError as e: - print(f"❌ Ошибка парсинга app/excluded_containers.json: {e}") + docker_logger.error(f"Ошибка парсинга app/excluded_containers.json: {e}") return [] except Exception as e: - print(f"❌ Ошибка загрузки app/excluded_containers.json: {e}") + docker_logger.error(f"Ошибка загрузки app/excluded_containers.json: {e}") return [] def save_excluded_containers(containers: List[str]) -> bool: @@ -57,7 +58,7 @@ def save_excluded_containers(containers: List[str]) -> bool: json.dump(data, f, indent=2, ensure_ascii=False) return True except Exception as e: - print(f"❌ Ошибка сохранения app/excluded_containers.json: {e}") + docker_logger.error(f"Ошибка сохранения app/excluded_containers.json: {e}") return False def get_all_projects() -> List[str]: @@ -105,11 +106,11 @@ def get_all_projects() -> List[str]: projects.add("standalone") except Exception as e: - print(f"❌ Ошибка получения списка проектов: {e}") + docker_logger.error(f"Ошибка получения списка проектов: {e}") return [] result = sorted(list(projects)) - print(f"📋 Доступные проекты (с учетом исключенных контейнеров): {result}") + docker_logger.info(f"Доступные проекты (с учетом исключенных контейнеров): {result}") return result def list_containers(projects: Optional[List[str]] = None, include_stopped: bool = False) -> List[Dict]: @@ -121,7 +122,7 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool # Загружаем список исключенных контейнеров из JSON файла excluded_containers = load_excluded_containers() - print(f"🚫 Список исключенных контейнеров: {excluded_containers}") + docker_logger.info(f"Список исключенных контейнеров: {excluded_containers}") items = [] excluded_count = 0 @@ -198,7 +199,7 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool # Фильтрация исключенных контейнеров if basic_info["name"] in excluded_containers: excluded_count += 1 - print(f"⚠️ Пропускаем исключенный контейнер: {basic_info['name']}") + docker_logger.warning(f"Пропускаем исключенный контейнер: {basic_info['name']}") continue # Добавляем контейнер в список @@ -206,11 +207,11 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool except Exception as e: # Пропускаем контейнеры с критическими ошибками - print(f"⚠️ Пропускаем проблемный контейнер {c.name if hasattr(c, 'name') else 'unknown'} (ID: {c.id[:12]}): {e}") + docker_logger.warning(f"Пропускаем проблемный контейнер {c.name if hasattr(c, 'name') else 'unknown'} (ID: {c.id[:12]}): {e}") continue except Exception as e: - print(f"❌ Ошибка получения списка контейнеров: {e}") + docker_logger.error(f"Ошибка получения списка контейнеров: {e}") return [] # Сортируем по проекту, сервису и имени @@ -236,8 +237,8 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool except Exception: continue - print(f"📊 Статистика: найдено {len(items)} контейнеров, исключено {excluded_count} контейнеров") + docker_logger.info(f"Статистика: найдено {len(items)} контейнеров, исключено {excluded_count} контейнеров") for project, stats in project_stats.items(): - print(f" 📦 {project}: {stats['visible']} видимых, {stats['excluded']} исключенных") + docker_logger.info(f" 📦 {project}: {stats['visible']} видимых, {stats['excluded']} исключенных") return items diff --git a/app/core/logger.py b/app/core/logger.py new file mode 100644 index 0000000..a540955 --- /dev/null +++ b/app/core/logger.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +LogBoard+ - Конфигурация логгера +Автор: Сергей Антропов +Сайт: https://devops.org.ru +""" + +import logging +import sys +from typing import Optional + +import os +from app.core.config import DEBUG_MODE + +def setup_logger(name: str = "logboard", level: Optional[str] = None) -> logging.Logger: + """ + Настраивает и возвращает логгер для приложения + + Args: + name: Имя логгера + level: Уровень логирования (DEBUG, INFO, WARNING, ERROR, CRITICAL) + + Returns: + Настроенный логгер + """ + # Определяем уровень логирования + if level is None: + level = os.getenv("LOG_LEVEL", "DEBUG" if DEBUG_MODE else "INFO") + + # Создаем логгер + logger = logging.getLogger(name) + logger.setLevel(getattr(logging, level.upper())) + + # Очищаем существующие обработчики + logger.handlers.clear() + + # Создаем форматтер + formatter = logging.Formatter( + fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + # Создаем обработчик для консоли + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(getattr(logging, level.upper())) + console_handler.setFormatter(formatter) + + # Добавляем обработчик к логгеру + logger.addHandler(console_handler) + + # Предотвращаем дублирование логов + logger.propagate = False + + return logger + +# Создаем основной логгер приложения +app_logger = setup_logger("logboard") + +# Создаем специализированные логгеры +auth_logger = setup_logger("logboard.auth") +docker_logger = setup_logger("logboard.docker") +api_logger = setup_logger("logboard.api") +websocket_logger = setup_logger("logboard.websocket") diff --git a/env.example b/env.example index 21e096d..6eac9fe 100644 --- a/env.example +++ b/env.example @@ -102,6 +102,9 @@ WEB_VERSION=1.0.0 # В продакшене обязательно установите в false DEBUG_MODE=false +# Уровень логирования (DEBUG, INFO, WARNING, ERROR, CRITICAL) +LOG_LEVEL=INFO + # ============================================================================= # НАСТРОЙКИ ПРОИЗВОДИТЕЛЬНОСТИ # =============================================================================