通过钩子函数+Traceid实现Flask链路追踪

traceid,flask · 浏览次数 : 0

小编点评

**代码示例:** ```python import requests from flask import request, g import time from flask import Flask def get_uuid(): # 获取唯一标识符 return str(uuid.uuid4()).replace('-', '') def register_handler(app): app.config['DEBUG'] = True # 开启调试模式 app.run(debug=True) # 启动应用程序 app = Flask(__name__) # 设置唯一标识符 g.traceid = get_uuid() @app.route('/') def index(): # 获取请求参数 trace_id = request.args.get('traceid') # 获取请求数据 data = request.get_json() # 处理请求数据 # 返回响应 return f"Hello, World! (Traceid: {trace_id})" if __name__ == '__main__': register_handler(app) app.run() ``` **执行命令:** 1. 创建一个名为 `simple_link` 的文件夹。 2. 在 `simple_link` 中创建以下文件: - `index.py` - `requirements.txt` 3. 运行以下命令: ``` pip install requirements.txt python index.py ``` 4. 启动服务器: ``` python index.py ``` 5. 在浏览器中访问 `localhost:5000/`。 **结果:** 您将看到以下输出: ``` Hello, World! (Traceid: a5637579351c477a80090a88f5347088) ``` **解释:** 1. 代码首先定义了一个 `get_uuid()` 函数,用于生成唯一的标识符。 2. `register_handler()` 函数设置 `DEBUG` 参数,启用调试模式。 3. 应用程序启动并运行。 4. `index()` 函数定义了一个路由,接收 `traceid` 参数。 5. 获取请求参数并从 `request.args` 中获取 `traceid`。 6. 获取请求数据并将其存储在 `data` 中。 7. 代码使用 `request.get_json()` 将 JSON 数据转换为 Python 数据类型。 8. 代码根据 `traceid` 处理请求数据。 9. 最后,代码返回一个响应,包含 `Hello, World!` 的文本和 `traceid`。

正文

背景

在flask web中我们通常需要一个traceid作为调用参数传递给全链路各个调用函数

  1. 需要针对一次请求创建一个唯一的traceid:这里用uuid去简化代替
  2. 我们需要保证traceid不被污染,在每个请求期间存在,在请求结束销毁且线程独立:这里通过flask中的g对象来存储线程内的数据
  3. 由于我们使用g对象来存储,那么当接口中发起新的请求时候,新请求会创建新的g对象,此时g对象为空,我们需要让traceid可以在多个请求中共享数据:这里通过请求头中增加traceid来传递

实现

首先定义二个主要函数

  • 定义一个请求开始的时候需要调用的函数,用于初始化traceid或者获取上一个请求中的traceid以及其他一些相关请求参数
  • 定义一个请求结束的时候需要调用的函数,用于请求结束的日志响应报文收尾记录。
import requests
from flask import request, g
import time
from flask import Flask

def get_uuid():
    import uuid
    return str(uuid.uuid4()).replace('-', '')


def trace_add_log_record(event_des='',msg_dict={},remarks=''):
    #trace_links_index每次调用+1
    request.trace_links_index = request.trace_links_index + 1
    logs = {
        'traceid': g.traceid,
        'trace_index': request.trace_links_index,
        'event_des': event_des,
        'msg_dict': msg_dict,
        'remarks': remarks
    }
    print(logs)

def trace_start_log_record_handler():
    # 获取traceid,如果存在则使用,否则生成一个
    if "traceid" in request.headers:
        g.traceid =  request.headers['traceid']
    else:
        g.traceid = get_uuid()
    # 初始化trace_links_index
    request.trace_links_index = 0
    # 记录开始时间
    request.start_time = time.time()
    log_msg = {
        'headers': request.headers,
        'url': request.url,
        'method': request.method,
        "request_data": request.args if request.method == "GET" else request.get_json(),
        'ip': request.headers.get("X-Real-IP") or request.remote_addr,
        'start_time': request.start_time
    }
    # 记录日志
    trace_add_log_record(event_des='start', msg_dict=log_msg)

def trace_end_log_record_handler(reponse):
    # 记录结束时间
    request.end_time = time.time()
    # 记录traceid到响应头
    reponse.headers.add('traceid', g.traceid)
    log_msg = {
        "end_time" : request.end_time,
        "cost_time": request.end_time - request.start_time,
        "status_code": reponse.status_code,
        "headers": reponse.headers,
        "response_data": reponse.data.decode('utf-8')
    }
    # 记录日志
    trace_add_log_record(event_des='end', msg_dict=log_msg)

接着,我们通过钩子函数去触发我们上述所写的两个重要函数

"""
写钩子函数在app中注册,还有一种写法
@app.before_request
def before_request2():
    print('before_request2')


@app.after_request
def after_request1(response):
    print('after_request1')

    return response
"""
#写钩子函数在app中注册
def register_handler(response):
    def before_request():
        trace_start_log_record_handler()
    def after_request(response):
        trace_end_log_record_handler(reponse=response)
        return response
    # 注册钩子函数
    response.before_request(before_request)
    response.after_request(after_request)

最后写测试接口进行测试

app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/test')
def test():
    name = request.args.get('name')
    hello_world = requests.get('http://127.0.0.1:5000/', headers={'traceid': g.traceid})
    return name + hello_world.text

@app.route('/test2', methods=['POST'])
def test2():
    name = request.get_json().get('name')
    hello_world = requests.get('http://127.0.0.1:5000/', headers={'traceid': g.traceid})
    return name + hello_world.text

if __name__ == '__main__':

    register_handler(app)
    app.run(debug=True)

这样我们简单了完成了通过traceid把链路串联起来了

{'traceid': 'a5637579351c477a80090a88f5347088', 'trace_index': 1, 'event_des': 'start', 'msg_dict': {'headers': EnvironHeaders([('User-Agent', 'Apifox/1.0.0 (https://apifox.com)'), ('Content-Type', 'application/json'), ('Accept', '*/*'), ('Host', '127.0.0.1:5000'), ('Accept-Encoding', 'gzip, deflate, br'), ('Connection', 'keep-alive')]), 'url': 'http://127.0.0.1:5000/test?name=yetangjian', 'method': 'GET', 'request_data': ImmutableMultiDict([('name', 'yetangjian')]), 'ip': '127.0.0.1', 'start_time': 1717311086.757312}, 'remarks': ''}
{'traceid': 'a5637579351c477a80090a88f5347088', 'trace_index': 1, 'event_des': 'start', 'msg_dict': {'headers': EnvironHeaders([('Host', '127.0.0.1:5000'), ('User-Agent', 'python-requests/2.25.1'), ('Accept-Encoding', 'gzip, deflate'), ('Accept', '*/*'), ('Connection', 'keep-alive'), ('Traceid', 'a5637579351c477a80090a88f5347088')]), 'url': 'http://127.0.0.1:5000/', 'method': 'GET', 'request_data': ImmutableMultiDict([]), 'ip': '127.0.0.1', 'start_time': 1717311086.7663064}, 'remarks': ''}
{'traceid': 'a5637579351c477a80090a88f5347088', 'trace_index': 2, 'event_des': 'end', 'msg_dict': {'end_time': 1717311086.7663064, 'cost_time': 0.0, 'status_code': 200, 'headers': Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '13'), ('traceid', 'a5637579351c477a80090a88f5347088')]), 'response_data': 'Hello, World!'}, 'remarks': ''}
{'traceid': 'a5637579351c477a80090a88f5347088', 'trace_index': 2, 'event_des': 'end', 'msg_dict': {'end_time': 1717311086.7683115, 'cost_time': 0.010999441146850586, 'status_code': 200, 'headers': Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '23'), ('traceid', 'a5637579351c477a80090a88f5347088')]), 'response_data': 'yetangjianHello, World!'}, 'remarks': ''}

 

与通过钩子函数+Traceid实现Flask链路追踪相似的内容:

通过钩子函数+Traceid实现Flask链路追踪

背景 在flask web中我们通常需要一个traceid作为调用参数传递给全链路各个调用函数 需要针对一次请求创建一个唯一的traceid:这里用uuid去简化代替 我们需要保证traceid不被污染,在每个请求期间存在,在请求结束销毁且线程独立:这里通过flask中的g对象来存储线程内的数据 由

iptables的使用

iptables的使用 容器系列文章 容器系列视频 iptables介绍 iptables是基于内核提供的netfilter框架实现的,网络协议栈是分层的,在tcp ip网络模型里,tcp传输层下面的一层就是ip网络层,而netfilter就是工作在ip网络层,通过定义钩子函数,允许用户代码干预数据

Nuxt3 的生命周期和钩子函数(六)

摘要:本文深入解析了Nuxt3框架中的多个核心生命周期钩子和组件注册功能,包括imports:sources、imports:extend、imports:context、imports:dirs、components:dirs及components:extend,通过实例代码指导开发者如何在不同场...

Nuxt3 的生命周期和钩子函数(七)

摘要:文章阐述了Nuxt3中Nitro生命周期钩子的使用,如nitro:config自定义配置、nitro:init注册构建钩子、nitro:build:before/after调整构建设置及处理公共资产、prerender:routes扩展预渲染路由、build:error捕获构建错误,通过示例代...

12.1 使用键盘鼠标监控钩子

本节将介绍如何使用`Windows API`中的`SetWindowsHookEx`和`RegisterHotKey`函数来实现键盘鼠标的监控。这些函数可以用来设置全局钩子,通过对特定热键挂钩实现监控的效果,两者的区别在于`SetWindowsHookEx`函数可以对所有线程进行监控,包括其他进程中...

4.3 IAT Hook 挂钩技术

IAT(Import Address Table)Hook是一种针对Windows操作系统的API Hooking 技术,用于修改应用程序对动态链接库(DLL)中导入函数的调用。IAT是一个数据结构,其中包含了应用程序在运行时使用的导入函数的地址。IAT Hook的原理是通过修改IAT中的函数指针,将原本要调用的函数指向另一个自定义的函数。这样,在应用程序执行时,当调用被钩子的函数时,实际上会执行

驱动开发:摘除InlineHook内核钩子

在笔者上一篇文章`《驱动开发:内核层InlineHook挂钩函数》`中介绍了通过替换`函数`头部代码的方式实现`Hook`挂钩,对于ARK工具来说实现扫描与摘除`InlineHook`钩子也是最基本的功能,此类功能的实现一般可在应用层进行,而驱动层只需要保留一个`读写字节`的函数即可,将复杂的流程放在应用层实现是一个非常明智的选择,与`《驱动开发:内核实现进程反汇编》`中所使用的读写驱动基本一致,

4.5 x64dbg 探索钩子劫持技术

钩子劫持技术是计算机编程中的一种技术,它们可以让开发者拦截系统函数或应用程序函数的调用,并在函数调用前或调用后执行自定义代码,钩子劫持技术通常用于病毒和恶意软件,也可以让开发者扩展或修改系统函数的功能,从而提高软件的性能和增加新功能。钩子劫持技术的实现一般需要在对端内存中通过`create_alloc()`函数准备一块空间,并通过`assemble_write_memory()`函数,将一段汇编代

4.4 EAT Hook 挂钩技术

EAT(Export Address Table)用于修改动态链接库(DLL)中导出函数的调用。与`IAT Hook`不同,EAT Hook是在DLL自身中进行钩子操作,而不是修改应用程序的导入表。它的原理是通过修改DLL的导出函数地址,将原本要导出的函数指向另一个自定义的函数。这样,在应用程序调用DLL的导出函数时,实际上会执行自定义的函数。与IAT不同是EAT存放的不是函数地址,而是导出函数地

驱动开发:内核扫描SSDT挂钩状态

在笔者上一篇文章`《驱动开发:内核实现SSDT挂钩与摘钩》`中介绍了如何对`SSDT`函数进行`Hook`挂钩与摘钩的,本章将继续实现一个新功能,如何`检测SSDT`函数是否挂钩,要实现检测`挂钩状态`有两种方式,第一种方式则是类似于`《驱动开发:摘除InlineHook内核钩子》`文章中所演示的通过读取函数的前16个字节与`原始字节`做对比来判断挂钩状态,另一种方式则是通过对比函数的`当前地址`