如何从 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
如果我将此过滤器添加到 werkzeug
logger 并使用 Flask 开发服务器,过滤器将起作用。对 /api/v1/ready
的请求不会出现在日志文件中。但是,我似乎无法将过滤器添加到 gunicorn
logger。使用以下代码,对 /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()
的结果而不是原始值应用过滤是错误的,因为:
- Gunicorn 将已经完成构建消息的工作。
- 过滤机制可以由客户端操作。这将允许例如攻击者通过将他们的用户代理设置为
Wget/1.20.1/api/v1/ready (linux-gnu)
. 来隐藏他们的活动
我有一个装有 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
如果我将此过滤器添加到 werkzeug
logger 并使用 Flask 开发服务器,过滤器将起作用。对 /api/v1/ready
的请求不会出现在日志文件中。但是,我似乎无法将过滤器添加到 gunicorn
logger。使用以下代码,对 /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()
的结果而不是原始值应用过滤是错误的,因为:- Gunicorn 将已经完成构建消息的工作。
- 过滤机制可以由客户端操作。这将允许例如攻击者通过将他们的用户代理设置为
Wget/1.20.1/api/v1/ready (linux-gnu)
. 来隐藏他们的活动