# Source: https://pawamoy.github.io/posts/unify-logging-for-a-gunicorn-uvicorn-app/ # This code taken from comment by GroverChouT import datetime as dt import logging import os import sys from pprint import pprint from gunicorn.glogging import Logger from loguru import logger from loguru._datetime import datetime as loguru_datetime from server import config START_TIME = dt.datetime.now().strftime("%H-%M-%S") # Python's logging module is not supporting TRACE level # https://bugs.python.org/issue31732 # https://betterstack.com/community/guides/logging/how-to-start-logging-with-python/ logging.addLevelName("TRACE", 5) ENQUEUE = False # True # https://github.com/Delgan/loguru/issues/1118 BACKTRACE = True DIAGNOSE = True LOG_LEVEL = logging.getLevelName(config.LOG_LEVEL_NAME) LOG_FORMAT = "[{process.id}] | {time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - [{extra[id]}] - {message}" LOG_FOLDER_PATH = f"server/user_data/logs/{{time:YYYY-MM-DD}}/{START_TIME}" LOG_FOLDER_PATH_FORMATED = LOG_FOLDER_PATH.format(time=loguru_datetime.now()) def logger_register(): pid = os.getpid() handlers = [ { "sink": sys.stdout, "level": LOG_LEVEL, "format": LOG_FORMAT, "enqueue": ENQUEUE, "backtrace": BACKTRACE, "diagnose": DIAGNOSE, }, ] # handlers.append( # { # "sink": f"{LOG_FOLDER_PATH}/standart_{pid}_log_server", # "level": LOG_LEVEL, # "format": LOG_FORMAT, # "enqueue": ENQUEUE, # } # ) if config.MORE_LOGS: handlers.append( { "sink": f"{LOG_FOLDER_PATH}/debug_{pid}_log_server", "level": "DEBUG", "format": LOG_FORMAT, "enqueue": ENQUEUE, } ) handlers.append( { "sink": f"{LOG_FOLDER_PATH}/trace_{pid}_log_server", "level": "TRACE", "format": LOG_FORMAT, "enqueue": ENQUEUE, } ) logger.configure( handlers=handlers, extra={"id": None}, ) class InterceptHandler(logging.Handler): def emit(self, record): # Get corresponding Loguru level if it exists try: level = logger.level(record.levelname).name except ValueError: level = record.levelno # Find caller from where originated the logged message frame, depth = logging.currentframe(), 2 while frame.f_code.co_filename == logging.__file__: frame = frame.f_back depth += 1 raw_message = record.getMessage() try: logger.opt(depth=depth, exception=record.exc_info).log(level, raw_message) except Exception as ex: pprint(raw_message) print(raw_message) raise ex class GunicornLogger(Logger): def setup(self, cfg) -> None: handler = InterceptHandler() # logging.getLogger("gunicorn.error").handlers = [InterceptHandler()] # logging.getLogger("gunicorn.access").handlers = [InterceptHandler()] # Add log handler to logger and set log level self.error_log.addHandler(handler) self.error_log.setLevel(LOG_LEVEL) self.access_log.addHandler(handler) self.access_log.setLevel(LOG_LEVEL) # Configure logger before gunicorn starts logging logger_register() def configure_logger() -> None: logging.root.handlers = [InterceptHandler()] logging.root.setLevel(LOG_LEVEL) # Remove all log handlers and propagate to root logger for name in logging.root.manager.loggerDict.keys(): logging.getLogger(name).handlers = [] logging.getLogger(name).propagate = True # Configure logger (again) if gunicorn is not used logger_register()