透過鉤子函式+Traceid實現Flask鏈路追蹤

yetangjian發表於2024-06-02

背景

在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': ''}

相關文章