72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
|
|
"""验证遥测口径、有界内存和请求关联,防止监控干扰业务及泄漏原始地址。"""
|
||
|
|
|
||
|
|
from datetime import datetime, timedelta
|
||
|
|
|
||
|
|
from fastapi import FastAPI
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
|
||
|
|
from app.core.monitoring import REQUEST_ID, MonitorMiddleware, Recorder, merge_metrics
|
||
|
|
|
||
|
|
|
||
|
|
def test_合并直方图计算P95且四百不计系统错误():
|
||
|
|
recorder = Recorder()
|
||
|
|
at = datetime(2026, 11, 2, 10)
|
||
|
|
for status, elapsed in [(200, 10), (400, 100), (403, 200), (500, 750)]:
|
||
|
|
recorder.record("GET", "/items/{id}", status, elapsed, "r", at=at)
|
||
|
|
result = merge_metrics(recorder.metrics()[at].values())
|
||
|
|
assert (result["count"], result["errors_4xx"], result["errors_5xx"]) == (4, 2, 1)
|
||
|
|
assert result["error_rate"] == 25
|
||
|
|
assert result["p95_ms"] == 750
|
||
|
|
assert merge_metrics([])["p95_ms"] is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_中间件生成编号使用模板排除自身且不读取正文():
|
||
|
|
app, recorder = FastAPI(), Recorder()
|
||
|
|
app.add_middleware(MonitorMiddleware, recorder=recorder)
|
||
|
|
|
||
|
|
@app.get("/items/{id}")
|
||
|
|
def item(id: str):
|
||
|
|
return {"request_id": REQUEST_ID.get()}
|
||
|
|
|
||
|
|
@app.get("/api/v1/system/monitor/overview")
|
||
|
|
def monitor():
|
||
|
|
return {}
|
||
|
|
|
||
|
|
client = TestClient(app)
|
||
|
|
response = client.get("/items/private-id?token=never-record")
|
||
|
|
assert response.headers["x-request-id"] == response.json()["request_id"]
|
||
|
|
client.get("/api/v1/system/monitor/overview")
|
||
|
|
client.get("/private-route")
|
||
|
|
values = next(iter(recorder.metrics().values()))
|
||
|
|
assert set(values) == {("GET", "/items/{id}"), ("GET", "/__unmatched__")}
|
||
|
|
assert "private" not in str(recorder.metrics())
|
||
|
|
assert REQUEST_ID.get() == "-"
|
||
|
|
|
||
|
|
|
||
|
|
def test_内存分钟路由和事件队列均有界():
|
||
|
|
recorder = Recorder()
|
||
|
|
at = datetime(2026, 11, 2, 10)
|
||
|
|
for i in range(2100):
|
||
|
|
recorder.record("GET", f"/route/{i}", 500, 1, str(i), at=at)
|
||
|
|
assert len(recorder.events) == 2000 and recorder.dropped_events == 100
|
||
|
|
assert len(recorder.metrics()[at]) == 201
|
||
|
|
for i in range(11):
|
||
|
|
recorder.record("GET", "/ok", 200, 1, "r", at=at + timedelta(minutes=i))
|
||
|
|
assert len(recorder.metrics()) == 10
|
||
|
|
|
||
|
|
|
||
|
|
def test_遥测内部失败不改变业务响应(monkeypatch):
|
||
|
|
app, recorder = FastAPI(), Recorder()
|
||
|
|
app.add_middleware(MonitorMiddleware, recorder=recorder)
|
||
|
|
|
||
|
|
@app.get("/ok")
|
||
|
|
def ok():
|
||
|
|
return {"ok": True}
|
||
|
|
|
||
|
|
def broken(*args, **kwargs):
|
||
|
|
raise RuntimeError("synthetic-recorder-failure")
|
||
|
|
|
||
|
|
monkeypatch.setattr(recorder, "record", broken)
|
||
|
|
response = TestClient(app).get("/ok")
|
||
|
|
assert response.status_code == 200 and response.json() == {"ok": True}
|