104 lines
2.6 KiB
Python
104 lines
2.6 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
STANDARD_LOG_RECORD_FIELDS = {
|
|
"args",
|
|
"asctime",
|
|
"created",
|
|
"exc_info",
|
|
"exc_text",
|
|
"filename",
|
|
"funcName",
|
|
"levelname",
|
|
"levelno",
|
|
"lineno",
|
|
"module",
|
|
"msecs",
|
|
"message",
|
|
"msg",
|
|
"name",
|
|
"pathname",
|
|
"process",
|
|
"processName",
|
|
"relativeCreated",
|
|
"stack_info",
|
|
"thread",
|
|
"threadName",
|
|
"taskName",
|
|
}
|
|
|
|
|
|
class JsonFormatter(logging.Formatter):
|
|
|
|
def __init__(self, service_name: str):
|
|
super().__init__()
|
|
self.service_name = service_name
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
log_payload = {
|
|
"timestamp": datetime.fromtimestamp(
|
|
record.created, tz=timezone.utc
|
|
).isoformat(),
|
|
"level": record.levelname,
|
|
"service": self.service_name,
|
|
"logger": record.name,
|
|
"message": record.getMessage(),
|
|
"module": record.module,
|
|
"function": record.funcName,
|
|
"line": record.lineno,
|
|
}
|
|
|
|
extra_fields = self._collect_extra_fields(record)
|
|
if extra_fields:
|
|
log_payload["extra"] = extra_fields
|
|
|
|
if record.exc_info:
|
|
log_payload["exception"] = self.formatException(record.exc_info)
|
|
|
|
return json.dumps(log_payload, ensure_ascii=False)
|
|
|
|
def _collect_extra_fields(self, record: logging.LogRecord) -> dict:
|
|
extra_fields = {}
|
|
|
|
for key, value in record.__dict__.items():
|
|
if key in STANDARD_LOG_RECORD_FIELDS or key.startswith("_"):
|
|
continue
|
|
|
|
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
extra_fields[key] = value
|
|
else:
|
|
extra_fields[key] = repr(value)
|
|
|
|
return extra_fields
|
|
|
|
|
|
def setup_logging(service_name: str) -> None:
|
|
log_level_name = os.getenv("LOG_LEVEL", "INFO").upper()
|
|
log_level = getattr(logging, log_level_name, logging.INFO)
|
|
|
|
root_logger = logging.getLogger()
|
|
root_logger.handlers.clear()
|
|
root_logger.setLevel(log_level)
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(JsonFormatter(service_name=service_name))
|
|
root_logger.addHandler(handler)
|
|
|
|
for logger_name in (
|
|
"uvicorn",
|
|
"uvicorn.error",
|
|
"uvicorn.access",
|
|
"aiogram",
|
|
"aiohttp",
|
|
"asyncio",
|
|
):
|
|
logger = logging.getLogger(logger_name)
|
|
logger.handlers.clear()
|
|
logger.propagate = True
|
|
|
|
logging.captureWarnings(True)
|