Add observability module: Spring AI built-in meters and spans, cost per endpoint from token usage, Prometheus and Grafana dashboard

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JXVi2GMQ7bR5EmbUFdDj7N
This commit is contained in:
Claude
2026-10-09 06:39:21 +00:00
parent 65f580c5bc
commit cc2a1b8bcb
45 changed files with 1731 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Runs every dashboard query twice: straight against Prometheus, and through Grafana's /api/ds/query with the
provisioned datasource (what a panel really does). Prints one line per panel query and exits non-zero if any
query errors or returns no series. Usage: verify-dashboard.py [output-file]"""
import base64, json, pathlib, sys, time, urllib.parse, urllib.request
root = pathlib.Path(__file__).resolve().parent.parent
dash = json.loads((root / "dashboards" / "spring-ai-observability.json").read_text())
auth = "Basic " + base64.b64encode(b"admin:admin").decode()
def get(url, data=None, headers=None):
req = urllib.request.Request(url, data=data, headers=headers or {})
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())
lines, bad = [], 0
now = int(time.time())
lines.append(f"{'panel':<58} {'prom series':>11} {'grafana frames':>14}")
for p in dash["panels"]:
for t in p["targets"]:
q = urllib.parse.urlencode({"query": t["expr"]})
prom = get("http://127.0.0.1:9090/api/v1/query?" + q)
n_prom = len(prom["data"]["result"]) if prom["status"] == "success" else -1
body = json.dumps({"queries": [{"refId": t["refId"], "datasource": t["datasource"], "expr": t["expr"], "instant": False,
"range": True, "interval": "", "intervalMs": 5000, "maxDataPoints": 200}],
"from": str((now - 600) * 1000), "to": str(now * 1000)}).encode()
gf = get("http://127.0.0.1:3000/api/ds/query", body, {"Content-Type": "application/json", "Authorization": auth})
res = gf["results"][t["refId"]]
n_gf = -1 if "error" in res else len(res.get("frames", []))
name = f"{p['title'][:48]} [{t['refId']}]"
lines.append(f"{name:<58} {n_prom:>11} {n_gf:>14}")
if n_prom < 1 or n_gf < 1:
bad += 1
out = "\n".join(lines)
print(out)
if len(sys.argv) > 1:
pathlib.Path(sys.argv[1]).write_text("# Every dashboard query, run against Prometheus and through Grafana's query API after scripts/load.sh\n\n" + out + "\n")
sys.exit(1 if bad else 0)