Pyt*_*ice 12 python logging flask gunicorn
我希望 gunicorn.error 使用以下基于键值的日志格式,而不是在gunicorn/glogging.py 中定义的默认值:
'format': 'timestamp=%(asctime)s pid=%(process)d loglevel=%(levelname)s msg=%(message)s'`
Run Code Online (Sandbox Code Playgroud)
在我的 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)
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我使用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
Run Code Online (Sandbox Code Playgroud)
Gunicorn 的文档说可以指定要使用的记录器类,但我不知道如何做到这一点。
gunicorn日志记录的问题在于,它通常在您的代码之前在同一个 Python 进程中运行,自行配置日志记录并在任何 WSGI 相关代码运行之前记录一些消息,并且您有机会按照自己的方式配置日志记录。幸运的是,正如已经提到的,它的“记录器”(gunicorn.glogging.Logger默认情况下)是可配置的。
$ gunicorn --help | grep logger
--logger-class STRING
The logger you want to use to log events in Gunicorn.
Run Code Online (Sandbox Code Playgroud)
请注意,不建议使用增量配置(即由您dictConfig调用一次),并且可能不是您想要的:gunicorn
[...] 一旦设置了配置,就没有令人信服的情况可以在运行时任意更改记录器、处理程序、过滤器、格式化程序的对象图;记录器和处理程序的详细程度可以通过设置级别来控制(对于记录器,还可以设置传播标志)。在多线程环境中以安全的方式任意改变对象图是有问题的;虽然并非不可能,但其带来的好处不值得它增加实施的复杂性。
因此,我建议将其视为gunicorn日志记录方面的常规库并完全禁用其日志记录配置(有利于您的应用程序)。
这是一个简单的应用程序,具有 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'],
}
}
Run Code Online (Sandbox Code Playgroud)
这里是它的实现,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())
Run Code Online (Sandbox Code Playgroud)
如果你运行python app.py你应该得到:
timestamp=2021-07-25 12:09:04,488 pid=4599 loglevel=INFO msg=Calculating result
b'Hello world!'
Run Code Online (Sandbox Code Playgroud)
如果你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
Run Code Online (Sandbox Code Playgroud)
通过指定我自己的自定义日志记录类,我很幸运。你可以通过创建一个继承自 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')))
Run Code Online (Sandbox Code Playgroud)
现在,根据您启动 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()
Run Code Online (Sandbox Code Playgroud)
这应该可以实现您想要的目标,而无需任何令人讨厌的 INI 文件,并且它是一种完全受支持的配置样式。
小智 5
Gunicorn 使用名为 glogger 的自定义记录器glogger 代码,其内部有一个 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"}'
Run Code Online (Sandbox Code Playgroud)
我希望这有帮助,在我看来,这是覆盖记录器格式的干净方法之一。
但是如果您想在日志中添加自定义属性,您可能必须创建记录器类的新实例,因为此记录器类不支持过滤器。并且分配记录器实例也无济于事。
| 归档时间: |
|
| 查看次数: |
13482 次 |
| 最近记录: |