mirror of
https://github.com/KohakuBlueleaf/KohakuHub.git
synced 2026-04-30 17:37:51 -05:00
Merge pull request #1 from ntrwansuiBC/main
Logger improvements with loguru
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ config.toml
|
|||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
kohakuhub.conf
|
kohakuhub.conf
|
||||||
lint-reports/
|
lint-reports/
|
||||||
|
/logs
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -73,3 +73,7 @@ download_keep_sessions_days = 30 # Keep sessions from last N days
|
|||||||
debug_log_payloads = false # Log commit payloads (development only)
|
debug_log_payloads = false # Log commit payloads (development only)
|
||||||
# Site identification
|
# Site identification
|
||||||
site_name = "KohakuHub" # Customizable site name (e.g., "MyCompany Hub")
|
site_name = "KohakuHub" # Customizable site name (e.g., "MyCompany Hub")
|
||||||
|
# Log Settings
|
||||||
|
log_level = "INFO" # Low level logs will be filted (avaiable options: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" )
|
||||||
|
log_format = "terminal" # Output logs to "file" or "terminal" (maybe sql in future)
|
||||||
|
log_dir = "logs/" # Path to log file (if log_format is "file")
|
||||||
@@ -24,6 +24,7 @@ dependencies = [
|
|||||||
"fastapi",
|
"fastapi",
|
||||||
"fsspec",
|
"fsspec",
|
||||||
"httpx",
|
"httpx",
|
||||||
|
"loguru",
|
||||||
"numpy",
|
"numpy",
|
||||||
"peewee",
|
"peewee",
|
||||||
"psycopg2-binary",
|
"psycopg2-binary",
|
||||||
|
|||||||
@@ -360,6 +360,9 @@ def generate_hub_api_service(config: dict) -> str:
|
|||||||
- KOHAKU_HUB_LFS_KEEP_VERSIONS=5
|
- KOHAKU_HUB_LFS_KEEP_VERSIONS=5
|
||||||
- KOHAKU_HUB_LFS_AUTO_GC=true
|
- KOHAKU_HUB_LFS_AUTO_GC=true
|
||||||
- KOHAKU_HUB_AUTO_MIGRATE=true # Auto-confirm database migrations (required for Docker)
|
- KOHAKU_HUB_AUTO_MIGRATE=true # Auto-confirm database migrations (required for Docker)
|
||||||
|
- KOHAKU_HUB_LOG_LEVEL=INFO
|
||||||
|
- KOHAKU_HUB_LOG_FORMAT=terminal
|
||||||
|
- KOHAKU_HUB_LOG_DIR=logs/
|
||||||
|
|
||||||
## ===== Auth & SMTP Configuration =====
|
## ===== Auth & SMTP Configuration =====
|
||||||
- KOHAKU_HUB_REQUIRE_EMAIL_VERIFICATION=false
|
- KOHAKU_HUB_REQUIRE_EMAIL_VERIFICATION=false
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ class AppConfig(BaseModel):
|
|||||||
]
|
]
|
||||||
# Site identification
|
# Site identification
|
||||||
site_name: str = "KohakuHub" # Configurable site name (e.g., "MyCompany Hub")
|
site_name: str = "KohakuHub" # Configurable site name (e.g., "MyCompany Hub")
|
||||||
|
# Log settings
|
||||||
|
log_level: str = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||||
|
log_format: str = "file" # Output logs to "file" or "terminal" (maybe sql in future)
|
||||||
|
log_dir: str = "logs/" # Path to log file (if log_format is "file")
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
@@ -423,6 +427,12 @@ def load_config(path: str = None) -> Config:
|
|||||||
app_env["debug_log_payloads"] = (
|
app_env["debug_log_payloads"] = (
|
||||||
os.environ["KOHAKU_HUB_DEBUG_LOG_PAYLOADS"].lower() == "true"
|
os.environ["KOHAKU_HUB_DEBUG_LOG_PAYLOADS"].lower() == "true"
|
||||||
)
|
)
|
||||||
|
if "KOHAKU_HUB_LOG_LEVEL" in os.environ:
|
||||||
|
app_env["log_level"] = os.environ["KOHAKU_HUB_LOG_LEVEL"]
|
||||||
|
if "KOHAKU_HUB_LOG_FORMAT" in os.environ:
|
||||||
|
app_env["log_format"] = os.environ["KOHAKU_HUB_LOG_FORMAT"]
|
||||||
|
if "KOHAKU_HUB_LOG_DIR" in os.environ:
|
||||||
|
app_env["log_dir"] = os.environ["KOHAKU_HUB_LOG_DIR"]
|
||||||
if app_env:
|
if app_env:
|
||||||
config_from_env["app"] = app_env
|
config_from_env["app"] = app_env
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +1,56 @@
|
|||||||
"""Custom logging module for KohakuHub with colored output and formatted tracebacks."""
|
"""Loguru-based logging implementation for KohakuHub."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
import traceback as tb
|
import traceback as tb
|
||||||
from datetime import datetime
|
from loguru import logger
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from kohakuhub.config import cfg
|
||||||
class Color:
|
|
||||||
"""ANSI color codes for terminal output."""
|
|
||||||
|
|
||||||
# Text colors
|
|
||||||
RESET = "\033[0m"
|
|
||||||
BLACK = "\033[30m"
|
|
||||||
RED = "\033[31m"
|
|
||||||
GREEN = "\033[32m"
|
|
||||||
YELLOW = "\033[33m"
|
|
||||||
BLUE = "\033[34m"
|
|
||||||
MAGENTA = "\033[35m"
|
|
||||||
CYAN = "\033[36m"
|
|
||||||
WHITE = "\033[37m"
|
|
||||||
|
|
||||||
# Bright colors
|
|
||||||
BRIGHT_BLACK = "\033[90m"
|
|
||||||
BRIGHT_RED = "\033[91m"
|
|
||||||
BRIGHT_GREEN = "\033[92m"
|
|
||||||
BRIGHT_YELLOW = "\033[93m"
|
|
||||||
BRIGHT_BLUE = "\033[94m"
|
|
||||||
BRIGHT_MAGENTA = "\033[95m"
|
|
||||||
BRIGHT_CYAN = "\033[96m"
|
|
||||||
BRIGHT_WHITE = "\033[97m"
|
|
||||||
|
|
||||||
# Background colors
|
|
||||||
BG_BLACK = "\033[40m"
|
|
||||||
BG_RED = "\033[41m"
|
|
||||||
BG_GREEN = "\033[42m"
|
|
||||||
BG_YELLOW = "\033[43m"
|
|
||||||
BG_BLUE = "\033[44m"
|
|
||||||
|
|
||||||
# Text styles
|
|
||||||
BOLD = "\033[1m"
|
|
||||||
DIM = "\033[2m"
|
|
||||||
UNDERLINE = "\033[4m"
|
|
||||||
|
|
||||||
|
|
||||||
class LogLevel(Enum):
|
class LogLevel(Enum):
|
||||||
"""Log levels with associated colors."""
|
"""Log levels mapping to loguru levels."""
|
||||||
|
DEBUG = "DEBUG"
|
||||||
|
INFO = "INFO"
|
||||||
|
SUCCESS = "SUCCESS"
|
||||||
|
WARNING = "WARNING"
|
||||||
|
ERROR = "ERROR"
|
||||||
|
CRITICAL = "CRITICAL"
|
||||||
|
TRACE = "TRACE"
|
||||||
|
|
||||||
DEBUG = ("DEBUG", Color.BRIGHT_BLACK)
|
|
||||||
INFO = ("INFO", Color.BRIGHT_CYAN)
|
class InterceptHandler(logging.Handler):
|
||||||
SUCCESS = ("SUCCESS", Color.BRIGHT_GREEN)
|
"""
|
||||||
WARNING = ("WARNING", Color.BRIGHT_YELLOW)
|
Logger Interceptor:Redirects standard library logs to Loguru.
|
||||||
ERROR = ("ERROR", Color.BRIGHT_RED)
|
"""
|
||||||
CRITICAL = ("CRITICAL", Color.BG_RED + Color.WHITE + Color.BOLD)
|
|
||||||
|
def emit(self, record: logging.LogRecord) -> None:
|
||||||
|
# Get Level Name and API name from LogRecord
|
||||||
|
try:
|
||||||
|
level = logger.level(record.levelname).name
|
||||||
|
api_name = record.name.upper()
|
||||||
|
if "UVICORN" in api_name:
|
||||||
|
api_name = "UVICORN"
|
||||||
|
except ValueError:
|
||||||
|
level = record.levelno
|
||||||
|
|
||||||
|
frame, depth = logging.currentframe(), 2
|
||||||
|
while frame.f_code.co_filename == logging.__file__:
|
||||||
|
frame = frame.f_back
|
||||||
|
depth += 1
|
||||||
|
|
||||||
|
logger.bind(api_name=api_name).opt(depth=depth, exception=record.exc_info).log(
|
||||||
|
level,
|
||||||
|
record.getMessage()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
"""Custom logger with API name prefix and colored output."""
|
"""Loguru-based logger"""
|
||||||
|
|
||||||
def __init__(self, api_name: str = "APP"):
|
def __init__(self, api_name: str = "APP"):
|
||||||
"""Initialize logger with API name.
|
"""Initialize logger with API name.
|
||||||
@@ -66,74 +59,49 @@ class Logger:
|
|||||||
api_name: Name of the API/module (e.g., "AUTH", "FILE", "LFS")
|
api_name: Name of the API/module (e.g., "AUTH", "FILE", "LFS")
|
||||||
"""
|
"""
|
||||||
self.api_name = api_name.upper()
|
self.api_name = api_name.upper()
|
||||||
|
self._logger = logger.bind(api_name=api_name)
|
||||||
def _get_timestamp(self) -> str:
|
|
||||||
"""Get current timestamp in HH:MM:SS format."""
|
|
||||||
return datetime.now().strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
def _format_message(self, level: LogLevel, message: str) -> str:
|
|
||||||
"""Format log message with colors and structure.
|
|
||||||
|
|
||||||
Format: [LEVEL][API-NAME][Worker:PID][HH:MM:SS] message
|
|
||||||
|
|
||||||
Args:
|
|
||||||
level: Log level
|
|
||||||
message: Message to log
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Formatted colored string
|
|
||||||
"""
|
|
||||||
level_name, level_color = level.value
|
|
||||||
timestamp = self._get_timestamp()
|
|
||||||
worker_id = os.getpid() # Process ID identifies the worker
|
|
||||||
|
|
||||||
# Build formatted message
|
|
||||||
parts = [
|
|
||||||
f"{level_color}[{level_name}]{Color.RESET}",
|
|
||||||
f"{Color.BRIGHT_MAGENTA}[{self.api_name}]{Color.RESET}",
|
|
||||||
f"{Color.BLUE}[W:{worker_id}]{Color.RESET}",
|
|
||||||
f"{Color.BRIGHT_BLACK}[{timestamp}]{Color.RESET}",
|
|
||||||
message,
|
|
||||||
]
|
|
||||||
|
|
||||||
return " ".join(parts)
|
|
||||||
|
|
||||||
def _log(self, level: LogLevel, message: str):
|
def _log(self, level: LogLevel, message: str):
|
||||||
"""Internal log method."""
|
"""Internal log method."""
|
||||||
formatted = self._format_message(level, message)
|
match level:
|
||||||
print(
|
case LogLevel.DEBUG:
|
||||||
formatted,
|
self._logger.debug(message)
|
||||||
file=(
|
case LogLevel.INFO:
|
||||||
sys.stderr
|
self._logger.info(message)
|
||||||
if level in [LogLevel.ERROR, LogLevel.CRITICAL]
|
case LogLevel.SUCCESS:
|
||||||
else sys.stdout
|
self._logger.success(message)
|
||||||
),
|
case LogLevel.WARNING:
|
||||||
)
|
self._logger.warning(message)
|
||||||
|
case LogLevel.ERROR:
|
||||||
|
self._logger.error(message)
|
||||||
|
case LogLevel.CRITICAL:
|
||||||
|
self._logger.critical(message)
|
||||||
|
case LogLevel.TRACE:
|
||||||
|
self._logger.trace(message)
|
||||||
|
case _:
|
||||||
|
self._logger.log(level, message)
|
||||||
|
|
||||||
def debug(self, message: str):
|
def debug(self, message: str):
|
||||||
"""Log debug message."""
|
|
||||||
self._log(LogLevel.DEBUG, message)
|
self._log(LogLevel.DEBUG, message)
|
||||||
|
|
||||||
def info(self, message: str):
|
def info(self, message: str):
|
||||||
"""Log info message."""
|
|
||||||
self._log(LogLevel.INFO, message)
|
self._log(LogLevel.INFO, message)
|
||||||
|
|
||||||
def success(self, message: str):
|
def success(self, message: str):
|
||||||
"""Log success message."""
|
|
||||||
self._log(LogLevel.SUCCESS, message)
|
self._log(LogLevel.SUCCESS, message)
|
||||||
|
|
||||||
def warning(self, message: str):
|
def warning(self, message: str):
|
||||||
"""Log warning message."""
|
|
||||||
self._log(LogLevel.WARNING, message)
|
self._log(LogLevel.WARNING, message)
|
||||||
|
|
||||||
def error(self, message: str):
|
def error(self, message: str):
|
||||||
"""Log error message."""
|
|
||||||
self._log(LogLevel.ERROR, message)
|
self._log(LogLevel.ERROR, message)
|
||||||
|
|
||||||
def critical(self, message: str):
|
def critical(self, message: str):
|
||||||
"""Log critical error message."""
|
|
||||||
self._log(LogLevel.CRITICAL, message)
|
self._log(LogLevel.CRITICAL, message)
|
||||||
|
|
||||||
|
def trace(self, message: str):
|
||||||
|
self._log(LogLevel.TRACE, message)
|
||||||
|
|
||||||
def exception(self, message: str, exc: Optional[Exception] = None):
|
def exception(self, message: str, exc: Optional[Exception] = None):
|
||||||
"""Log exception with formatted traceback.
|
"""Log exception with formatted traceback.
|
||||||
|
|
||||||
@@ -168,9 +136,9 @@ class Logger:
|
|||||||
frames = tb.extract_tb(exc_tb)
|
frames = tb.extract_tb(exc_tb)
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
print(f"\n{Color.BRIGHT_RED}{'═' * 100}{Color.RESET}", file=sys.stderr)
|
self.trace(f"{'=' * 50}")
|
||||||
print(f"{Color.BRIGHT_RED}{Color.BOLD}TRACEBACK{Color.RESET}", file=sys.stderr)
|
self.trace("TRACEBACK")
|
||||||
print(f"{Color.BRIGHT_RED}{'═' * 100}{Color.RESET}\n", file=sys.stderr)
|
self.trace(f"{'=' * 50}")
|
||||||
|
|
||||||
# Print stack frames as tables
|
# Print stack frames as tables
|
||||||
for i, frame in enumerate(frames, 1):
|
for i, frame in enumerate(frames, 1):
|
||||||
@@ -179,7 +147,7 @@ class Logger:
|
|||||||
# Print final error table
|
# Print final error table
|
||||||
self._print_error_table(exc_type, exc_value, frames[-1] if frames else None)
|
self._print_error_table(exc_type, exc_value, frames[-1] if frames else None)
|
||||||
|
|
||||||
print(f"{Color.BRIGHT_RED}{'═' * 100}{Color.RESET}\n", file=sys.stderr)
|
self.trace(f"{'=' * 50}")
|
||||||
|
|
||||||
def _print_frame_table(self, index: int, frame: tb.FrameSummary, is_last: bool):
|
def _print_frame_table(self, index: int, frame: tb.FrameSummary, is_last: bool):
|
||||||
"""Print single stack frame as a table.
|
"""Print single stack frame as a table.
|
||||||
@@ -189,39 +157,27 @@ class Logger:
|
|||||||
frame: Frame summary
|
frame: Frame summary
|
||||||
is_last: Whether this is the last frame (error location)
|
is_last: Whether this is the last frame (error location)
|
||||||
"""
|
"""
|
||||||
color = Color.BRIGHT_RED if is_last else Color.BRIGHT_BLACK
|
# loguru only renders colors in format
|
||||||
|
# color = "ff0000" if is_last else "09D0EF"
|
||||||
|
|
||||||
# Table header
|
# Table header
|
||||||
print(
|
self.trace(f"┌─ Frame #{index} {' (ERROR HERE)' if is_last else ''}")
|
||||||
f"{color}┌─ Frame #{index} {' (ERROR HERE)' if is_last else ''}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
# File
|
# File
|
||||||
print(
|
self.trace(f"│ File: {frame.filename}")
|
||||||
f"{color}│ {Color.CYAN}File:{Color.RESET} {frame.filename}", file=sys.stderr
|
|
||||||
)
|
|
||||||
|
|
||||||
# Line number
|
# Line number
|
||||||
print(
|
self.trace(f"│ Line: {frame.lineno}")
|
||||||
f"{color}│ {Color.YELLOW}Line:{Color.RESET} {frame.lineno}", file=sys.stderr
|
|
||||||
)
|
|
||||||
|
|
||||||
# Function/Code
|
# Function/Code
|
||||||
if frame.name:
|
if frame.name:
|
||||||
print(
|
self.trace(f"│ In:{frame.name}()")
|
||||||
f"{color}│ {Color.GREEN}In:{Color.RESET} {frame.name}()",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
if frame.line:
|
if frame.line:
|
||||||
# Show the actual code line
|
# Show the actual code line
|
||||||
print(
|
self.trace(f"│ Code: {frame.line.strip()}")
|
||||||
f"{color}│ {Color.BRIGHT_WHITE}Code:{Color.RESET} {frame.line.strip()}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"{color}└{'─' * 99}{Color.RESET}\n", file=sys.stderr)
|
self.trace(f"└{'─' * 99}")
|
||||||
|
|
||||||
def _print_error_table(
|
def _print_error_table(
|
||||||
self, exc_type, exc_value, last_frame: Optional[tb.FrameSummary]
|
self, exc_type, exc_value, last_frame: Optional[tb.FrameSummary]
|
||||||
@@ -234,46 +190,83 @@ class Logger:
|
|||||||
last_frame: Last stack frame (error location)
|
last_frame: Last stack frame (error location)
|
||||||
"""
|
"""
|
||||||
# Table header
|
# Table header
|
||||||
print(
|
self.trace(" EXCEPTION DETAILS ")
|
||||||
f"{Color.BG_RED}{Color.WHITE}{Color.BOLD} EXCEPTION DETAILS {Color.RESET}",
|
|
||||||
file=sys.stderr,
|
self.trace(f"┌{'─' * 99}")
|
||||||
)
|
|
||||||
print(f"{Color.BRIGHT_RED}┌{'─' * 99}", file=sys.stderr)
|
|
||||||
|
|
||||||
# Error type
|
# Error type
|
||||||
print(
|
self.trace(f"│ Type: {exc_type.__name__}")
|
||||||
f"{Color.BRIGHT_RED}│ {Color.BOLD}Type:{Color.RESET} {Color.BRIGHT_RED}{exc_type.__name__}{Color.RESET}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Error message
|
# Error message
|
||||||
print(
|
self.trace(f"│ Message: {str(exc_value)}")
|
||||||
f"{Color.BRIGHT_RED}│ {Color.BOLD}Message:{Color.RESET} {Color.WHITE}{str(exc_value)}{Color.RESET}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_frame:
|
if last_frame:
|
||||||
# Error location
|
# Error location
|
||||||
print(
|
self.trace(f"│ Location: {last_frame.filename}:{last_frame.lineno}")
|
||||||
f"{Color.BRIGHT_RED}│ {Color.BOLD}Location:{Color.RESET} {Color.CYAN}{last_frame.filename}{Color.RESET}:{Color.YELLOW}{last_frame.lineno}{Color.RESET}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_frame.line:
|
if last_frame.line:
|
||||||
# Code that caused error
|
# Code that caused error
|
||||||
print(
|
self.trace(f"│ Code: {last_frame.line.strip()}")
|
||||||
f"{Color.BRIGHT_RED}│ {Color.BOLD}Code:{Color.RESET} {Color.BRIGHT_WHITE}{last_frame.line.strip()}{Color.RESET}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"{Color.BRIGHT_RED}└{'─' * 99}{Color.RESET}", file=sys.stderr)
|
self.trace(f"└{'─' * 99}")
|
||||||
|
|
||||||
|
|
||||||
class LoggerFactory:
|
class LoggerFactory:
|
||||||
"""Factory to create loggers with different API names."""
|
"""Factory to create loguru loggers."""
|
||||||
|
|
||||||
_loggers = {}
|
_loggers = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_logger_settings(cls):
|
||||||
|
|
||||||
|
log_dir = cfg.app.log_dir
|
||||||
|
if not os.path.exists(log_dir):
|
||||||
|
os.mkdir(log_dir)
|
||||||
|
log_path = os.path.join(log_dir, "kohakuhub.log")
|
||||||
|
log_level = cfg.app.log_level.upper()
|
||||||
|
log_format = cfg.app.log_format.lower()
|
||||||
|
# Configure loguru format to match existing style
|
||||||
|
|
||||||
|
# Remove default handler
|
||||||
|
logger.remove()
|
||||||
|
|
||||||
|
""" About Color codes for loguru.
|
||||||
|
Actually, it also support 8bit , Hex , RGB color codes.
|
||||||
|
See https://loguru.readthedocs.io/en/stable/api/logger.html#color
|
||||||
|
"""
|
||||||
|
logger.level("DEBUG", color="<fg #222024>")
|
||||||
|
logger.level("INFO", color="<fg #09D0EF>")
|
||||||
|
logger.level("SUCCESS", color="<fg #66FF00>")
|
||||||
|
logger.level("WARNING", color="<fg #FFEB2A>")
|
||||||
|
logger.level("ERROR", color="<fg #FF160C>")
|
||||||
|
logger.level("CRITICAL", color="<white><bg #FF160C><bold>")
|
||||||
|
logger.level("TRACE", color="<fg #FF160C>")
|
||||||
|
|
||||||
|
"""Add Defualt Terminal logger"""
|
||||||
|
logger.add(
|
||||||
|
sys.stderr,
|
||||||
|
format="<level>[{level}]</level><fg #FF00CD>[{extra[api_name]}]</fg #FF00CD><blue>[W:{process}]</blue>[{time:HH:mm:ss}] {message}",
|
||||||
|
level=log_level,
|
||||||
|
colorize=True
|
||||||
|
)
|
||||||
|
|
||||||
|
"""Add File logger"""
|
||||||
|
if log_format == "file":
|
||||||
|
logger.add(
|
||||||
|
log_path,
|
||||||
|
format="<level>[{level}]</level><fg #FF00CD>[{extra[api_name]}]</fg #FF00CD><blue>[W:{process}]</blue>[{time:HH:mm:ss}] {message}",
|
||||||
|
level=log_level,
|
||||||
|
rotation="2 MB"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger_name_list = [name for name in logging.root.manager.loggerDict]
|
||||||
|
for logger_name in logger_name_list:
|
||||||
|
_logger = logging.getLogger(logger_name)
|
||||||
|
_logger.setLevel(logging.INFO)
|
||||||
|
_logger.handlers = []
|
||||||
|
if '.' not in logger_name:
|
||||||
|
_logger.addHandler(InterceptHandler())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_logger(cls, api_name: str) -> Logger:
|
def get_logger(cls, api_name: str) -> Logger:
|
||||||
"""Get or create logger for API name.
|
"""Get or create logger for API name.
|
||||||
@@ -289,22 +282,16 @@ class LoggerFactory:
|
|||||||
return cls._loggers[api_name]
|
return cls._loggers[api_name]
|
||||||
|
|
||||||
|
|
||||||
# Convenience function for getting logger
|
def init_logger_settings():
|
||||||
|
"""Initialize logger settings."""
|
||||||
|
LoggerFactory.init_logger_settings()
|
||||||
|
|
||||||
|
|
||||||
def get_logger(api_name: str) -> Logger:
|
def get_logger(api_name: str) -> Logger:
|
||||||
"""Get logger for specific API.
|
"""Get logger for specific API.
|
||||||
|
|
||||||
Usage:
|
|
||||||
logger = get_logger("AUTH")
|
|
||||||
logger.info("User logged in")
|
|
||||||
logger.error("Login failed")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# ... code ...
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception("Failed to process request", e)
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
api_name: Name of the API/module (e.g., "AUTH", "FILE", "LFS")
|
api_name: Name of the API/module
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Logger instance
|
Logger instance
|
||||||
@@ -312,6 +299,7 @@ def get_logger(api_name: str) -> Logger:
|
|||||||
return LoggerFactory.get_logger(api_name)
|
return LoggerFactory.get_logger(api_name)
|
||||||
|
|
||||||
|
|
||||||
|
init_logger_settings()
|
||||||
# Pre-create common loggers
|
# Pre-create common loggers
|
||||||
logger_auth = get_logger("AUTH")
|
logger_auth = get_logger("AUTH")
|
||||||
logger_file = get_logger("FILE")
|
logger_file = get_logger("FILE")
|
||||||
|
|||||||
323
src/kohakuhub/old_logger.py
Normal file
323
src/kohakuhub/old_logger.py
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
"""Custom logging module for KohakuHub with colored output and formatted tracebacks."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback as tb
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class Color:
|
||||||
|
"""ANSI color codes for terminal output."""
|
||||||
|
|
||||||
|
# Text colors
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BLACK = "\033[30m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
MAGENTA = "\033[35m"
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
WHITE = "\033[37m"
|
||||||
|
|
||||||
|
# Bright colors
|
||||||
|
BRIGHT_BLACK = "\033[90m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_BLUE = "\033[94m"
|
||||||
|
BRIGHT_MAGENTA = "\033[95m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
|
||||||
|
# Background colors
|
||||||
|
BG_BLACK = "\033[40m"
|
||||||
|
BG_RED = "\033[41m"
|
||||||
|
BG_GREEN = "\033[42m"
|
||||||
|
BG_YELLOW = "\033[43m"
|
||||||
|
BG_BLUE = "\033[44m"
|
||||||
|
|
||||||
|
# Text styles
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
DIM = "\033[2m"
|
||||||
|
UNDERLINE = "\033[4m"
|
||||||
|
|
||||||
|
|
||||||
|
class LogLevel(Enum):
|
||||||
|
"""Log levels with associated colors."""
|
||||||
|
|
||||||
|
DEBUG = ("DEBUG", Color.BRIGHT_BLACK)
|
||||||
|
INFO = ("INFO", Color.BRIGHT_CYAN)
|
||||||
|
SUCCESS = ("SUCCESS", Color.BRIGHT_GREEN)
|
||||||
|
WARNING = ("WARNING", Color.BRIGHT_YELLOW)
|
||||||
|
ERROR = ("ERROR", Color.BRIGHT_RED)
|
||||||
|
CRITICAL = ("CRITICAL", Color.BG_RED + Color.WHITE + Color.BOLD)
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
"""Custom logger with API name prefix and colored output."""
|
||||||
|
|
||||||
|
def __init__(self, api_name: str = "APP"):
|
||||||
|
"""Initialize logger with API name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Name of the API/module (e.g., "AUTH", "FILE", "LFS")
|
||||||
|
"""
|
||||||
|
self.api_name = api_name.upper()
|
||||||
|
|
||||||
|
def _get_timestamp(self) -> str:
|
||||||
|
"""Get current timestamp in HH:MM:SS format."""
|
||||||
|
return datetime.now().strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
def _format_message(self, level: LogLevel, message: str) -> str:
|
||||||
|
"""Format log message with colors and structure.
|
||||||
|
|
||||||
|
Format: [LEVEL][API-NAME][Worker:PID][HH:MM:SS] message
|
||||||
|
|
||||||
|
Args:
|
||||||
|
level: Log level
|
||||||
|
message: Message to log
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted colored string
|
||||||
|
"""
|
||||||
|
level_name, level_color = level.value
|
||||||
|
timestamp = self._get_timestamp()
|
||||||
|
worker_id = os.getpid() # Process ID identifies the worker
|
||||||
|
|
||||||
|
# Build formatted message
|
||||||
|
parts = [
|
||||||
|
f"{level_color}[{level_name}]{Color.RESET}",
|
||||||
|
f"{Color.BRIGHT_MAGENTA}[{self.api_name}]{Color.RESET}",
|
||||||
|
f"{Color.BLUE}[W:{worker_id}]{Color.RESET}",
|
||||||
|
f"{Color.BRIGHT_BLACK}[{timestamp}]{Color.RESET}",
|
||||||
|
message,
|
||||||
|
]
|
||||||
|
|
||||||
|
return " ".join(parts)
|
||||||
|
|
||||||
|
def _log(self, level: LogLevel, message: str):
|
||||||
|
"""Internal log method."""
|
||||||
|
formatted = self._format_message(level, message)
|
||||||
|
print(
|
||||||
|
formatted,
|
||||||
|
file=(
|
||||||
|
sys.stderr
|
||||||
|
if level in [LogLevel.ERROR, LogLevel.CRITICAL]
|
||||||
|
else sys.stdout
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def debug(self, message: str):
|
||||||
|
"""Log debug message."""
|
||||||
|
self._log(LogLevel.DEBUG, message)
|
||||||
|
|
||||||
|
def info(self, message: str):
|
||||||
|
"""Log info message."""
|
||||||
|
self._log(LogLevel.INFO, message)
|
||||||
|
|
||||||
|
def success(self, message: str):
|
||||||
|
"""Log success message."""
|
||||||
|
self._log(LogLevel.SUCCESS, message)
|
||||||
|
|
||||||
|
def warning(self, message: str):
|
||||||
|
"""Log warning message."""
|
||||||
|
self._log(LogLevel.WARNING, message)
|
||||||
|
|
||||||
|
def error(self, message: str):
|
||||||
|
"""Log error message."""
|
||||||
|
self._log(LogLevel.ERROR, message)
|
||||||
|
|
||||||
|
def critical(self, message: str):
|
||||||
|
"""Log critical error message."""
|
||||||
|
self._log(LogLevel.CRITICAL, message)
|
||||||
|
|
||||||
|
def exception(self, message: str, exc: Optional[Exception] = None):
|
||||||
|
"""Log exception with formatted traceback.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Error message
|
||||||
|
exc: Exception object (if None, uses sys.exc_info())
|
||||||
|
"""
|
||||||
|
self.error(message)
|
||||||
|
self._print_formatted_traceback(exc)
|
||||||
|
|
||||||
|
def _print_formatted_traceback(self, exc: Optional[Exception] = None):
|
||||||
|
"""Print formatted traceback as tables.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
1. Stack trace table for each frame
|
||||||
|
2. Final error table with actual error details
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exc: Exception object (if None, uses sys.exc_info())
|
||||||
|
"""
|
||||||
|
if exc is None:
|
||||||
|
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||||
|
else:
|
||||||
|
exc_type = type(exc)
|
||||||
|
exc_value = exc
|
||||||
|
exc_tb = exc.__traceback__
|
||||||
|
|
||||||
|
if exc_tb is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extract traceback frames
|
||||||
|
frames = tb.extract_tb(exc_tb)
|
||||||
|
|
||||||
|
# Print header
|
||||||
|
print(f"\n{Color.BRIGHT_RED}{'═' * 100}{Color.RESET}", file=sys.stderr)
|
||||||
|
print(f"{Color.BRIGHT_RED}{Color.BOLD}TRACEBACK{Color.RESET}", file=sys.stderr)
|
||||||
|
print(f"{Color.BRIGHT_RED}{'═' * 100}{Color.RESET}\n", file=sys.stderr)
|
||||||
|
|
||||||
|
# Print stack frames as tables
|
||||||
|
for i, frame in enumerate(frames, 1):
|
||||||
|
self._print_frame_table(i, frame, is_last=(i == len(frames)))
|
||||||
|
|
||||||
|
# Print final error table
|
||||||
|
self._print_error_table(exc_type, exc_value, frames[-1] if frames else None)
|
||||||
|
|
||||||
|
print(f"{Color.BRIGHT_RED}{'═' * 100}{Color.RESET}\n", file=sys.stderr)
|
||||||
|
|
||||||
|
def _print_frame_table(self, index: int, frame: tb.FrameSummary, is_last: bool):
|
||||||
|
"""Print single stack frame as a table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
index: Frame index
|
||||||
|
frame: Frame summary
|
||||||
|
is_last: Whether this is the last frame (error location)
|
||||||
|
"""
|
||||||
|
color = Color.BRIGHT_RED if is_last else Color.BRIGHT_BLACK
|
||||||
|
|
||||||
|
# Table header
|
||||||
|
print(
|
||||||
|
f"{color}┌─ Frame #{index} {' (ERROR HERE)' if is_last else ''}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
# File
|
||||||
|
print(
|
||||||
|
f"{color}│ {Color.CYAN}File:{Color.RESET} {frame.filename}", file=sys.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
# Line number
|
||||||
|
print(
|
||||||
|
f"{color}│ {Color.YELLOW}Line:{Color.RESET} {frame.lineno}", file=sys.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
# Function/Code
|
||||||
|
if frame.name:
|
||||||
|
print(
|
||||||
|
f"{color}│ {Color.GREEN}In:{Color.RESET} {frame.name}()",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
if frame.line:
|
||||||
|
# Show the actual code line
|
||||||
|
print(
|
||||||
|
f"{color}│ {Color.BRIGHT_WHITE}Code:{Color.RESET} {frame.line.strip()}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"{color}└{'─' * 99}{Color.RESET}\n", file=sys.stderr)
|
||||||
|
|
||||||
|
def _print_error_table(
|
||||||
|
self, exc_type, exc_value, last_frame: Optional[tb.FrameSummary]
|
||||||
|
):
|
||||||
|
"""Print final error details as a table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exc_type: Exception type
|
||||||
|
exc_value: Exception value
|
||||||
|
last_frame: Last stack frame (error location)
|
||||||
|
"""
|
||||||
|
# Table header
|
||||||
|
print(
|
||||||
|
f"{Color.BG_RED}{Color.WHITE}{Color.BOLD} EXCEPTION DETAILS {Color.RESET}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
print(f"{Color.BRIGHT_RED}┌{'─' * 99}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Error type
|
||||||
|
print(
|
||||||
|
f"{Color.BRIGHT_RED}│ {Color.BOLD}Type:{Color.RESET} {Color.BRIGHT_RED}{exc_type.__name__}{Color.RESET}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Error message
|
||||||
|
print(
|
||||||
|
f"{Color.BRIGHT_RED}│ {Color.BOLD}Message:{Color.RESET} {Color.WHITE}{str(exc_value)}{Color.RESET}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
if last_frame:
|
||||||
|
# Error location
|
||||||
|
print(
|
||||||
|
f"{Color.BRIGHT_RED}│ {Color.BOLD}Location:{Color.RESET} {Color.CYAN}{last_frame.filename}{Color.RESET}:{Color.YELLOW}{last_frame.lineno}{Color.RESET}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
if last_frame.line:
|
||||||
|
# Code that caused error
|
||||||
|
print(
|
||||||
|
f"{Color.BRIGHT_RED}│ {Color.BOLD}Code:{Color.RESET} {Color.BRIGHT_WHITE}{last_frame.line.strip()}{Color.RESET}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"{Color.BRIGHT_RED}└{'─' * 99}{Color.RESET}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerFactory:
|
||||||
|
"""Factory to create loggers with different API names."""
|
||||||
|
|
||||||
|
_loggers = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_logger(cls, api_name: str) -> Logger:
|
||||||
|
"""Get or create logger for API name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Name of the API/module
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Logger instance
|
||||||
|
"""
|
||||||
|
if api_name not in cls._loggers:
|
||||||
|
cls._loggers[api_name] = Logger(api_name)
|
||||||
|
return cls._loggers[api_name]
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience function for getting logger
|
||||||
|
def get_logger(api_name: str) -> Logger:
|
||||||
|
"""Get logger for specific API.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
logger = get_logger("AUTH")
|
||||||
|
logger.info("User logged in")
|
||||||
|
logger.error("Login failed")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# ... code ...
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Failed to process request", e)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Name of the API/module (e.g., "AUTH", "FILE", "LFS")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Logger instance
|
||||||
|
"""
|
||||||
|
return LoggerFactory.get_logger(api_name)
|
||||||
|
|
||||||
|
|
||||||
|
# Pre-create common loggers
|
||||||
|
logger_auth = get_logger("AUTH")
|
||||||
|
logger_file = get_logger("FILE")
|
||||||
|
logger_lfs = get_logger("LFS")
|
||||||
|
logger_repo = get_logger("REPO")
|
||||||
|
logger_org = get_logger("ORG")
|
||||||
|
logger_settings = get_logger("SETTINGS")
|
||||||
|
logger_api = get_logger("API")
|
||||||
|
logger_db = get_logger("DB")
|
||||||
Reference in New Issue
Block a user