diff --git a/src/components/grafana/dash.json b/src/components/grafana/dash.json new file mode 100644 index 00000000..4a0633ad --- /dev/null +++ b/src/components/grafana/dash.json @@ -0,0 +1,510 @@ +{ + "apiVersion": "dashboard.grafana.app/v2", + "kind": "Dashboard", + "metadata": { + "name": "27f71330-d1af-4fde-b2f8-41285dddef6e", + "namespace": "stacks-1412373", + "uid": "e01ae8e8-c5e8-49ff-852c-eb197b68ba21", + "resourceVersion": "2045161115373011293", + "generation": 26, + "creationTimestamp": "2026-04-17T13:54:05Z", + "labels": { + "grafana.app/deprecatedInternalID": "504502390951936" + }, + "annotations": { + "grafana.app/createdBy": "service-account:afjd95p4uhqtcc", + "grafana.app/folder": "dfjd968owvfggf", + "grafana.app/saved-from-ui": "Grafana Cloud", + "grafana.app/updatedBy": "user:cf1q5zu9i0kxsc", + "grafana.app/updatedTimestamp": "2026-04-17T15:23:01Z", + "grafana.app/folderTitle": "icb-test-lat-ICB-GENERATED", + "grafana.app/folderUrl": "/dashboards/f/dfjd968owvfggf/icbtestlaticbgenerated" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "builtIn": true + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Logs", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "cloudwatch", + "version": "v0", + "datasource": { + "name": "efjd96fgdz4sgf" + }, + "spec": { + "expression": "fields @Timestamp, trace_id as exploreTraces, trace_id as panelTraces\n | parse @message '\"body\":\"*\"' as body\n | parse @message '\"res\":{\"statusCode\":*}' as statusCode\n | parse @message '\"severity_text\":\"*\"' as logLevel\n | filter body like /${search_text}/\n | filter ${status_code}\n | filter ${log_level}\n | sort @timestamp desc\n | limit ${limit}", + "logGroups": [ + { + "name": "icb-test-dev-lg" + } + ], + "queryMode": "Logs", + "region": "default", + "statsGroups": [] + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "Transformation", + "group": "organize", + "spec": { + "options": { + "excludeByName": { + "Value": true + }, + "includeByName": {}, + "indexByName": { + "@timestamp": 0, + "body": 3, + "logLevel": 2, + "statusCode": 1 + }, + "renameByName": { + "@timestamp": "Timestamp", + "body": "Body", + "exploreTraces": "Explore Traces", + "logLevel": "Log Level", + "panelTraces": "Panel Traces", + "statusCode": "Status Code", + "traceId": "Trace Id" + } + } + } + }, + { + "kind": "Transformation", + "group": "sortBy", + "spec": { + "options": { + "sort": [ + { + "desc": true, + "field": "Time" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "13.1.0-24556818486", + "spec": { + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Explore Traces" + } + ] + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "scope": "series", + "options": "Explore Traces" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Explore traces", + "url": "/explore?left={\"datasource\":\"dfjdcu10ysykgc\",\"queries\":[{\"queryType\":\"getTrace\",\"query\":\"${__data.fields.exploreTraces}\"}],\"range\":{\"from\":\"now-1h\",\"to\":\"now\"}}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "scope": "series", + "options": "Panel Traces" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": false, + "title": "Panel traces", + "url": "/d/27f71330-d1af-4fde-b2f8-41285dddef6e/icb-grafana-test-logs-and-traces?var-traceId=${__data.fields.panelTraces}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "scope": "series", + "options": "Panel Traces" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "data-links" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "scope": "series", + "options": "Explore Traces" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "data-links" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Traces", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-x-ray-datasource", + "version": "v0", + "datasource": { + "name": "dfjdcu10ysykgc" + }, + "spec": { + "group": { + "GroupARN": "arn:aws:xray:us-east-1:587728158746:group/Default", + "GroupName": "Default", + "InsightsConfiguration": { + "InsightsEnabled": false, + "NotificationsEnabled": false + } + }, + "query": "$traceId", + "queryMode": "X-Ray", + "queryType": "getTrace", + "region": "default" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "13.1.0-24556818486", + "spec": { + "options": { + "cellHeight": "sm", + "showHeader": true + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "traceID" + }, + "properties": [ + { + "id": "custom.width", + "value": 323 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "serviceTags" + }, + "properties": [ + { + "id": "custom.width", + "value": 152 + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 12, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 12, + "width": 24, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "1m", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "ICB Grafana Test Logs & Traces", + "variables": [ + { + "kind": "TextVariable", + "spec": { + "name": "search_text", + "current": { + "text": "", + "value": "" + }, + "query": "", + "label": "Search Text", + "hide": "dontHide", + "skipUrlSync": false + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "status_code", + "query": "[{\"text\":\"N/A\",\"value\":\"!ispresent(statusCode)\"},{\"text\":\"1xx\",\"value\":\"statusCode >= 100 and statusCode < 200\"},{\"text\":\"2xx\",\"value\":\"statusCode >= 200 and statusCode < 300\"},{\"text\":\"3xx\",\"value\":\"statusCode >= 300 and statusCode < 400\"},{\"text\":\"4xx\",\"value\":\"statusCode >= 400 and statusCode < 500\"},{\"text\":\"5xx\",\"value\":\"statusCode >= 500 and statusCode < 600\"}]", + "current": { + "text": "2xx", + "value": "statusCode >= 200 and statusCode < 300" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Status Code", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true, + "valuesFormat": "json" + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "log_level", + "query": "[{\"text\":\"trace\",\"value\":\"logLevel = 'trace'\"},{\"text\":\"debug\",\"value\":\"logLevel = 'debug'\"},{\"text\":\"info\",\"value\":\"logLevel = 'info'\"},{\"text\":\"warn\",\"value\":\"logLevel = 'warn'\"},{\"text\":\"error\",\"value\":\"logLevel = 'error'\"},{\"text\":\"fatal\",\"value\":\"logLevel = 'fatal'\"}]", + "current": { + "text": "info", + "value": "logLevel = 'info'" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Log Level", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true, + "valuesFormat": "json" + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "limit", + "query": "[{\"text\":\"20\",\"value\":20},{\"text\":\"50\",\"value\":50},{\"text\":\"100\",\"value\":100},{\"text\":\"250\",\"value\":250},{\"text\":\"500\",\"value\":500},{\"text\":\"1000\",\"value\":1000}]", + "current": { + "text": "20", + "value": "20" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Limit", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true, + "valuesFormat": "json" + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "traceId", + "current": { + "text": "", + "value": "" + }, + "query": "", + "label": "Trace Id", + "hide": "hideVariable", + "skipUrlSync": false + } + } + ] + } +} diff --git a/src/components/grafana/dashboards/logs-and-traces.ts b/src/components/grafana/dashboards/logs-and-traces.ts index 59260209..98bdcf4b 100644 --- a/src/components/grafana/dashboards/logs-and-traces.ts +++ b/src/components/grafana/dashboards/logs-and-traces.ts @@ -3,8 +3,12 @@ import { GrafanaDashboardBuilder } from './builder'; import { createStatusCodeVariable } from '../variables/status-code'; import { createLimitVariable } from '../variables/limit'; import { createLogLevelVariable } from '../variables/log-level'; -import { createLogsViewPanel } from '../panels/logs'; import { createSearchTextVariable } from '../variables/search-text'; +import { createTraceIdVariable } from '../variables/trace-id'; +import { + createLogsViewPanel, + createTracesViewPanel, +} from '../panels/logs-traces'; export namespace LogsAndTracesDashboard { export type Args = { @@ -28,7 +32,8 @@ export function createLogsAndTracesDashboard( config: LogsAndTracesDashboard.Args, ): GrafanaDashboardBuilder.CreateDashboard { const argsWithDefaults = mergeWithDefaults(defaults, config); - const { title, logsDataSourceName, logGroupName } = argsWithDefaults; + const { title, logsDataSourceName, logGroupName, tracesDataSourceName } = + argsWithDefaults; return new GrafanaDashboardBuilder(config.name) .withConfig(argsWithDefaults.dashboardConfig) @@ -37,11 +42,17 @@ export function createLogsAndTracesDashboard( .addVariable(createStatusCodeVariable()) .addVariable(createLogLevelVariable()) .addVariable(createLimitVariable()) + .addVariable(createTraceIdVariable()) .addPanel( createLogsViewPanel({ logGroupName, dataSourceName: logsDataSourceName, }), ) + .addPanel( + createTracesViewPanel({ + dataSourceName: tracesDataSourceName, + }), + ) .build(); } diff --git a/src/components/grafana/panels/helpers.ts b/src/components/grafana/panels/helpers.ts index aceb92dd..52a0f056 100644 --- a/src/components/grafana/panels/helpers.ts +++ b/src/components/grafana/panels/helpers.ts @@ -1,4 +1,4 @@ -import { Panel, Metric, Transformation } from './types'; +import { Panel, Metric, Target, Transformation } from './types'; const percentageFieldConfig = { unit: 'percent', @@ -141,25 +141,20 @@ export function createTablePanel( title: string, position: Panel.Position, dataSource: string, - logGroupName: string, - expression: string, - transformations: Transformation[], + targets: Target[], + transformations?: Transformation[], + overrides?: any, ): Panel { return { type: 'table', title, gridPos: position, datasource: dataSource, - targets: [ - { - expression, - logGroups: [{ name: logGroupName }], - queryMode: 'Logs', - }, - ], + targets, transformations, fieldConfig: { defaults: {}, + overrides, }, }; } diff --git a/src/components/grafana/panels/logs-traces.ts b/src/components/grafana/panels/logs-traces.ts new file mode 100644 index 00000000..2933383a --- /dev/null +++ b/src/components/grafana/panels/logs-traces.ts @@ -0,0 +1,107 @@ +import { Panel } from './types'; +import { createTablePanel } from './helpers'; + +export function createLogsViewPanel(config: { + logGroupName: string; + dataSourceName: string; +}): Panel { + return createTablePanel( + 'Logs', + { x: 0, y: 0, w: 24, h: 12 }, + config.dataSourceName, + [ + { + expression: `fields @Timestamp, trace_id as traceId + | parse @message '"body":"*"' as body + | parse @message '"res":{"statusCode":*}' as statusCode + | parse @message '"severity_text":"*"' as logLevel + | filter body like /\${search_text}/ + | filter \${status_code} + | filter \${log_level} + | sort @timestamp desc + | limit \${limit}`, + logGroups: [{ name: config.logGroupName }], + queryMode: 'Logs', + }, + ], + [ + { + id: 'organize', + options: { + renameByName: { + statusCode: 'Status Code', + logLevel: 'Log Level', + body: 'Body', + '@timestamp': 'Timestamp', + traceId: 'Trace Id', + }, + indexByName: { + '@timestamp': 0, + statusCode: 1, + logLevel: 2, + body: 3, + }, + excludeByName: { + Value: true, + }, + }, + }, + { + id: 'sortBy', + options: { + sort: [ + { + field: 'Time', + desc: true, + }, + ], + }, + }, + ], + [ + { + matcher: { + id: 'byName', + options: 'traceId', + }, + properties: [ + { + id: 'displayName', + value: 'Traces', + }, + { + id: 'links', + value: [ + { + title: 'Open traces', + url: `/d/\${__dashboard.uid}/\${__dashboard}?var-traceId=\${__data.fields.traceId}`, + }, + ], + }, + { + id: 'custom.cellOptions', + value: { + type: 'data-links', + }, + }, + ], + }, + ], + ); +} + +export function createTracesViewPanel(config: { + dataSourceName: string; +}): Panel { + return createTablePanel( + 'Traces', + { x: 0, y: 0, w: 24, h: 12 }, + config.dataSourceName, + [ + { + query: '$traceId', + queryType: 'getTrace', + }, + ], + ); +} diff --git a/src/components/grafana/panels/logs.ts b/src/components/grafana/panels/logs.ts deleted file mode 100644 index 6bf66357..00000000 --- a/src/components/grafana/panels/logs.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Panel } from './types'; -import { createTablePanel } from './helpers'; - -export function createLogsViewPanel(config: { - logGroupName: string; - dataSourceName: string; -}): Panel { - return createTablePanel( - 'Logs', - { x: 0, y: 0, w: 24, h: 12 }, - config.dataSourceName, - config.logGroupName, - `fields @Timestamp - | parse @message '"body":"*"' as body - | parse @message '"res":{"statusCode":*}' as statusCode - | parse @message '"severity_text":"*"' as logLevel - | filter body like /\${search_text}/ - | filter \${status_code} - | filter \${log_level} - | sort @timestamp desc - | limit \${limit}`, - [ - { - id: 'organize', - options: { - renameByName: { - statusCode: 'Status Code', - logLevel: 'Log Level', - body: 'Body', - '@timestamp': 'Timestamp', - }, - indexByName: { - '@timestamp': 0, - statusCode: 1, - logLevel: 2, - body: 3, - }, - excludeByName: { - Value: true, - }, - }, - }, - { - id: 'sortBy', - options: { - sort: [ - { - field: 'Time', - desc: true, - }, - ], - }, - }, - ], - ); -} diff --git a/src/components/grafana/panels/types.ts b/src/components/grafana/panels/types.ts index 01c58d4a..69bcda29 100644 --- a/src/components/grafana/panels/types.ts +++ b/src/components/grafana/panels/types.ts @@ -3,14 +3,7 @@ export type Panel = { gridPos: Panel.Position; type: string; datasource: string; - targets: { - expr?: string; - expression?: string; - legendFormat?: string; - logGroups?: { name: string }[]; - queryMode?: string; - queryType?: string; - }[]; + targets: Target[]; fieldConfig: { defaults: { unit?: string; @@ -28,6 +21,16 @@ export type Panel = { spanNulls: boolean; }; }; + overrides?: { + matcher: { + id: string; + options: string; + }; + properties: { + id: string; + value: string | { title: string; url: string }[] | { type: string }; + }[]; + }; }; transformations?: Transformation[]; options?: { @@ -52,6 +55,16 @@ export namespace Panel { }; } +export type Target = { + expr?: string; + expression?: string; + legendFormat?: string; + logGroups?: { name: string }[]; + queryMode?: string; + queryType?: string; + query?: string; +}; + export type Metric = { label: string; query: string; diff --git a/src/components/grafana/variables/helpers.ts b/src/components/grafana/variables/helpers.ts index 6ff6a58c..11719622 100644 --- a/src/components/grafana/variables/helpers.ts +++ b/src/components/grafana/variables/helpers.ts @@ -26,10 +26,12 @@ export function createCustomVariable( export function createTextBoxVariable( name: string, label: string, + hide?: string, ): TextBoxVariable { return { type: 'textbox', name, label, + hide, }; } diff --git a/src/components/grafana/variables/trace-id.ts b/src/components/grafana/variables/trace-id.ts new file mode 100644 index 00000000..4ff5eca2 --- /dev/null +++ b/src/components/grafana/variables/trace-id.ts @@ -0,0 +1,5 @@ +import { createTextBoxVariable } from './helpers'; + +export function createTraceIdVariable() { + return createTextBoxVariable('traceId', 'Trace Id', 'hideVariable'); +} diff --git a/src/components/grafana/variables/types.ts b/src/components/grafana/variables/types.ts index cf4c773b..9e3802c9 100644 --- a/src/components/grafana/variables/types.ts +++ b/src/components/grafana/variables/types.ts @@ -16,6 +16,7 @@ export type TextBoxVariable = { type: 'textbox'; name: string; label: string; + hide?: string; }; export type Variable = CustomVariable | TextBoxVariable;