【问题标题】:How to filter logs from gunicorn?如何从 gunicorn 过滤日志?
【发布时间】:2019-02-23 17:44:21
【问题描述】:

我有一个带有 gunicorn 的 Flask 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,但没有帮助。

【问题讨论】:

    标签: python logging gunicorn


    【解决方案1】:

    虽然自定义日志记录类可以工作,但对于简单的访问日志过滤器来说,它可能有点过头了。相反,我会使用 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) 来隐藏他们的活动。

    【讨论】:

      【解决方案2】:

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

      像你一样定义你的类:

      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

      【讨论】:

      • 我想补充一点,过滤请求方法也很聪明。在我的例子中,我过滤了/health,它也匹配/healthfoobar。过滤 GET /health 之类的字符串(带有尾随空格)将匹配特定的路由和方法。
      【解决方案3】:

      我终于找到了一种创建子类的方法

      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。然后,您可以将此类作为gunicorn 的参数提供,例如

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

      【讨论】:

        猜你喜欢
        • 2016-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-25
        • 1970-01-01
        • 2022-11-17
        • 2015-04-03
        • 2022-06-24
        相关资源
        最近更新 更多