如何覆盖Gunicorn的日志配置以使用自定义格式化程序

时间:2016-12-11 15:26:41

标签: python logging flask gunicorn

我希望gunicorn.error使用以下基于键值的日志格式,而不是gunicorn/glogging.py中定义的默认格式:

'format': 'timestamp=%(asctime)s pid=%(process)d loglevel=%(levelname)s msg=%(message)s'`

在我的gunicorn配置文件中:

import logging.config

workers = 2
bind = "127.0.0.1:8000"
loglevel = 'INFO'

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'key_value': {
            'format': 'timestamp=%(asctime)s pid=%(process)d loglevel=%(levelname)s msg=%(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'key_value',
            'stream': 'ext://sys.stdout'
        }
    },
    'loggers': {
        'gunicorn.error': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        },
        'flask.app': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        }
    },
}

logging.config.dictConfig(LOGGING)

Gunicorn以我的自定义格式和默认格式记录两次:

timestamp=2016-12-11 15:20:49,559 pid=22110 loglevel=INFO msg=Starting gunicorn 19.6.0
[2016-12-11 15:20:49 +0000] [22110] [INFO] Starting gunicorn 19.6.0
timestamp=2016-12-11 15:20:49,559 pid=22110 loglevel=INFO msg=Listening at: http://127.0.0.1:8000 (22110)
[2016-12-11 15:20:49 +0000] [22110] [INFO] Listening at: http://127.0.0.1:8000 (22110)
timestamp=2016-12-11 15:20:49,559 pid=22110 loglevel=INFO msg=Using worker: sync
[2016-12-11 15:20:49 +0000] [22110] [INFO] Using worker: sync
timestamp=2016-12-11 15:20:49,560 pid=22115 loglevel=INFO msg=Booting worker with pid: 22115
[2016-12-11 15:20:49 +0000] [22115] [INFO] Booting worker with pid: 22115
timestamp=2016-12-11 15:20:49,595 pid=22115 loglevel=INFO msg=Starting Flask application
timestamp=2016-12-11 15:20:49,659 pid=22120 loglevel=INFO msg=Booting worker with pid: 22120
[2016-12-11 15:20:49 +0000] [22120] [INFO] Booting worker with pid: 22120
timestamp=2016-12-11 15:20:49,693 pid=22120 loglevel=INFO msg=Starting Flask application

我使用logging_tree库来查看已配置的记录器,并且我看到2个gunicorn记录器向控制台发出信息:

<--""
   Level WARNING
   |
   o<--"flask"
   |   Level NOTSET so inherits level WARNING
   |   |
   |   o   "flask.app"
   |       Level INFO
   |       Propagate OFF
   |       Handler Stream <open file '<stdout>', mode 'w' at 0x7f86676b1150>
   |         Level INFO
   |         Formatter fmt='timestamp=%(asctime)s pid=%(process)d loglevel=%(levelname)s msg=%(message)s' datefmt=None
   |
   o<--"gunicorn"
       Level NOTSET so inherits level WARNING
       |
       o   "gunicorn.access"
       |   Level INFO
       |   Propagate OFF
       |
       o   "gunicorn.error"
       |   Level INFO
       |   Propagate OFF
       |   Handler Stream <open file '<stdout>', mode 'w' at 0x7f86676b1150>
       |     Level INFO
       |     Formatter fmt='timestamp=%(asctime)s pid=%(process)d loglevel=%(levelname)s msg=%(message)s' datefmt=None
       |   Handler Stream <open file '<stderr>', mode 'w' at 0x7f86676b11e0>
       |     Formatter fmt='%(asctime)s [%(process)d] [%(levelname)s] %(message)s' datefmt='[%Y-%m-%d %H:%M:%S %z]'
       |
       o<--"gunicorn.http"
           Level NOTSET so inherits level WARNING
           |
           o<--"gunicorn.http.wsgi"
               Level NOTSET so inherits level WARNING

Gunicorn的docs说可以指定要使用的记录器类,但我不知道该怎么做。

5 个答案:

答案 0 :(得分:1)

我通过指定自己的自定义日志记录类获得了很好的运气。您可以通过创建一个继承自Gunicorn的gunicorn.glogging.Logger类的类,然后覆盖此类的setup(self, cfg)方法来完成此操作。

例如:

import logging
from gunicorn import glogging


class CustomLogger(glogging.Logger):
    """Custom logger for Gunicorn log messages."""

    def setup(self, cfg):
        """Configure Gunicorn application logging configuration."""
        super().setup(cfg)

        # Override Gunicorn's `error_log` configuration.
        self._set_handler(
            self.error_log, cfg.errorlog, logging.Formatter(
                fmt=('timestamp=%(asctime)s pid=%(process)d '
                     'loglevel=%(levelname)s msg=%(message)s'))

现在,根据你如何推出Gunicorn,你可以提供这个课程的完整路径; IE:program.app.CustomLogger通过--logger-class选项,或者您可以通过您自己的客户Gunicorn应用程序类直接传递它:

from gunicorn.app import base
from program.app import app, CustomLogger


class WebServer(base.BaseApplication):
    """Gunicorn WSGI Web Server."""

    def __init__(self, app, options):
        """Initialize server object."""
        self.options = options or {}
        self.application = app
        super().__init__()

    def load():
        """Return WSGI application."""
        return self.application

    def load_config():
        """Load configuration into Gunicorn."""
        self.cfg.set('logger_class', CustomLogger)


if __name__ == '__main__':
    WebServer(app, {}).run()

这应该可以实现您期望的目标,而不需要任何令人讨厌的INI文件,并且它是完全支持的配置样式。

答案 1 :(得分:0)

我认为您只需要使用您自己的配置提供配置文件(.ini.conf)。确保覆盖默认值。

examples目录中有examples/logging.conf

答案 2 :(得分:0)

我最近自己碰上了gunicorn,还有其他一些python软件包,它们在导入时不遵循现有的日志记录配置。

以下功能将重置所有现有的日志记录配置并从字典中加载新配置。有关配置字典架构的更多信息,请参见logging.config.dictConfig

在导入所有有问题的模块后只需调用reset_logging_configuration()

import logging
import logging.config

from my_logging_config import CONFIG  # see `logging.config.dictConfig`

def reset_logging_configuration():
    logging._acquireLock()
    for logger in logging.Logger.manager.loggerDict.values():
        if not isinstance(logger, logging.Logger):
            continue
        logger.handlers.clear()        # unset all logger handlers
        logger.filters.clear()         # unset all logger filters
        logger.level = logging.NOTSET  # unset logger level
        logger.propagate = True        # enable logger propagation
    logging._releaseLock()
    logging.config.dictConfig(CONFIG)  # load actual config

Beaware :该方法与logging的内部结构混淆,并且比实际的解决方案更能解决问题。但是,许多模块在导入时会盲目地覆盖现有的logging配置,因此您别无选择,只能在加载后覆盖其配置。谨慎使用!

此代码基于logging/config.py中的代码段。

答案 3 :(得分:0)

Gunicorn使用名为glogger glogger code的自定义记录器,该记录器内部具有Logger类,您可以使用记录器已经可用的属性来更改消息的格式。

具有自定义格式的gunicorn_config文件可能如下所示(属性不言自明)

from gunicorn import glogging    
glogging.Logger.error_fmt = '{"AppName": "%(name)s", "logLevel": "%(levelname)s", "Timestamp": "%(created)f", "Class_Name":"%(module)s", "Method_name": "%(funcName)s", "process_id":%(process)d, "message": "%(message)s"}'
glogging.Logger.datefmt = ""

glogging.Logger.access_fmt = '{"AppName": "%(name)s", "logLevel": "%(levelname)s", "Timestamp": "%(created)f","Class_Name":"%(module)s", "Method_name": "%(funcName)s", "process_id":%(process)d, "message": "%(message)s"}'
glogging.Logger.syslog_fmt = '{"AppName": "%(name)s", "logLevel": "%(levelname)s", "Timestamp": "%(created)f","Class_Name":"%(module)s", "Method_name": "%(funcName)s", "process_id":%(process)d, "message": "%(message)s"}'

我希望这会有所帮助,我认为这是重写记录器格式的一种干净方法。

,但是如果您想在日志中添加自定义属性,则可能必须创建一个新的logger类实例,因为该logger类不支持过滤器。并分配记录器实例也无济于事。

答案 4 :(得分:0)

gunicorn 的日志记录问题在于它通常在您的代码之前在同一个 Python 进程中运行,configures logging 自己运行并在您的任何 WSGI 相关代码运行之前记录一些消息,并且您有机会按照自己的方式配置日志记录。幸运的是,如前所述,它的“记录器”(默认为 gunicorn.glogging.Logger)是可配置的。

$ gunicorn --help | grep logger
  --logger-class STRING
                        The logger you want to use to log events in Gunicorn.

请注意,不建议使用 incremental configuration(即 dictConfiggunicorn 调用一次,然后由您调用),而且可能不是您想要的:

<块引用>

[...] 一旦配置被设置,在运行时任意改变记录器、处理程序、过滤器、格式化程序的对象图并没有令人信服的理由;记录器和处理程序的详细程度可以通过设置级别(以及在记录器的情况下,传播标志)来控制。在多线程环境中,以安全的方式任意更改对象图是有问题的;虽然并非不可能,但其带来的好处不值得增加实施的复杂性。

因此,我建议将 gunicorn 视为 regular library with regards to logging 并完全禁用其日志记录配置(以支持您的应用程序)。

这是一个简单的应用程序,它具有 HTTP 和控制台入口点,两者应具有相同的日志记录配置:

logging_cfg = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'KeyValueFormatter': {
            'format': (
                'timestamp=%(asctime)s pid=%(process)d '
                'loglevel=%(levelname)s msg=%(message)s'
            )
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'KeyValueFormatter',
        }
    },
    'loggers': {
        'gunicorn.access': {
            'propagate': True,
        },
        'gunicorn.error': {
            'propagate': True,
        },
    },
    'root': {
        'level': 'INFO',
        'handlers': ['console'],
    }
}

这里是它的实现,app.py

import logging.config

from gunicorn import glogging


logger = logging.getLogger(__name__)

def get_result():
    logger.info('Calculating result')
    return b'Hello world!'

def wsgi_app(environ, start_response):
    result = get_result()
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [result]

def configure_logging():
    logging.config.dictConfig(logging_cfg)

class UniformLogger(glogging.Logger):

    def setup(self, cfg):
        configure_logging()


if __name__ == '__main__':
    configure_logging()
    print(get_result())

如果你运行 python app.py 你应该得到:

timestamp=2021-07-25 12:09:04,488 pid=4599 loglevel=INFO msg=Calculating result
b'Hello world!'

如果您运行 gunicorn --logger-class=app.UniformLogger app:wsgi_app 后跟 curl localhost:8000

timestamp=2021-07-25 12:16:56,892 pid=4874 loglevel=INFO msg=Starting gunicorn 20.1.0
timestamp=2021-07-25 12:16:56,892 pid=4874 loglevel=INFO msg=Listening at: http://127.0.0.1:8000 (4874)
timestamp=2021-07-25 12:16:56,893 pid=4874 loglevel=INFO msg=Using worker: sync
timestamp=2021-07-25 12:16:56,894 pid=4876 loglevel=INFO msg=Booting worker with pid: 4876
timestamp=2021-07-25 12:17:06,926 pid=4876 loglevel=INFO msg=Calculating result
相关问题