我们为了问题定位,常见做法是在日志中加入 logid,用于关联一个请求的上下文。这就涉及两个问题:1. logid 这个“全局”变量如何保存传递。2. 如何让打印日志的时候自动带上 logid(毕竟不能每个打日志的地方都手动传入)
logid保存与传递
传统做法就是讲 logid 保存在 threading.local 里面,一个线程里都是一样的值。在 before_app_request
就生成好,logid并放进去。
1 2 3 4 5 6 7 |
import threading from blueprint.hooks import hooks thread_local = threading.local() app = Flask() app.thread_local = thread_local |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import uuid from flask import Blueprint from flask import current_app as app hooks = Blueprint('hooks', __name__) @hooks.before_app_request def before_request(): """ 处理请求之前的钩子 :return: """ # 生成logid app.thread_local.logid = uuid.uuid1().time |
因为需要一个数字的 logid 所以简单使用 uuid.uuid1().time
一般并发完全够了,不会重复且趋势递增(看logid就能知道请求的早晚)。
打印日志自动带上logid
这个就是 Python 日志库自带的功能了,可以使用 Filter 来实现这个需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import logging # https://docs.python.org/3/library/logging.html#logrecord-attributes log_format = "%(asctime)s %(levelname)s [%(threadName)s-%(thread)d] %(logid)s %(filename)s:%(lineno)d %(message)s" file_handler = logging.FileHandler(file_name) logger = logging.getLogger() logid_filter = ContextFilter() file_handler.addFilter(logid_filter) file_handler.setFormatter(logging.Formatter(log_format)) logger.addHandler(file_handler) class ContextFilter(logging.Filter): """ logging Filter """ def filter(self, record): """ threading local 获取logid :param record: :return: """ log_id = thread_local.logid if hasattr(thread_local, 'logid') else '-' record.logid = log_id return True |
log_format 中我们用了很多系统自带的占位符,但 %(logid)s
默认没有的。每条日志打印输出前都会过 Filter,利用此特征我们就可以把 record.logid
赋值上,最终打印出来的时候就有 logid 了。
虽然最终实现了,但因为是通用化方案,所以有些复杂了。其实官方教程中介绍了一种更加简单的方式:injecting-request-information,看来没事还得多看看官方文档。