请求ID日志记录¶
当出现问题时,能够识别特定请求的所有相关日志消息非常重要。这通常是通过为每个请求生成唯一的ID,然后将该ID添加到每个日志条目来实现的。
如果希望跟踪整个应用程序中的每个请求,包括从深度嵌套或以其他方式位于正常请求上下文之外的组件中跟踪每个请求,则可以使用 thread-local 用于存储请求ID的上下文对象:
# context.py
import threading
class _Context:
def __init__(self):
self._thread_local = threading.local()
@property
def request_id(self):
return getattr(self._thread_local, 'request_id', None)
@request_id.setter
def request_id(self, value):
self._thread_local.request_id = value
ctx = _Context()
然后,您可以创建一个 middleware 类为每个请求生成唯一的ID,并将其持久化到线程本地:
# middleware.py
from uuid import uuid4
from context import ctx
class RequestIDMiddleware:
def process_request(self, req, resp):
ctx.request_id = str(uuid4())
# It may also be helpful to include the ID in the response
def process_response(self, req, resp, resource, req_succeeded):
resp.set_header('X-Request-ID', ctx.request_id)
或者,如果您的所有应用程序逻辑都可以访问 request ,您只需使用 context 对象来存储ID:
# middleware.py
from uuid import uuid4
# Optional logging package (pip install structlog)
import structlog
class RequestIDMiddleware:
def process_request(self, req, resp):
request_id = str(uuid4())
# Using Falcon 2.0 syntax
req.context.request_id = request_id
# Or if your logger has built-in support for contexts
req.context.log = structlog.get_logger(request_id=request_id)
# It may also be helpful to include the ID in the response
def process_response(self, req, resp, resource, req_succeeded):
resp.set_header('X-Request-ID', req.context.request_id)
备注
如果您的应用程序部署在注入请求ID头的反向代理之后,您可以很容易地调整此配方以使用上游ID,而不是生成新的ID。通过这样做,您可以跨整个请求路径提供可跟踪性。
考虑到这一点,您可能还希望将此ID包含在对下游服务的任何请求中。
一旦您有权访问请求ID,您就可以通过子类化将其包括在日志中 logging.Formatter
并重写 format()
方法,或使用第三方日志库(如 structlog 如上所述。
紧要关头,您也可以直接输出请求ID:
# some_other_module.py
import logging
from context import ctx
def create_widget_object(name: str) -> Any:
request_id = 'request_id={0}'.format(ctx.request_id)
logging.debug('%s going to create widget: %s', request_id, name)
try:
# create the widget
except:
logging.exception('%s something went wrong', request_id)
logging.debug('%s created widget: %s', request_id, name)