感谢关注天善智能,走好数据之路↑↑↑

欢迎关注天善智能,我们是专注于商业智能BI,人工智能AI,大数据分析与挖掘领域的垂直社区,学习,问答、求职一站式搞定!

本文作者:天善智能社区专家 Ljian1992

天善智能社区地址:https://www.hellobi.com/

前言

对于如何优雅的实现打印requestId这事情上,google了半天都没有找到一个满足我的需求的实现。唯有自己去实现一个,整个思路我觉得还挺有趣的,值得分享出来,所以就有了这文章。

需求

我负责的子系统有:IP管理系统,爬虫管理系统,爬虫系统。这个只是整个系统的其中一部分。这个时候,如果某个请求或者某个服务出错了,需要定位问题的原因就比较复杂了。为了快速定位问题,很核心的一个点日志规范,而日志规范中最最最重要的一点就是要有个requestId来跟踪整个整一个请求或服务的调用链路。需求来了,为每一条日志都加上requestId这个参数。这个需求再抽象一下,就是为每一个日志都增加一个前缀。这个前缀可以有requestId, server_name等等信息。这样抽象之后呢,如果还需要增加其他参数也方便。ps:requestId的生成可以简单粗暴的uuid来,也可以让它带有信息(IP,服务名等等),这个不是本文的重点,最好就由一个专门的函数来生成,以后有变化改起来也方便。正如某名言说的那样,用一个稳定的东西来包装一个不稳定的东西以提供一个稳定的服务

实现思路

不用动脑子想到的实现方式

类的初始化参数&函数参数都提供一个参数专门来传递log前缀

做一个装饰器给需要打印业务log的方法或函数使用

对于方式1,这个实现起来巨恶心,入侵过大,偶合过大我的内心是拒绝的。对于方式2稍微好了一点,传说当中的切面编程,对代码没有入侵性,但是粒度太大,函数或者方法内的log无法加上前缀,肯定是不行的,所以只能放弃。所以此时又把目光放到方式1上,此时大脑冒出了一个大胆的想法,给整个系统的类或函数打补丁。补丁的作用就是为类的初始化方法添加上log前缀的可选参数,函数参数添加上log前缀的可选参数。然后还居然真的有相关的模块可以获取方法或者类的参数信息,并修改,这个高级功能只存在python3之中。准备撸起袖子就开干的时候,还是被恶心到了,即便实现了,还是需要将log前缀层层传递,代码将会变得很难看。前面说的层层传递,抽象一下就是在每个调用间共享一个变量,此时大脑想起了threading模块中的local类,但是但是我们的服务部署一般是gevent的模式,threading的local模块并不是一个协程安全的东西。万能的google再次拯救我,在werkzeug实现了一个协程安全Local类,celery当中也实现了,实现方式都差不多,连注释都一毛一样。

最终的实现方式

暂时想到的最优的实现方式:在服务的开头出在local对象中设置上log的前缀,出口处删掉,每条log都从Local对象处获取前缀打印。这都是由框架来做,具体的业务代码是无感知的。这样做的好处就是对代码几乎是无入侵的,扩展性强,大家都开心的方式。在细化一下具体实现方式,本质上还是添加中间层来实现。对没错,任何计算机问题均可以通过增加一个中间层来解决

http请求:在header处添加上requestID这参数,然后中间件来做统一处理

celery异步任务:任务消息带上log前缀,任务开始前做添加上前缀,任务返回后删掉

现核心过程

一点说明,代码中有部分配置信息,这个我赖得改直接就粘贴复制过来了,不是什么重点内容 ,忽略就好。另外的,必要的一下基础知识 ,我并没有进行讲解,一来嘛,不是重点,二来嘛,我感觉我会写得没别人好,但会给出有帮助的文章链接。

django 中间件

给application添加Local对象清理的中间件

有参考价值的文章: 关于werzeug模块中对的Local对象的使用的中文文章

文件wsgi.py

from werkzeug.local import Local, LocalManager

local = Local()

local_manager = LocalManager([local])

# 请求结束后对当前线程或者协程中的local对象中的变量进行清除

application = local_manager.make_middleware(application)

自定义django的中间件

将django中间件怎么做的文章: django中间件。代码没啥好说的,核心点就是设置给Local对象设置上一个dict对象,里面包含了需要的前缀信息。

from xxx import local

class LogPrexiMiddleware(MiddlewareMixin):

def process_request(self, request):

log_data = self._get_log_data(request)

attr_name = getattr(settings, LOCAL_LOG_ATTR_NAME, DEFAULT_LOCAL_LOG_ATTR_NAME)

setattr(local, attr_name, log_data)

def _get_log_data(self, request):

log_header_key = getattr(settings, LOG_HEADER_KEY_SETTING, None)

log_data = {}

if log_header_key and log_header_key in request.META:

log_data = {"requestId": request.META.get(log_header_key)}

if not log_data and getattr(settings, GENERATE_REQUEST_ID_IF_NOT_IN_HEADER_SETTING, False):

# 通过一个函数生成request_id

request_id = gen_request_id()

log_data = {"requestId": request_id}

return log_data

log的filter

关于python log处理模块的文章:python log基础知识。

from xxx import local

class PrefixFilter(Filter):

def filter(self, record):

log_data = getattr(local, settings.LOCAL_LOG_ATTR_NAME, {})

log_prefix = "".join(["[{}={}]".format(key, value) for key, value in log_data.items()])

# log format: [%(log_prefix)s][%(asctime)s,%(msecs)d][%(levelname)s]

record.log_prefix = log_prefix

return True

celery异步任务

这个是整个实现的难点所在,也是值得借鉴的地方,这部分必须唠叨的解释一下,因为这部分网上没啥好的文章给我贴出来,而且有的部分被迫自己实现的,说来都是泪啊,差点弄我一个通宵。前面所说的,任务消息带上log前缀,任务开始前做添加上前缀,任务返回后删掉难点是在于如何优雅的做到不入侵原先的代码。

任务消息带上log前缀

马上想到的方式在异步任务的参数上带上,但是感觉不太优雅,直觉上celery框架应该有提供携带额外信息的参数。翻了一下celery官网文档,有一个headers的参数放出刺眼的光芒引起了我的注意,测试了一下还真的可以,这样仅仅需要将log前缀信息放在headers中即可。ps:测试的时候还顺便更加理解amqp这协议,celery的一下额外的控制参数例如task_id都是存放在消息的headers中的,然后在接收到消息后做统一的处理。

任务开始前做添加上前缀,任务返回前删掉

直觉上celery框架是有留出有相关的接口做这事,还是去翻文档。翻到有一个after_return的handler, 但是但是没有一个pre_execute的handler。只找到一个task_prerun的信号,这货是个全局性的东西,感觉上粒度太大,我不好控制。这时候冒出大胆的想法,去翻源码,看看怎么添加上pre_execute的handler,再利用monkey_patch给celery这个包打补丁。直接翻源码还是挺痛苦的,有点晕,此时想到了pycharm是可以断点调试的,断点观察整个调用过程+全局关键字搜索来找添加的入口。最后在celery/app/trace.py文件中找到了build_tracer这个函数,一个任务执行所有的流程控制都在里面。真的是牛逼啊,用一个流程控制的方法来控制整个流程,思路很好,值得借鉴,大神们的软件架构思路无比膜拜。所要做的就很简单,按照里面的规则照葫芦画瓢的添加一个pre_execute的处理,然后mokey_patch搞起来即可。

具体的一下代码

from xxx import local, local_manager

class BaseTask(app.Task):

def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,

link=None, link_error=None, shadow=None, **options):

"""

自动根据local变量中的log相关的信息,设置去message的headers中,让异步任务根据设置的log信息设置log

"""

# 自动把log prefix 信息添加到消息的headers中

log_data = getattr(local, settings.LOCAL_LOG_ATTR_NAME, {})

headers = options.get("headers", {})

headers.update({settings.LOCAL_LOG_ATTR_NAME: json.dumps(log_data)})

options["headers"] = headers

super(BaseTask, self).apply_async(args=args, kwargs=kwargs, task_id=task_id, producer=producer,

link=link, link_error=link_error, shadow=shadow, **options)

def pre_execute(self, status, retval, task_id, args, kwargs, einfo):

# 任务开始前将headers中的log前缀信息,给添加到local变量里面

log_data = {}

if hasattr(self.request, settings.LOCAL_LOG_ATTR_NAME):

try:

log_data = json.loads(getattr(self.request, settings.LOCAL_LOG_ATTR_NAME))

except ValueError as why:

sm_log.debug("异步任务设置log的出错", exc_info=True)

setattr(local, settings.LOCAL_LOG_ATTR_NAME, log_data)

def after_return(self, status, retval, task_id, args, kwargs, einfo):

# 清理变量,其实不清理也没什么影响,毕竟部署都是进程池或者是协程池的,不会有很多。

super(BaseTask, self).after_return(status, retval, task_id, args, kwargs, einfo)

local_manager.cleanup()

# 使用说明

方式一:粒度可控的

@app.task(bind=True, base=BaseTask, max_retries=3)

def debug_t(self, a):

pass

方式二:全局的

app.Task = BaseTask

最后的唠叨

人上了年纪,容易发出一些感慨。这个东西实现后,总结的体会是:1. 越来越意识到抽象是一样多么重要的事情,如果没有去抽象出共性,我可能就被迫用最恶心的方式1来实现了。2. 编程这工作虽然说是偏文科,以知识和经验为主,但是良好的分析思路却能很好的弥补上知识和经验上的不足。在分析能力的提高这部分还是要经过有意识的训练才行,这里立个flag,那天我觉得自己分析能力有了一个台阶的上升后,撸一篇如果分析的文章。

优雅的实现打印requestId相关推荐

  1. Python爱好者社区历史文章列表(每周append更新一次)

    2月22日更新:   0.Python从零开始系列连载: Python从零开始系列连载(1)--安装环境 Python从零开始系列连载(2)--jupyter的常用操作 Python从零开始系列连载( ...

  2. Python爱好者社区历史文章列表(1)

    2019独角兽企业重金招聘Python工程师标准>>> https://mp.weixin.qq.com/s?__biz=MzI5NDY1MjQzNA==&mid=22474 ...

  3. 【Java】遍历时优雅地删除集合元素

    实体类 import java.io.Serializable; import java.util.Objects;public class Person implements Serializabl ...

  4. Python优雅技巧

    枚举 不要这么做: i = 0 for item in iterable: print i, item i += 1 而是这样: for i, item in enumerate(iterable): ...

  5. tornado + peewee 下打印执行 SQL 日志

    tornado + peewee 下打印执行 SQL 日志 文章目录 tornado + peewee 下打印执行 SQL 日志 起步 环境准备 打印 peewee 的 SQL 日志 第一个为什么 第 ...

  6. python打印数字正方形_python打印空心正方形-女性时尚流行美容健康娱乐mv-ida网...

    女性时尚流行美容健康娱乐mv-ida网 mvida时尚娱乐网 首页 美容 护肤 化妆技巧 发型 服饰 健康 情感 美体 美食 娱乐 明星八卦 首页  > 高级搜索 python 实现全排列的 打 ...

  7. 程序员要避免的五种程序注释方式

    导读:注释,本是提高代码可读性.让其他开发人员更快速地理解程序的,然而一些无意义的注释会让人郁闷至极.本文是国外网站GreaterGeek上的一篇文章,作者通过分类介绍了五种一定要避免的程序注释方式. ...

  8. 老生常谈:注释怎么写?

    版权声明:本文为博主原创文章,未经博主允许不得转载. 整理自知乎上我的一次回答.http://www.zhihu.com/question/20594192 我的观点,只写说明性注释,不写功能性注释. ...

  9. vue商城源码_一个标星 5.2k+ 的牛逼开源商城系统

    关注上方"终端研发部",选择星标, 关键时间,第一时间送达! 来源:GitHub精选 大家好,我是于小二.今天推荐的这个项目是「newbee-mall」,这个名字乍一看我以为叫牛逼 ...

最新文章

  1. 深入解析Java锁机制
  2. 分布式事务之消息补偿解决方案
  3. python处理excel案例_python操作excel例子
  4. Oracle 存储过程错误之PLS-00201: 必须声明标识符
  5. Qt5:Qt中图片的翻转,旋转,缩放,扭曲操作
  6. 置顶,博客中所有源码 github
  7. ckeditor:复制内容到ckeditor时,只保留文本,忽略其样式解决方法
  8. 六、Linux企业级YUM软件管理
  9. AutoCad入门(一)
  10. 高斯消元法求解线性方程组
  11. Java混元功法_陈式太极心意混元 入门功法---太极内功 1
  12. [办公软件word] 怎么让Worde2019显示所有批注?
  13. python3 爬虫学习:自动给你心上人的微博点赞(一)
  14. 计算机一直重启无法进入系统,电脑进不了系统一直重启怎么处理
  15. 为何很多 App 图标使用白色做底色?
  16. 2C领域最后一个资本宠儿,快看成“超新Z世代”娱乐平台
  17. java 反转字符串
  18. 2061:【例1.2】梯形面积
  19. Google Chrome 66 稳定版更新:修复四大严重安全漏洞
  20. AcWing1102. 移动骑士

热门文章

  1. 网易CEO丁磊:我从来没有远大的理想
  2. public static
  3. 【桌面开发】vscode+Debugger-For-NWjs+nwjs-sdk-vx.x.x-xxos调试环境搭建
  4. GC垃圾回收之CMS、G1
  5. 2018ICPC南京 Problem M. Mediocre String Problem(回文串,马拉车,扩展KMP)
  6. 【算法学习】递归函数的使用
  7. CVPR 2023 | 去雨去噪去模糊,图像low-level任务,视觉AIGC系列
  8. 从防窃听到双系统隔离 手机安全是怎样一步步发展的?
  9. 《生物化学与分子生物学》----蛋白质----听课笔记(七)
  10. python指数平滑预测_指数平滑方法简介