Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
36c5bfa
feat(telemetry): initialize OpenTelemetry metrics provider
lovasoa Mar 14, 2026
9d73989
feat(metrics): implement and integrate HTTP request duration metrics
lovasoa Mar 14, 2026
594392d
feat(metrics): instrument database query durations
lovasoa Mar 14, 2026
03f1b7b
refactor(database): add OTel name mapping helper
lovasoa Mar 14, 2026
1692b5c
feat(metrics): instrument database connection pool
lovasoa Mar 14, 2026
feb0a77
feat(telemetry): update OTel collector to receive OTLP metrics
lovasoa Mar 14, 2026
dc98ea3
feat(telemetry): add metrics panels to Grafana dashboard
lovasoa Mar 14, 2026
e5117f4
refactor(metrics): clean up database instrumentation with helper func…
lovasoa Mar 14, 2026
9d969d4
fix(metrics): ensure correct db.system.name for ODBC connections by r…
lovasoa Mar 14, 2026
4ec275a
fix(metrics): remove global OnceLock and pass database type explicitl…
lovasoa Mar 14, 2026
43368e6
refactor(metrics): use OpenTelemetry semantic convention constants
lovasoa Mar 14, 2026
461f348
refactor(metrics): eliminate all OTel convention string literals in f…
lovasoa Mar 14, 2026
090b8c3
fix(telemetry): use standard YAML list notation in OTel collector con…
lovasoa Mar 14, 2026
8d19fc7
fix(telemetry): fix clippy warnings and use latest OTel attribute names
lovasoa Mar 14, 2026
d85bbce
fix(telemetry): add Default impl for TelemetryMetrics
lovasoa Mar 14, 2026
cb6eb49
Add app-scoped telemetry metrics for HTTP
lovasoa Mar 14, 2026
3a8ac44
Refactor db query metrics recording
lovasoa Mar 14, 2026
a8682e5
Use semantic convention attribute names
lovasoa Mar 14, 2026
5082820
Track db pool metrics during lifecycle
lovasoa Mar 14, 2026
ef0d1ce
Adjust telemetry docker compose
lovasoa Mar 14, 2026
33e8632
fix(metrics): configure explicit histogram boundaries and fix databas…
lovasoa Mar 14, 2026
0c46af7
Enable metrics views for explicit histograms
lovasoa Mar 14, 2026
2848690
Use observable gauge for pool connection count
lovasoa Mar 14, 2026
539abd8
Read pool gauge directly from sqlx pool
lovasoa Mar 14, 2026
8f8fc09
telemetry: export OTEL metrics every second in example stack
lovasoa Mar 14, 2026
acd38a7
Improve Grafana trace list links and readability
lovasoa Mar 14, 2026
9fe421d
Restore removed DB connection debug logs
lovasoa Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ tracing-opentelemetry = "0.32"
tracing-actix-web = { version = "0.7", default-features = false, features = ["opentelemetry_0_31"] }
tracing-log = "0.2"
opentelemetry = "0.31"
opentelemetry_sdk = { version = "0.31", features = ["rt-tokio-current-thread"] }
opentelemetry-otlp = { version = "0.31", features = ["http-proto", "grpc-tonic"] }
opentelemetry_sdk = { version = "0.31", features = ["metrics", "rt-tokio-current-thread", "spec_unstable_metrics_views"] }
opentelemetry-otlp = { version = "0.31", features = ["http-proto", "grpc-tonic", "metrics"] }
opentelemetry-semantic-conventions = { version = "0.31", features = ["semconv_experimental"] }


[features]
Expand Down
7 changes: 5 additions & 2 deletions examples/telemetry/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ services:
environment:
- DATABASE_URL=postgres://sqlpage:sqlpage@postgres:5432/sqlpage
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
- OTEL_METRIC_EXPORT_INTERVAL=1000
- OTEL_SERVICE_NAME=sqlpage
volumes:
- ./website:/var/www
Expand Down Expand Up @@ -113,8 +114,10 @@ services:
- "1514:1514/udp"
- "1516:1516/udp"
depends_on:
- tempo
- postgres
tempo:
condition: service_started
postgres:
condition: service_started
loki:
condition: service_healthy

Expand Down
192 changes: 153 additions & 39 deletions examples/telemetry/grafana/sqlpage-home.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"showLineNumbers": false,
"showMiniMap": false
},
"content": "<div style=\"padding: 4px 2px 0; font-size: 15px; line-height: 1.55;\"><h1 style=\"margin: 0 0 10px; font-size: 28px;\">Recent SQLPage traces, logs, and PostgreSQL metrics</h1><p style=\"margin: 0 0 8px;\">Open <a href=\"http://localhost\" target=\"_blank\" rel=\"noopener noreferrer\">http://localhost</a> and interact with the app. New requests will appear here automatically.</p><p style=\"margin: 0; color: #666;\">The trace table shows recent requests. Click any trace ID to open the full span waterfall in Grafana. PostgreSQL slow-query explain plans appear in the PostgreSQL Logs panel and link back to the same trace via the extracted trace ID. The metrics panels come from the OpenTelemetry PostgreSQL receiver via Prometheus.</p></div>",
"content": "<div style=\"padding: 4px 2px 0; font-size: 15px; line-height: 1.55;\"><h1 style=\"margin: 0 0 10px; font-size: 28px;\">SQLPage Observability</h1><p style=\"margin: 0 0 8px;\">Open <a href=\"http://localhost\" target=\"_blank\" rel=\"noopener noreferrer\">http://localhost</a> and interact with the app. New requests will appear here automatically.</p><p style=\"margin: 0; color: #666;\">This dashboard shows traces, logs, and application metrics exported by SQLPage. Trace waterfalls link to PostgreSQL logs via trace IDs. Metrics include HTTP durations, DB query latencies, and connection pool states.</p></div>",
"mode": "html"
},
"pluginVersion": "12.4.0",
Expand All @@ -54,7 +54,39 @@
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 4,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
Expand All @@ -63,38 +95,31 @@
{
"color": "green",
"value": null
},
{
"color": "orange",
"value": 10
}
]
},
"unit": "none"
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 4,
"h": 8,
"w": 12,
"x": 0,
"y": 4
},
"id": 4,
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0",
"targets": [
Expand All @@ -103,12 +128,22 @@
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum(postgresql_backends)",
"expr": "histogram_quantile(0.95, sum(rate(http_server_request_duration_seconds_bucket[5m])) by (le, http_route))",
"legendFormat": "HTTP P95 {{http_route}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "histogram_quantile(0.95, sum(rate(db_client_operation_duration_seconds_bucket[5m])) by (le, db_operation_name))",
"legendFormat": "DB P95 {{db_operation_name}}",
"refId": "B"
}
],
"title": "PostgreSQL Backends",
"type": "stat"
"title": "Request & Query Latency (P95)",
"type": "timeseries"
},
{
"datasource": {
Expand Down Expand Up @@ -162,17 +197,17 @@
}
]
},
"unit": "bytes"
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 4,
"h": 8,
"w": 12,
"x": 12,
"y": 4
},
"id": 5,
"id": 11,
"options": {
"legend": {
"calcs": [],
Expand All @@ -192,12 +227,12 @@
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum(postgresql_db_size) by (postgresql_database_name)",
"legendFormat": "{{postgresql_database_name}}",
"expr": "sum(db_client_connection_count) by (db_client_connection_state)",
"legendFormat": "{{db_client_connection_state}}",
"refId": "A"
}
],
"title": "PostgreSQL Database Size",
"title": "SQLPage DB Connection Pool",
"type": "timeseries"
},
{
Expand All @@ -223,8 +258,8 @@
},
"properties": [
{
"id": "custom.width",
"value": 300
"id": "custom.hidden",
"value": true
}
]
},
Expand All @@ -248,7 +283,35 @@
"properties": [
{
"id": "custom.width",
"value": 140
"value": 120
}
]
},
{
"matcher": {
"id": "byName",
"options": "traceName"
},
"properties": [
{
"id": "custom.width",
"value": 520
},
{
"id": "custom.cellOptions",
"value": {
"type": "data-links"
}
},
{
"id": "links",
"value": [
{
"targetBlank": false,
"title": "${__value.text}",
"url": "/a/grafana-exploretraces-app/explore?traceId=${__data.fields.traceID}"
}
]
}
]
},
Expand Down Expand Up @@ -279,10 +342,10 @@
]
},
"gridPos": {
"h": 12,
"h": 8,
"w": 24,
"x": 0,
"y": 8
"y": 12
},
"id": 2,
"options": {
Expand Down Expand Up @@ -335,7 +398,7 @@
"renameByName": {
"startTime": "Start time",
"traceDuration": "Duration",
"traceID": "Trace ID",
"traceID": "Trace",
"traceName": "Route",
"traceService": "Service"
}
Expand All @@ -344,6 +407,36 @@
],
"type": "table"
},
{
"datasource": {
"type": "tempo",
"uid": "tempo"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 30
},
"id": 12,
"pluginVersion": "12.4.0",
"targets": [
{
"datasource": {
"type": "tempo",
"uid": "tempo"
},
"limit": 20,
"query": "$traceId",
"queryType": "traceql",
"refId": "A",
"tableType": "traces"
}
],
"timeFrom": "1h",
"title": "Selected Trace",
"type": "traces"
},
{
"datasource": {
"type": "loki",
Expand Down Expand Up @@ -428,9 +521,30 @@
"refresh": "5s",
"schemaVersion": 41,
"style": "dark",
"tags": ["sqlpage", "tracing", "logs"],
"tags": ["sqlpage", "tracing", "logs", "metrics"],
"templating": {
"list": []
"list": [
{
"current": {
"selected": true,
"text": "",
"value": ""
},
"hide": 2,
"label": "Trace ID",
"name": "traceId",
"options": [
{
"selected": true,
"text": "",
"value": ""
}
],
"query": "",
"skipUrlSync": false,
"type": "textbox"
}
]
},
"time": {
"from": "now-1h",
Expand All @@ -440,5 +554,5 @@
"timezone": "browser",
"title": "SQLPage Observability Home",
"uid": "sqlpage-tracing-home",
"version": 5
"version": 6
}
Loading
Loading