Show parsed log tables instead of raw lines

Raw lines are close to unreadable when the payload is JSON, and the Kong audit
records are the worst case: a wall of braces where the interesting fields are
status, route and latency.

Each dashboard now leads with a table carrying one column per parsed field,
sortable and filterable, with the unparsed record as the last column and the
cell inspector enabled on every cell. The original logs panel moves into a
collapsed row underneath rather than disappearing: a table cannot render ANSI
colour, expand a record inline, tail live, or turn a trace id into a link to
Tempo.

Parsing is per-format since the five sources agree on nothing — nested JSON for
Kong, JSON for Keycloak, NCSA access lines for the Django API, ANSI-coloured
text for LibreChat, logfmt for the stack itself.

The Kong and stack parsers name their fields explicitly rather than extracting
everything: a bare `| json` on a Kong record yields around seventy columns, and
a bare `| logfmt` explodes Loki's own query-metrics lines into dozens. Columns
are selected by include-pattern rather than by excluding names, because the
stream labels that ride along are not a fixed set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-25 18:11:53 +02:00
parent 0ba38bc8f7
commit 7a8a476ef0
6 changed files with 1295 additions and 88 deletions
+286 -19
View File
@@ -1189,9 +1189,9 @@
"description": "4xx and 5xx only, broken down by the Kong service that answered."
},
{
"id": 16,
"type": "logs",
"title": "Recent audit records",
"id": 21,
"type": "table",
"title": "Audit records",
"datasource": {
"type": "loki",
"uid": "loki"
@@ -1209,21 +1209,288 @@
"type": "loki",
"uid": "loki"
},
"expr": "{job=\"kong-audit\", domain=~\"${gateway:pipe}\"} | json",
"queryType": "range"
"expr": "{job=\"kong-audit\", domain=~\"${gateway:pipe}\"} | json status=\"response.status\", method=\"request.method\", uri=\"request.uri\", route=\"route.name\", svc=\"service.name\", latency=\"latencies.request\", client=\"client_ip\"",
"queryType": "range",
"maxLines": 500
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"enableLogDetails": true
"transformations": [
{
"id": "extractFields",
"options": {
"source": "labels",
"replace": false
}
},
{
"id": "filterFieldsByName",
"options": {
"include": {
"pattern": "^(timestamp|body|status|method|uri|route|svc|latency|client)$"
}
}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {
"Time": 0,
"status": 1,
"method": 2,
"uri": 3,
"route": 4,
"svc": 5,
"latency": 6,
"client": 7,
"body": 8
},
"renameByName": {
"status": "Status",
"method": "Method",
"uri": "URI",
"route": "Route",
"svc": "Service",
"latency": "Latency (ms)",
"client": "Client",
"body": "Raw record",
"timestamp": "Time"
}
}
}
],
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": true,
"filterable": true
},
"mappings": []
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Status"
},
"properties": [
{
"id": "custom.width",
"value": 80
},
{
"id": "mappings",
"value": [
{
"type": "regex",
"options": {
"pattern": "^2..$",
"result": {
"color": "green",
"index": 0
}
}
},
{
"type": "regex",
"options": {
"pattern": "^3..$",
"result": {
"color": "blue",
"index": 1
}
}
},
{
"type": "regex",
"options": {
"pattern": "^4..$",
"result": {
"color": "orange",
"index": 2
}
}
},
{
"type": "regex",
"options": {
"pattern": "^5..$",
"result": {
"color": "red",
"index": 3
}
}
}
]
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-text"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Method"
},
"properties": [
{
"id": "custom.width",
"value": 80
}
]
},
{
"matcher": {
"id": "byName",
"options": "URI"
},
"properties": [
{
"id": "custom.width",
"value": 260
}
]
},
{
"matcher": {
"id": "byName",
"options": "Route"
},
"properties": [
{
"id": "custom.width",
"value": 220
}
]
},
{
"matcher": {
"id": "byName",
"options": "Service"
},
"properties": [
{
"id": "custom.width",
"value": 140
}
]
},
{
"matcher": {
"id": "byName",
"options": "Latency (ms)"
},
"properties": [
{
"id": "custom.width",
"value": 110
}
]
},
{
"matcher": {
"id": "byName",
"options": "Client"
},
"properties": [
{
"id": "custom.width",
"value": 130
}
]
},
{
"matcher": {
"id": "byName",
"options": "Raw record"
},
"properties": [
{
"id": "custom.width",
"value": 260
}
]
}
]
},
"description": "Full Kong record per request: route, status, latencies, client, and the captured request/response bodies. Where the record carries a trace id, the Loki datasource's derived field opens the matching trace in Tempo."
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
},
"sortBy": [
{
"displayName": "Time",
"desc": true
}
]
},
"description": "Each Kong record parsed into columns. The unparsed JSON stays in the last column — click any cell to inspect the full value."
},
{
"id": 22,
"type": "row",
"title": "Raw log lines",
"collapsed": true,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 52
},
"panels": [
{
"id": 16,
"type": "logs",
"title": "Raw log lines",
"datasource": {
"type": "loki",
"uid": "loki"
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 53
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "{job=\"kong-audit\", domain=~\"${gateway:pipe}\"} | json",
"queryType": "range"
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"enableLogDetails": true
},
"description": "Kept alongside the table: the logs panel renders ANSI colour, expands a record's detail inline, tails live, and turns a trace id into a link to Tempo — none of which a table can do. Collapsed so it stays out of the way until needed."
}
]
},
{
"id": 17,
@@ -1234,7 +1501,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 52
"y": 53
},
"panels": []
},
@@ -1250,7 +1517,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 53
"y": 54
},
"targets": [
{
@@ -1315,7 +1582,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 53
"y": 54
},
"targets": [
{
@@ -1380,7 +1647,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 53
"y": 54
},
"targets": [
{