From f8a9590de59fa59103eaec7dbab90427045ea530 Mon Sep 17 00:00:00 2001 From: omkarK06 <127078473+omkarK06@users.noreply.github.com> Date: Fri, 28 Apr 2023 21:28:12 +0530 Subject: [PATCH] feat: traces UI (#589) * added traces initial plugin and route * added composable useTraces * added trace details UI * add: added api call to get trace spans list * add: added span details sidebar * fix: converted bar sizing from px to % * updated: traces UI * added gantt chart in trace details page * fix: on close of trace details pop up reset span details sidebar data * updated sql query to get traces * updated traces UI 1. Removed select stream 2. Moved service_name and operation_name at top 3. Disabled sql mode 4. Remove add field in table icon from field list 5. change query added group by trace_id 6. Removed time interval 6. changed histogram chart to scatter * add: updated custom time range selection in trace details chart * fix: type errors and resplaced _timestamp with dynamic timestamp key from store * fix: removed unused files --- web/src/assets/images/left_nav/traces.svg | 3 + web/src/composables/shared/router.ts | 9 + web/src/composables/useTraces.ts | 158 ++ web/src/layouts/MainLayout.vue | 5 + web/src/locales/languages/en.json | 1 + web/src/plugins/index.ts | 2 + web/src/plugins/traces/AppCollapse.vue | 55 + web/src/plugins/traces/DetailTable.vue | 398 +++++ web/src/plugins/traces/Index.vue | 1460 +++++++++++++++++ web/src/plugins/traces/IndexList.vue | 601 +++++++ web/src/plugins/traces/QueryEditor.vue | 332 ++++ web/src/plugins/traces/SearchBar.vue | 431 +++++ web/src/plugins/traces/SearchResult.vue | 512 ++++++ web/src/plugins/traces/SpanBlock.vue | 203 +++ web/src/plugins/traces/SpanRenderer.vue | 97 ++ web/src/plugins/traces/SyntaxGuide.vue | 227 +++ web/src/plugins/traces/TraceChart.vue | 215 +++ web/src/plugins/traces/TraceDetails.vue | 546 ++++++ .../plugins/traces/TraceDetailsSidebar.vue | 111 ++ web/src/plugins/traces/TraceHeader.vue | 105 ++ web/src/plugins/traces/TraceTree.vue | 133 ++ web/src/services/stream.ts | 21 +- web/src/utils/zincutils.ts | 4 + web/src/views/AppTraces.vue | 21 + 24 files changed, 5643 insertions(+), 7 deletions(-) create mode 100644 web/src/assets/images/left_nav/traces.svg create mode 100644 web/src/composables/useTraces.ts create mode 100644 web/src/plugins/traces/AppCollapse.vue create mode 100644 web/src/plugins/traces/DetailTable.vue create mode 100644 web/src/plugins/traces/Index.vue create mode 100644 web/src/plugins/traces/IndexList.vue create mode 100644 web/src/plugins/traces/QueryEditor.vue create mode 100644 web/src/plugins/traces/SearchBar.vue create mode 100644 web/src/plugins/traces/SearchResult.vue create mode 100644 web/src/plugins/traces/SpanBlock.vue create mode 100644 web/src/plugins/traces/SpanRenderer.vue create mode 100644 web/src/plugins/traces/SyntaxGuide.vue create mode 100644 web/src/plugins/traces/TraceChart.vue create mode 100644 web/src/plugins/traces/TraceDetails.vue create mode 100644 web/src/plugins/traces/TraceDetailsSidebar.vue create mode 100644 web/src/plugins/traces/TraceHeader.vue create mode 100644 web/src/plugins/traces/TraceTree.vue create mode 100644 web/src/views/AppTraces.vue diff --git a/web/src/assets/images/left_nav/traces.svg b/web/src/assets/images/left_nav/traces.svg new file mode 100644 index 0000000000..a96aa0054d --- /dev/null +++ b/web/src/assets/images/left_nav/traces.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/composables/shared/router.ts b/web/src/composables/shared/router.ts index 5c651a9f1c..6709d0909b 100644 --- a/web/src/composables/shared/router.ts +++ b/web/src/composables/shared/router.ts @@ -21,6 +21,7 @@ import AddPanel from "@/views/Dashboards/addPanel/AddPanel.vue"; import MemberSubscription from "@/views/MemberSubscription.vue"; import Search from "@/views/Search.vue"; import AppMetrics from "@/views/AppMetrics.vue"; +import AppTraces from "@/views/AppTraces.vue"; import LogStream from "@/views/LogStream.vue"; import { FunctionList, @@ -71,6 +72,14 @@ const useRoutes = () => { keepAlive: true, }, }, + { + path: "traces", + name: "traces", + component: AppTraces, + meta: { + keepAlive: true, + }, + }, { path: "logstreams", name: "logstreams", diff --git a/web/src/composables/useTraces.ts b/web/src/composables/useTraces.ts new file mode 100644 index 0000000000..3e91159c0d --- /dev/null +++ b/web/src/composables/useTraces.ts @@ -0,0 +1,158 @@ +// Copyright 2022 Zinc Labs Inc. and Contributors + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http:www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { reactive } from "vue"; +import { useLocalTraceFilterField } from "@/utils/zincutils"; + +const defaultObject = { + organizationIdetifier: "", + runQuery: false, + loading: false, + + config: { + splitterModel: 20, + lastSplitterPosition: 0, + splitterLimit: [0, 40], + refreshTimes: [ + [ + { label: "5 sec", value: 5 }, + { label: "1 min", value: 60 }, + { label: "1 hr", value: 3600 }, + ], + [ + { label: "10 sec", value: 10 }, + { label: "5 min", value: 300 }, + { label: "2 hr", value: 7200 }, + ], + [ + { label: "15 sec", value: 15 }, + { label: "15 min", value: 900 }, + { label: "1 day", value: 86400 }, + ], + [ + { label: "30 sec", value: 30 }, + { label: "30 min", value: 1800 }, + ], + ], + }, + meta: { + refreshInterval: "0", + refreshIntervalLabel: "Off", + showFields: true, + showQuery: true, + showHistogram: true, + showDetailTab: false, + showTraceDetails: false, + sqlMode: false, + resultGrid: { + wrapCells: false, + manualRemoveFields: false, + rowsPerPage: 150, + chartInterval: "1 second", + chartKeyFormat: "HH:mm:ss", + navigation: { + currentRowIndex: 0, + }, + }, + scrollInfo: {}, + }, + data: { + query: "", + parsedQuery: {}, + errorMsg: "", + errorCode: 0, + additionalErrorMsg: "", + stream: { + streamLists: [], + selectedStream: { label: "", value: "" }, + selectedStreamFields: [], + selectedFields: [], + filterField: "", + addToFilter: "", + functions: [], + }, + resultGrid: { + currentDateTime: new Date(), + currentPage: 0, + columns: [], + }, + transforms: [], + queryResults: [], + sortedQueryResults: [], + streamResults: [], + histogram: {}, + editorValue: "", + datetime: { + tab: "relative", + relative: { + period: { + label: "Minutes", + value: "Minutes", + }, + value: 15, + }, + absolute: { + date: { + from: "", + to: "", + }, + startTime: "00:00", + endTime: "23:59", + }, + }, + searchAround: { + indexTimestamp: 0, + size: 10, + histogramHide: false, + }, + traceDetails: { + traceId: "", + spanList: [], + loading: false, + selectedSpanId: null, + showSpanDetails: false, + }, + }, +}; + +// let searchObj = reactive(structuredClone(defaultObject)); +// let localLogsObj:any = useLocalLogsObj(); +// let searchObj = {}; +// if (typeof localLogsObj === "object") { +// searchObj = localLogsObj.value; +// } else { +let searchObj = reactive(Object.assign({}, defaultObject)); +// } + +const useLogs = () => { + const resetSearchObj = () => { + // delete searchObj.data; + searchObj = reactive(Object.assign({}, defaultObject)); + }; + const updatedLocalLogFilterField = (): void => { + const identifier: string = searchObj.organizationIdetifier || "default"; + const selectedFields: any = + useLocalTraceFilterField()?.value != null + ? useLocalTraceFilterField()?.value + : {}; + selectedFields[ + `${identifier}_${searchObj.data.stream.selectedStream.value}` + ] = searchObj.data.stream.selectedFields; + useLocalTraceFilterField(selectedFields); + }; + + return { searchObj, resetSearchObj, updatedLocalLogFilterField }; +}; + +export default useLogs; diff --git a/web/src/layouts/MainLayout.vue b/web/src/layouts/MainLayout.vue index 01a1e34a05..b1ba3a66cc 100644 --- a/web/src/layouts/MainLayout.vue +++ b/web/src/layouts/MainLayout.vue @@ -348,6 +348,11 @@ export default defineComponent({ icon: "img:" + getImageURL("images/left_nav/metrics.svg"), link: "/metrics", }, + { + title: t("menu.traces"), + icon: "img:" + getImageURL("images/left_nav/traces.svg"), + link: "/traces", + }, { title: t("menu.dashboard"), icon: "img:" + getImageURL("images/left_nav/dashb2.svg"), diff --git a/web/src/locales/languages/en.json b/web/src/locales/languages/en.json index ede302c43c..a5c1db9da2 100644 --- a/web/src/locales/languages/en.json +++ b/web/src/locales/languages/en.json @@ -29,6 +29,7 @@ "user": "Users", "index": "Streams", "search": "Logs", + "traces": "Traces", "template": "Template", "signOut": "Sign Out", "account": "Account", diff --git a/web/src/plugins/index.ts b/web/src/plugins/index.ts index 1ba5fa9cd1..fae8dd2557 100644 --- a/web/src/plugins/index.ts +++ b/web/src/plugins/index.ts @@ -14,10 +14,12 @@ import Logs from "./logs/Index.vue"; import AppMetrics from "./metrics/Index.vue"; +import AppTraces from "./traces/Index.vue"; export default { install: (app: any, options: any) => { app.component("zinc-logs", Logs); app.component("zinc-metrics", AppMetrics); + app.component("zinc-traces", AppTraces); }, }; diff --git a/web/src/plugins/traces/AppCollapse.vue b/web/src/plugins/traces/AppCollapse.vue new file mode 100644 index 0000000000..60dc2106d2 --- /dev/null +++ b/web/src/plugins/traces/AppCollapse.vue @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/DetailTable.vue b/web/src/plugins/traces/DetailTable.vue new file mode 100644 index 0000000000..862f25acd9 --- /dev/null +++ b/web/src/plugins/traces/DetailTable.vue @@ -0,0 +1,398 @@ + + + + + + + + + {{ t("search.rowDetail") }} + + + + + + + + + + + + + + + + + + + + + + + + + + + No data available. + + + + + + {{ t("search.sourceName") }} + + + {{ t("search.sourceValue") }} + + + + + {{ value }} + + + + + + Inlcude Search Term + + + + + + Exclude Search Term + + + + + {{ key }} + + + + + + + + + + + + {{ rowData }} + + + + + + + + + + + + + No of Records: + + + + + + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/Index.vue b/web/src/plugins/traces/Index.vue new file mode 100644 index 0000000000..eeb5f50a0d --- /dev/null +++ b/web/src/plugins/traces/Index.vue @@ -0,0 +1,1460 @@ + + + + + + + + + + + + + + + + + + + + + + Result not found. + + + + Click here + to configure a full text search field to the stream. + + + {{ + searchObj.data.additionalErrorMsg + }} + + + + + No stream selected. + + + + No result found. + + + + + + + + + + + + + {{ + searchObj.data.errorMsg + }} + + + + + + + + + diff --git a/web/src/plugins/traces/IndexList.vue b/web/src/plugins/traces/IndexList.vue new file mode 100644 index 0000000000..2683643add --- /dev/null +++ b/web/src/plugins/traces/IndexList.vue @@ -0,0 +1,601 @@ + + + + + + + + + + + + + {{ props.row.name }} + + + + + + openFilterCreator(event, props.row)" + > + + + + {{ props.row.name }} + + + + + + + + + + + + + + No values found + + + + + + + {{ value.key }} + + + {{ value.count }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/QueryEditor.vue b/web/src/plugins/traces/QueryEditor.vue new file mode 100644 index 0000000000..6fa6d8d2fa --- /dev/null +++ b/web/src/plugins/traces/QueryEditor.vue @@ -0,0 +1,332 @@ + + + + + + + + + diff --git a/web/src/plugins/traces/SearchBar.vue b/web/src/plugins/traces/SearchBar.vue new file mode 100644 index 0000000000..0b59dd2916 --- /dev/null +++ b/web/src/plugins/traces/SearchBar.vue @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + Run query + + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/SearchResult.vue b/web/src/plugins/traces/SearchResult.vue new file mode 100644 index 0000000000..95994057f1 --- /dev/null +++ b/web/src/plugins/traces/SearchResult.vue @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + {{ col.label }} + + + + {{ col.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/SpanBlock.vue b/web/src/plugins/traces/SpanBlock.vue new file mode 100644 index 0000000000..906069681b --- /dev/null +++ b/web/src/plugins/traces/SpanBlock.vue @@ -0,0 +1,203 @@ + + + + + + + + {{ formatTimeWithSuffix(span.durationMs) }} + + + + + + + + + diff --git a/web/src/plugins/traces/SpanRenderer.vue b/web/src/plugins/traces/SpanRenderer.vue new file mode 100644 index 0000000000..50e54d86cc --- /dev/null +++ b/web/src/plugins/traces/SpanRenderer.vue @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/SyntaxGuide.vue b/web/src/plugins/traces/SyntaxGuide.vue new file mode 100644 index 0000000000..10b5ce508c --- /dev/null +++ b/web/src/plugins/traces/SyntaxGuide.vue @@ -0,0 +1,227 @@ + + + + + + + + Syntax Guide + + + + + + + + For full text search of value 'error' use + match_all('error') in query editor + + + For case-insensitive full text search of value 'error' use + match_all_ignore_case('error') + + + For column search of value 'error' use + str_match(fieldname, 'error') + + + To search value 200 for code column use + code=200 + + + To search value 'stderr' for stream column use + stream='stderr' + + + To search and use query function extract_ip on cloumn + log use + extract_ip(log) | code=200 + + + For additional examples, + click here. + + + + + + + + + Syntax Guide: SQL Mode + + + + + + + + For full text search of value 'error' use + SELECT * FROM stream WHERE match_all('error') + + + For case-insensitive full text search of value 'error' use + SELECT * FROM stream WHERE + match_all_ignore_case('error') + + + For column search of value 'error' use + SELECT * FROM stream WHERE + str_match(fieldname, 'error') + + + To search value 200 for code column use + SELECT * FROM stream WHERE code=200 + + + To search value 'stderr' for stream column use + SELECT * FROM stream WHERE stream='stderr' + + + To search and use query function extract_ip on cloumn + log use + SELECT extract_ip(log) FROM stream WHERE + code=200 + + + For additional examples, + click here. + + + + + + + + + + + + + diff --git a/web/src/plugins/traces/TraceChart.vue b/web/src/plugins/traces/TraceChart.vue new file mode 100644 index 0000000000..ea6350c337 --- /dev/null +++ b/web/src/plugins/traces/TraceChart.vue @@ -0,0 +1,215 @@ + + + + + + + diff --git a/web/src/plugins/traces/TraceDetails.vue b/web/src/plugins/traces/TraceDetails.vue new file mode 100644 index 0000000000..fcd899db13 --- /dev/null +++ b/web/src/plugins/traces/TraceDetails.vue @@ -0,0 +1,546 @@ + + + + + + + + {{ traceTree[0]["operationName"] }} + + Spans: {{ spanList.length - 1 }} + + + + + + + + + + + + + + + + + + + + + + + + + Getting trace data + + + + + + + + diff --git a/web/src/plugins/traces/TraceDetailsSidebar.vue b/web/src/plugins/traces/TraceDetailsSidebar.vue new file mode 100644 index 0000000000..86ec6cec3d --- /dev/null +++ b/web/src/plugins/traces/TraceDetailsSidebar.vue @@ -0,0 +1,111 @@ + + + + + + Span Details + + + + + + {{ span.operation_name }} + + + {{ span.service_name }} + + + + + + + + + + + {{ key }} + {{ span[key] }} + + + + + + + + + + + diff --git a/web/src/plugins/traces/TraceHeader.vue b/web/src/plugins/traces/TraceHeader.vue new file mode 100644 index 0000000000..0cb1fe0189 --- /dev/null +++ b/web/src/plugins/traces/TraceHeader.vue @@ -0,0 +1,105 @@ + + + + + + Operation Name + + + + {{ baseTracePosition.tics[0].label }} + + + {{ baseTracePosition.tics[1].label }} + + + {{ baseTracePosition.tics[2].label }} + + + {{ baseTracePosition.tics[3].label }} + {{ baseTracePosition.tics[4].label }} + + + + + + + + + diff --git a/web/src/plugins/traces/TraceTree.vue b/web/src/plugins/traces/TraceTree.vue new file mode 100644 index 0000000000..a94b7706fd --- /dev/null +++ b/web/src/plugins/traces/TraceTree.vue @@ -0,0 +1,133 @@ + + + + + + + + + + + + {{ span.operationName }} + + + + + + + + + + diff --git a/web/src/services/stream.ts b/web/src/services/stream.ts index 3b7e03407e..5a91a8ba32 100644 --- a/web/src/services/stream.ts +++ b/web/src/services/stream.ts @@ -62,18 +62,25 @@ const stream = { start_time, end_time, query_context, - query_fn + query_fn, + type, }: any) => { const fieldsString = fields.join(","); - let url = `/api/${org_identifier}/${stream_name}/_values?fields=${fieldsString}&size=${size}&start_time=${start_time}&end_time=${end_time}&sql=${query_context}`; - if (query_fn.trim() != "") { - url = url + `&query_fn=${query_fn}`; - } + let url = `/api/${org_identifier}/${stream_name}/_values?fields=${fieldsString}&size=${size}&start_time=${start_time}&end_time=${end_time}`; + if (query_context) url = url + `&sql=${query_context}`; + if (query_fn?.trim()) url = url + `&query_fn=${query_fn}`; + if (type) url += "&type=" + type; return http().get(url); }, - delete: (org_identifier: string, stream_name: string, stream_type: string) => { - return http().delete(`/api/${org_identifier}/${stream_name}?type=${stream_type}`); + delete: ( + org_identifier: string, + stream_name: string, + stream_type: string + ) => { + return http().delete( + `/api/${org_identifier}/${stream_name}?type=${stream_type}` + ); }, }; diff --git a/web/src/utils/zincutils.ts b/web/src/utils/zincutils.ts index 07e8fc1e62..4f8841e6f8 100644 --- a/web/src/utils/zincutils.ts +++ b/web/src/utils/zincutils.ts @@ -172,6 +172,10 @@ export const useLocalLogFilterField = (val = "", isDelete = false) => { return useLocalStorage("logFilterField", val, isDelete, true); }; +export const useLocalTraceFilterField = (val = "", isDelete = false) => { + return useLocalStorage("traceFilterField", val, isDelete, true); +}; + export const useLocalUserInfo = (val = "", isDelete = false) => { const userInfo: any = useLocalStorage("userInfo", val, isDelete); return userInfo.value; diff --git a/web/src/views/AppTraces.vue b/web/src/views/AppTraces.vue new file mode 100644 index 0000000000..1937d400d0 --- /dev/null +++ b/web/src/views/AppTraces.vue @@ -0,0 +1,21 @@ + + + + + + + +
{{ key }}
{{ rowData }}