Implement log_manager module with LoggerPrint and LogManager
This commit is contained in:
@@ -13,13 +13,18 @@
|
|||||||
- Документация: `doc/src.utils.config_manager.md`, `src/utils/config_manager/AGENTS.md`
|
- Документация: `doc/src.utils.config_manager.md`, `src/utils/config_manager/AGENTS.md`
|
||||||
- [x] Создан `requirements.txt`
|
- [x] Создан `requirements.txt`
|
||||||
- [x] Опубликован на https://git.komisar.gin.by/komisar/komAI
|
- [x] Опубликован на https://git.komisar.gin.by/komisar/komAI
|
||||||
|
- [x] Реализован модуль `src/utils/log_manager` для логирования
|
||||||
|
- `LoggerPrint` с методом `print()` для консоли и файла
|
||||||
|
- `LogManager` с фабрикой обработчиков и ротацией
|
||||||
|
- Константы для конфига (`LOG_CATEGORY`, `LOG_CONSOLE`, etc.)
|
||||||
|
- 9/9 тестов проходят
|
||||||
|
- Документация: `src/utils/log_manager/AGENTS.md`
|
||||||
|
|
||||||
## In Progress
|
## In Progress
|
||||||
|
|
||||||
## Pending
|
## Pending
|
||||||
|
|
||||||
- [ ] Реализовать модуль логирования
|
- [ ] stdout/stderr перехват при аварийном завершении (on crash)
|
||||||
- [ ] stdout/stderr перехват при аварийном завершении (on crash)
|
|
||||||
- [ ] Создать приложение `app/komAI.py` как точку входа
|
- [ ] Создать приложение `app/komAI.py` как точку входа
|
||||||
- [ ] Реализовать систему модулей (`modules/`)
|
- [ ] Реализовать систему модулей (`modules/`)
|
||||||
- [ ] Настроить CI/CD
|
- [ ] Настроить CI/CD
|
||||||
|
|||||||
74
src/utils/log_manager/__init__.py
Normal file
74
src/utils/log_manager/__init__.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
from .manager import (
|
||||||
|
LogManager,
|
||||||
|
get_log_manager,
|
||||||
|
register,
|
||||||
|
register_global_params,
|
||||||
|
get_logger,
|
||||||
|
setup,
|
||||||
|
print,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
LOG_CATEGORY,
|
||||||
|
LOG_CONSOLE,
|
||||||
|
LOG_STDERR,
|
||||||
|
LOG_FILE,
|
||||||
|
LOG_PATH,
|
||||||
|
LOG_LEVEL,
|
||||||
|
LOG_FILE_LEVEL,
|
||||||
|
LOG_ROTATION,
|
||||||
|
LOG_ROTATION_SIZE,
|
||||||
|
LOG_ROTATION_COUNT,
|
||||||
|
LOG_TIMESTAMP,
|
||||||
|
LOG_BUFFERED,
|
||||||
|
MODULE_LOG_CONSOLE,
|
||||||
|
MODULE_LOG_STDERR,
|
||||||
|
MODULE_LOG_FILE,
|
||||||
|
MODULE_LOG_LEVEL,
|
||||||
|
DEFAULT_TIMESTAMP,
|
||||||
|
DEFAULT_LOG_LEVEL,
|
||||||
|
DEFAULT_FILE_LEVEL,
|
||||||
|
DEFAULT_LOG_PATH,
|
||||||
|
DEFAULT_LOG_FILE,
|
||||||
|
DEFAULT_ROTATION,
|
||||||
|
DEFAULT_ROTATION_SIZE,
|
||||||
|
DEFAULT_ROTATION_COUNT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .factory import FileHandlerFactory, parse_size
|
||||||
|
from .logger import LoggerPrint
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"LogManager",
|
||||||
|
"get_log_manager",
|
||||||
|
"register",
|
||||||
|
"register_global_params",
|
||||||
|
"get_logger",
|
||||||
|
"setup",
|
||||||
|
"print",
|
||||||
|
"LoggerPrint",
|
||||||
|
"LOG_CATEGORY",
|
||||||
|
"LOG_CONSOLE",
|
||||||
|
"LOG_STDERR",
|
||||||
|
"LOG_FILE",
|
||||||
|
"LOG_PATH",
|
||||||
|
"LOG_LEVEL",
|
||||||
|
"LOG_FILE_LEVEL",
|
||||||
|
"LOG_ROTATION",
|
||||||
|
"LOG_ROTATION_SIZE",
|
||||||
|
"LOG_ROTATION_COUNT",
|
||||||
|
"LOG_TIMESTAMP",
|
||||||
|
"LOG_BUFFERED",
|
||||||
|
"MODULE_LOG_CONSOLE",
|
||||||
|
"MODULE_LOG_STDERR",
|
||||||
|
"MODULE_LOG_FILE",
|
||||||
|
"MODULE_LOG_LEVEL",
|
||||||
|
"DEFAULT_TIMESTAMP",
|
||||||
|
"DEFAULT_LOG_LEVEL",
|
||||||
|
"DEFAULT_FILE_LEVEL",
|
||||||
|
"DEFAULT_LOG_PATH",
|
||||||
|
"DEFAULT_LOG_FILE",
|
||||||
|
"DEFAULT_ROTATION",
|
||||||
|
"DEFAULT_ROTATION_SIZE",
|
||||||
|
"DEFAULT_ROTATION_COUNT",
|
||||||
|
]
|
||||||
31
src/utils/log_manager/constants.py
Normal file
31
src/utils/log_manager/constants.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Category
|
||||||
|
LOG_CATEGORY = "logger"
|
||||||
|
|
||||||
|
# Global config keys
|
||||||
|
LOG_CONSOLE = "log_console"
|
||||||
|
LOG_STDERR = "log_stderr"
|
||||||
|
LOG_FILE = "log_file"
|
||||||
|
LOG_PATH = "log_path"
|
||||||
|
LOG_LEVEL = "log_level"
|
||||||
|
LOG_FILE_LEVEL = "log_file_level"
|
||||||
|
LOG_ROTATION = "log_rotation"
|
||||||
|
LOG_ROTATION_SIZE = "log_rotation_size"
|
||||||
|
LOG_ROTATION_COUNT = "log_rotation_count"
|
||||||
|
LOG_TIMESTAMP = "log_timestamp"
|
||||||
|
LOG_BUFFERED = "log_buffered"
|
||||||
|
|
||||||
|
# Module config keys
|
||||||
|
MODULE_LOG_CONSOLE = "log_console"
|
||||||
|
MODULE_LOG_STDERR = "log_stderr"
|
||||||
|
MODULE_LOG_FILE = "log_file"
|
||||||
|
MODULE_LOG_LEVEL = "log_level"
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
DEFAULT_TIMESTAMP = "%Y-%m-%d %H:%M:%S"
|
||||||
|
DEFAULT_LOG_LEVEL = "INFO"
|
||||||
|
DEFAULT_FILE_LEVEL = "DEBUG"
|
||||||
|
DEFAULT_LOG_PATH = "./log"
|
||||||
|
DEFAULT_LOG_FILE = "app.log"
|
||||||
|
DEFAULT_ROTATION = "size"
|
||||||
|
DEFAULT_ROTATION_SIZE = "10MB"
|
||||||
|
DEFAULT_ROTATION_COUNT = 5
|
||||||
37
src/utils/log_manager/factory.py
Normal file
37
src/utils/log_manager/factory.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
def parse_size(size_str: str) -> int:
|
||||||
|
units = [("GB", 1024 * 1024 * 1024), ("MB", 1024 * 1024), ("KB", 1024), ("B", 1)]
|
||||||
|
size_str = size_str.upper().strip()
|
||||||
|
for unit, multiplier in units:
|
||||||
|
if size_str.endswith(unit):
|
||||||
|
return int(size_str[: -len(unit)]) * multiplier
|
||||||
|
return int(size_str)
|
||||||
|
|
||||||
|
|
||||||
|
class FileHandlerFactory:
|
||||||
|
@staticmethod
|
||||||
|
def create(
|
||||||
|
path: Union[str, Path],
|
||||||
|
filename: str,
|
||||||
|
rotation: str = "size",
|
||||||
|
max_bytes: str = "10MB",
|
||||||
|
backup_count: int = 5,
|
||||||
|
) -> logging.FileHandler:
|
||||||
|
base_path = Path(path)
|
||||||
|
base_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
filepath = base_path / filename
|
||||||
|
|
||||||
|
if rotation == "size":
|
||||||
|
return logging.handlers.RotatingFileHandler(
|
||||||
|
filepath,
|
||||||
|
maxBytes=parse_size(max_bytes),
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return logging.FileHandler(filepath, encoding="utf-8")
|
||||||
63
src/utils/log_manager/logger.py
Normal file
63
src/utils/log_manager/logger.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerPrint(logging.Logger):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
module_name: str,
|
||||||
|
console_enabled: bool = True,
|
||||||
|
stderr_enabled: bool = True,
|
||||||
|
file_enabled: bool = False,
|
||||||
|
file_handler: Optional[logging.FileHandler] = None,
|
||||||
|
timestamp_format: str = "%Y-%m-%d %H:%M:%S",
|
||||||
|
):
|
||||||
|
super().__init__(name)
|
||||||
|
self.module_name = module_name
|
||||||
|
self.console_enabled = console_enabled
|
||||||
|
self.stderr_enabled = stderr_enabled
|
||||||
|
self.file_enabled = file_enabled
|
||||||
|
self.file_handler = file_handler
|
||||||
|
self.timestamp_format = timestamp_format
|
||||||
|
|
||||||
|
if file_handler:
|
||||||
|
file_handler.setFormatter(
|
||||||
|
logging.Formatter(
|
||||||
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
|
datefmt=timestamp_format,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def print(self, msg: str, level: str = "info"):
|
||||||
|
effective_level = getattr(logging, level.upper(), logging.INFO)
|
||||||
|
log_record = logging.LogRecord(
|
||||||
|
name=self.name,
|
||||||
|
level=effective_level,
|
||||||
|
pathname="",
|
||||||
|
lineno=0,
|
||||||
|
msg=msg,
|
||||||
|
args=(),
|
||||||
|
exc_info=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.console_enabled and effective_level <= logging.INFO:
|
||||||
|
formatted = self._format(log_record)
|
||||||
|
sys.stdout.write(formatted + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
if self.stderr_enabled and effective_level >= logging.WARNING:
|
||||||
|
formatted = self._format(log_record)
|
||||||
|
sys.stderr.write(formatted + "\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
if self.file_enabled and self.file_handler:
|
||||||
|
self.file_handler.emit(log_record)
|
||||||
|
|
||||||
|
def _format(self, record: logging.LogRecord) -> str:
|
||||||
|
timestamp = time.strftime(self.timestamp_format, time.localtime(record.created))
|
||||||
|
return (
|
||||||
|
f"{timestamp} - {record.name} - {record.levelname} - {record.getMessage()}"
|
||||||
|
)
|
||||||
217
src/utils/log_manager/manager.py
Normal file
217
src/utils/log_manager/manager.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import logging
|
||||||
|
import src.utils.config_manager as config_manager
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Optional, Any
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
LOG_CATEGORY,
|
||||||
|
LOG_CONSOLE,
|
||||||
|
LOG_STDERR,
|
||||||
|
LOG_FILE,
|
||||||
|
LOG_PATH,
|
||||||
|
LOG_LEVEL,
|
||||||
|
LOG_FILE_LEVEL,
|
||||||
|
LOG_ROTATION,
|
||||||
|
LOG_ROTATION_SIZE,
|
||||||
|
LOG_ROTATION_COUNT,
|
||||||
|
LOG_TIMESTAMP,
|
||||||
|
LOG_BUFFERED,
|
||||||
|
MODULE_LOG_CONSOLE,
|
||||||
|
MODULE_LOG_STDERR,
|
||||||
|
MODULE_LOG_FILE,
|
||||||
|
MODULE_LOG_LEVEL,
|
||||||
|
DEFAULT_TIMESTAMP,
|
||||||
|
DEFAULT_LOG_LEVEL,
|
||||||
|
DEFAULT_FILE_LEVEL,
|
||||||
|
DEFAULT_LOG_PATH,
|
||||||
|
DEFAULT_LOG_FILE,
|
||||||
|
DEFAULT_ROTATION,
|
||||||
|
DEFAULT_ROTATION_SIZE,
|
||||||
|
DEFAULT_ROTATION_COUNT,
|
||||||
|
)
|
||||||
|
from .factory import FileHandlerFactory
|
||||||
|
from .logger import LoggerPrint
|
||||||
|
|
||||||
|
|
||||||
|
class LogManager:
|
||||||
|
def __init__(self, config=None):
|
||||||
|
self.config = config or config_manager.config
|
||||||
|
self._loggers: Dict[str, LoggerPrint] = {}
|
||||||
|
self._root_logger: Optional[LoggerPrint] = None
|
||||||
|
self._file_handlers: Dict[str, logging.FileHandler] = {}
|
||||||
|
self._setup_done = False
|
||||||
|
|
||||||
|
def register_global_params(self):
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_CONSOLE, val=True, cat=LOG_CATEGORY, desc="Enable console output"
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_STDERR, val=True, cat=LOG_CATEGORY, desc="Enable stderr for errors"
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_FILE, val=DEFAULT_LOG_FILE, cat=LOG_CATEGORY, desc="Log file name"
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_PATH,
|
||||||
|
val=DEFAULT_LOG_PATH,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="Log directory path",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_LEVEL,
|
||||||
|
val=DEFAULT_LOG_LEVEL,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="Console log level",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_FILE_LEVEL,
|
||||||
|
val=DEFAULT_FILE_LEVEL,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="File log level",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_ROTATION,
|
||||||
|
val=DEFAULT_ROTATION,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="Rotation type: size|external",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_ROTATION_SIZE,
|
||||||
|
val=DEFAULT_ROTATION_SIZE,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="Max file size for rotation",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_ROTATION_COUNT,
|
||||||
|
val=DEFAULT_ROTATION_COUNT,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="Number of rotated files to keep",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_TIMESTAMP,
|
||||||
|
val=DEFAULT_TIMESTAMP,
|
||||||
|
cat=LOG_CATEGORY,
|
||||||
|
desc="Log timestamp format",
|
||||||
|
)
|
||||||
|
self.config.register(
|
||||||
|
name=LOG_BUFFERED, val=False, cat=LOG_CATEGORY, desc="Buffer file writes"
|
||||||
|
)
|
||||||
|
|
||||||
|
def register(
|
||||||
|
self,
|
||||||
|
module: str,
|
||||||
|
log_console: bool = None,
|
||||||
|
log_stderr: bool = None,
|
||||||
|
log_file: str = None,
|
||||||
|
log_level: str = None,
|
||||||
|
):
|
||||||
|
cfg = self._get_module_config(module)
|
||||||
|
|
||||||
|
console_enabled = (
|
||||||
|
log_console if log_console is not None else cfg.get(LOG_CONSOLE)
|
||||||
|
)
|
||||||
|
stderr_enabled = log_stderr if log_stderr is not None else cfg.get(LOG_STDERR)
|
||||||
|
file_enabled = log_file if log_file is not None else cfg.get(LOG_FILE)
|
||||||
|
level = log_level if log_level is not None else cfg.get(LOG_LEVEL)
|
||||||
|
|
||||||
|
name = f"komAI.{module}"
|
||||||
|
logger = LoggerPrint(
|
||||||
|
name=name,
|
||||||
|
module_name=module,
|
||||||
|
console_enabled=console_enabled,
|
||||||
|
stderr_enabled=stderr_enabled,
|
||||||
|
file_enabled=bool(file_enabled),
|
||||||
|
timestamp_format=self.config.get(LOG_TIMESTAMP, cat=LOG_CATEGORY)
|
||||||
|
or DEFAULT_TIMESTAMP,
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_enabled:
|
||||||
|
file_handler = self._create_file_handler(module, file_enabled)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.file_enabled = True
|
||||||
|
logger.file_handler = file_handler
|
||||||
|
|
||||||
|
self._loggers[module] = logger
|
||||||
|
|
||||||
|
def _get_module_config(self, module: str) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
LOG_CONSOLE: self.config.get(MODULE_LOG_CONSOLE, cat=module),
|
||||||
|
LOG_STDERR: self.config.get(MODULE_LOG_STDERR, cat=module),
|
||||||
|
LOG_FILE: self.config.get(MODULE_LOG_FILE, cat=module),
|
||||||
|
LOG_LEVEL: self.config.get(MODULE_LOG_LEVEL, cat=module),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_global_config(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
LOG_CONSOLE: self.config.get(LOG_CONSOLE, cat=LOG_CATEGORY),
|
||||||
|
LOG_STDERR: self.config.get(LOG_STDERR, cat=LOG_CATEGORY),
|
||||||
|
LOG_FILE: self.config.get(LOG_FILE, cat=LOG_CATEGORY),
|
||||||
|
LOG_LEVEL: self.config.get(LOG_LEVEL, cat=LOG_CATEGORY),
|
||||||
|
LOG_PATH: self.config.get(LOG_PATH, cat=LOG_CATEGORY) or DEFAULT_LOG_PATH,
|
||||||
|
LOG_FILE_LEVEL: self.config.get(LOG_FILE_LEVEL, cat=LOG_CATEGORY)
|
||||||
|
or DEFAULT_FILE_LEVEL,
|
||||||
|
LOG_ROTATION: self.config.get(LOG_ROTATION, cat=LOG_CATEGORY)
|
||||||
|
or DEFAULT_ROTATION,
|
||||||
|
LOG_ROTATION_SIZE: self.config.get(LOG_ROTATION_SIZE, cat=LOG_CATEGORY)
|
||||||
|
or DEFAULT_ROTATION_SIZE,
|
||||||
|
LOG_ROTATION_COUNT: self.config.get(LOG_ROTATION_COUNT, cat=LOG_CATEGORY)
|
||||||
|
or DEFAULT_ROTATION_COUNT,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_file_handler(self, module: str, filename: str) -> logging.FileHandler:
|
||||||
|
global_cfg = self._get_global_config()
|
||||||
|
return FileHandlerFactory.create(
|
||||||
|
path=global_cfg[LOG_PATH],
|
||||||
|
filename=filename,
|
||||||
|
rotation=global_cfg[LOG_ROTATION],
|
||||||
|
max_bytes=global_cfg[LOG_ROTATION_SIZE],
|
||||||
|
backup_count=global_cfg[LOG_ROTATION_COUNT],
|
||||||
|
)
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self._setup_done = True
|
||||||
|
|
||||||
|
def get_logger(self, module: str) -> Optional[LoggerPrint]:
|
||||||
|
return self._loggers.get(module)
|
||||||
|
|
||||||
|
def print(self, msg: str, level: str = "info"):
|
||||||
|
if self._root_logger:
|
||||||
|
self._root_logger.print(msg, level)
|
||||||
|
else:
|
||||||
|
__builtins__["print"](msg)
|
||||||
|
|
||||||
|
|
||||||
|
_log_manager: Optional[LogManager] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_log_manager() -> LogManager:
|
||||||
|
global _log_manager
|
||||||
|
if _log_manager is None:
|
||||||
|
_log_manager = LogManager()
|
||||||
|
return _log_manager
|
||||||
|
|
||||||
|
|
||||||
|
def register_global_params():
|
||||||
|
get_log_manager().register_global_params()
|
||||||
|
|
||||||
|
|
||||||
|
def register(
|
||||||
|
module: str,
|
||||||
|
log_console: bool = None,
|
||||||
|
log_stderr: bool = None,
|
||||||
|
log_file: str = None,
|
||||||
|
log_level: str = None,
|
||||||
|
):
|
||||||
|
get_log_manager().register(module, log_console, log_stderr, log_file, log_level)
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(module: str) -> Optional[LoggerPrint]:
|
||||||
|
return get_log_manager().get_logger(module)
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
get_log_manager().setup()
|
||||||
|
|
||||||
|
|
||||||
|
def print(msg: str, level: str = "info"):
|
||||||
|
get_log_manager().print(msg, level)
|
||||||
156
tests/test_log_manager.py
Normal file
156
tests/test_log_manager.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.utils.log_manager import (
|
||||||
|
parse_size,
|
||||||
|
FileHandlerFactory,
|
||||||
|
LoggerPrint,
|
||||||
|
LogManager,
|
||||||
|
register,
|
||||||
|
register_global_params,
|
||||||
|
get_logger,
|
||||||
|
setup,
|
||||||
|
print as log_print,
|
||||||
|
get_log_manager,
|
||||||
|
LOG_CATEGORY,
|
||||||
|
LOG_CONSOLE,
|
||||||
|
LOG_STDERR,
|
||||||
|
LOG_FILE,
|
||||||
|
LOG_PATH,
|
||||||
|
LOG_LEVEL,
|
||||||
|
LOG_ROTATION,
|
||||||
|
LOG_ROTATION_SIZE,
|
||||||
|
LOG_ROTATION_COUNT,
|
||||||
|
LOG_TIMESTAMP,
|
||||||
|
DEFAULT_TIMESTAMP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_size():
|
||||||
|
assert parse_size("100B") == 100
|
||||||
|
assert parse_size("1KB") == 1024
|
||||||
|
assert parse_size("1MB") == 1024 * 1024
|
||||||
|
assert parse_size("1GB") == 1024 * 1024 * 1024
|
||||||
|
assert parse_size("10MB") == 10 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
def test_constants():
|
||||||
|
assert LOG_CATEGORY == "logger"
|
||||||
|
assert LOG_CONSOLE == "log_console"
|
||||||
|
assert LOG_STDERR == "log_stderr"
|
||||||
|
assert LOG_FILE == "log_file"
|
||||||
|
assert DEFAULT_TIMESTAMP == "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_handler_factory():
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
tmpdir_path = Path(tmpdir)
|
||||||
|
tmpdir_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
handler = FileHandlerFactory.create(
|
||||||
|
tmpdir, "test.log", rotation="size", max_bytes="1KB", backup_count=3
|
||||||
|
)
|
||||||
|
assert handler is not None
|
||||||
|
assert isinstance(handler.backupCount, int)
|
||||||
|
assert handler.backupCount == 3
|
||||||
|
handler.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_logger_print():
|
||||||
|
logger = LoggerPrint(
|
||||||
|
name="komAI.test", module_name="test", console_enabled=True, stderr_enabled=True
|
||||||
|
)
|
||||||
|
logger.print("Test message", level="info")
|
||||||
|
logger.print("Warning message", level="warning")
|
||||||
|
logger.print("Error message", level="error")
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_manager_registration():
|
||||||
|
import src.utils.config_manager as config
|
||||||
|
|
||||||
|
config.config.reset()
|
||||||
|
register_global_params()
|
||||||
|
|
||||||
|
register(module="test_mod", log_console=True, log_file="test_mod.log")
|
||||||
|
|
||||||
|
logger = get_logger("test_mod")
|
||||||
|
assert logger is not None
|
||||||
|
assert logger.module_name == "test_mod"
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_manager_no_console():
|
||||||
|
import src.utils.config_manager as config
|
||||||
|
|
||||||
|
config.config.reset()
|
||||||
|
register_global_params()
|
||||||
|
|
||||||
|
register(module="test_mod", log_console=False)
|
||||||
|
|
||||||
|
logger = get_logger("test_mod")
|
||||||
|
assert logger.console_enabled is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_manager_with_file():
|
||||||
|
import src.utils.config_manager as config
|
||||||
|
|
||||||
|
config.config.reset()
|
||||||
|
register_global_params()
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
register(module="test_mod", log_file="test_mod.log")
|
||||||
|
config.config.set(LOG_PATH, tmpdir, cat=LOG_CATEGORY)
|
||||||
|
|
||||||
|
logger = get_logger("test_mod")
|
||||||
|
assert logger.file_enabled is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_manager_print():
|
||||||
|
import src.utils.config_manager as config
|
||||||
|
|
||||||
|
config.config.reset()
|
||||||
|
register_global_params()
|
||||||
|
|
||||||
|
register(module="test_mod", log_console=True)
|
||||||
|
|
||||||
|
setup()
|
||||||
|
log_print("Global print test", level="info")
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_log_manager_singleton():
|
||||||
|
mgr1 = get_log_manager()
|
||||||
|
mgr2 = get_log_manager()
|
||||||
|
assert mgr1 is mgr2
|
||||||
|
|
||||||
|
|
||||||
|
def run_tests():
|
||||||
|
tests = [
|
||||||
|
test_parse_size,
|
||||||
|
test_constants,
|
||||||
|
test_file_handler_factory,
|
||||||
|
test_logger_print,
|
||||||
|
test_log_manager_registration,
|
||||||
|
test_log_manager_no_console,
|
||||||
|
test_log_manager_with_file,
|
||||||
|
test_log_manager_print,
|
||||||
|
test_get_log_manager_singleton,
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for test in tests:
|
||||||
|
try:
|
||||||
|
test()
|
||||||
|
print(f"PASS: {test.__name__}")
|
||||||
|
passed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"FAIL: {test.__name__} - {e}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
print(f"\n{passed}/{passed + failed} tests passed")
|
||||||
|
return failed == 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = run_tests()
|
||||||
|
exit(0 if success else 1)
|
||||||
Reference in New Issue
Block a user