如何从 gunicorn 中过滤日志?

How to filter logs from gunicorn?

我有一个装有 gunicorn 的烧瓶 API。 Gunicorn 将所有请求记录到我的 API,即

172.17.0.1 - - [19/Sep/2018:13:50:58 +0000] "GET /api/v1/myview HTTP/1.1" 200 16 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"

但是,我想过滤日志以排除在几秒钟内从其他服务调用的某个端点。

我写了一个过滤器来排除这个端点被记录:

class NoReadyFilter(logging.Filter):
    def filter(self, record):
        return record.getMessage().find('/api/v1/ready') == -1

如果我将此过滤器添加到 werkzeuglogger 并使用 Flask 开发服务器,过滤器将起作用。对 /api/v1/ready 的请求不会出现在日志文件中。但是,我似乎无法将过滤器添加到 gunicornlogger。使用以下代码,对 /api/v1/ready 的请求仍然出现:

if __name__ != '__main__':
    gunicorn_logger = logging.getLogger('gunicorn.glogging.Logger')
    gunicorn_logger.setLevel(logging.INFO)
    gunicorn_logger.addFilter(NoReadyFilter())

如何向 gunicorn 记录器添加过滤器?我尝试按照建议 here 将其添加到 gunicorn.error-logger,但没有帮助。

我终于找到了创建子class

的方法
class CustomGunicornLogger(glogging.Logger):

    def setup(self, cfg):
        super().setup(cfg)

        # Add filters to Gunicorn logger
        logger = logging.getLogger("gunicorn.access") 
        logger.addFilter(NoReadyFilter())

继承自 guncorn.glogging.Logger。然后,您可以提供此 class 作为 gunicorn 的参数,例如

gunicorn --logger-class "myproject.CustomGunicornLogger" app

这是一个老问题,但是你所做的没有用,因为你得到了错误的 gunicorn 记录器。访问日志不在 error 记录器上,而是在 access 记录器上 (cf https://github.com/benoitc/gunicorn/blob/b2dc0364630c26cc315ee417f9c20ce05ad01211/gunicorn/glogging.py#L61)

像您一样定义您的 class:

class NoReadyFilter(logging.Filter):
    def filter(self, record):
        return record.getMessage().find('/api/v1/ready') == -1

然后在您应用的主入口点:

if __name__ != "__main__":
    gunicorn_logger = logging.getLogger("gunicorn.access")
    gunicorn_logger.addFilter(NoReadyFilter())

gunicorn 运行 命令:gunicorn --access-logfile=- --log-file=- -b 0.0.0.0:5000 entrypoint:app

虽然自定义日志记录 class 可以工作,但对于简单的访问日志过滤器来说可能有点矫枉过正。相反,我会使用 Gunicorn 的 on_starting() 服务器挂钩来向访问记录器添加过滤器。

钩子可以添加到设置文件中(默认gunicorn.conf.py),所以所有的 gunicorn 配置都在一个地方。

import logging
import re

wsgi_app = 'myapp.wsgi'
bind = '0.0.0.0:9000'
workers = 5
accesslog = '-'

class RequestPathFilter(logging.Filter):
    def __init__(self, *args, path_re, **kwargs):
        super().__init__(*args, **kwargs)
        self.path_filter = re.compile(path_re)

    def filter(self, record):
        req_path = record.args['U']
        if not self.path_filter.match(req_path):
            return True  # log this entry
        # ... additional conditions can be added here ...
        return False     # do not log this entry

def on_starting(server):
    server.log.access_log.addFilter(RequestPathFilter(path_re=r'^/api/v1/ready$'))

关于此示例实现的一些说明:

  • RequestPathFilter 也可以嵌套 on_starting() 以对外部模块隐藏它。
  • 过滤已应用于 record.args。这包含用于构造日志消息的原始值。
  • record.getMessage() 的结果而不是原始值应用过滤是错误的,因为:
    1. Gunicorn 将已经完成构建消息的工作。
    2. 过滤机制可以由客户端操作。这将允许例如攻击者通过将他们的用户代理设置为 Wget/1.20.1/api/v1/ready (linux-gnu).
    3. 来隐藏他们的活动