Add search to Logs list
What does this MR do and why?
Adds capability to search logs by different attributes
ObservabilityClient changes:
- Reorganised the file and moved things around to give it a bit more structure ( next step is to split it into different files as it's growing a bit large)
- Made some functions more generic so that it can be shared and reused
- Add support to fetch logs with attributes filters to the ObservabilityClient ( reusing most of the existing logic )
UI changes:
- Add
attribute_search_token
component: simple token component withname=value
placeholder - Add
attributes_filters
toLogsFilteredSearch
, backed byFilteredSearch
- Handle attributes filters from LogsList
I know this MR looks quite big, as there are quite a lot of tests added, and moving things around also resulted in quite an hard to read diff. I've split this in multiple-commits and added comments to make reviewing easier. If it's still hard to review I can try and split into multiple MRs, and separate between client changes and UI changes. I didn't do it at first cause I'm not sure it's gonna make things that much better, but I'm open to it
Closes Search logs by different criterias (attributes,... (gitlab-org/opstrace/opstrace#2646 - closed)
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
How to set up and validate locally
- Prerequisites: be logged in and running GDK with Ultimate license
- Enable
:observability_logs
feature flag
Apply patch to load mocks ( copy the patch content below and run in your terminal: pbpaste | git apply
)
pbpaste | git apply
)diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js
index ab858eb94d22..01e8b12580fa 100644
--- a/app/assets/javascripts/observability/client.js
+++ b/app/assets/javascripts/observability/client.js
@@ -1,3 +1,4 @@
+/* eslint-disable @gitlab/require-i18n-strings */
import { isValidDate } from '~/lib/utils/datetime_utility';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import axios from '~/lib/utils/axios_utils';
@@ -15,15 +16,17 @@ function reportErrorAndThrow(e) {
* Provisioning API
*
* ***** */
+function mockReturnDataWithDelay(data) {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(data), 1000);
+ });
+}
// Provisioning API spec: https://gitlab.com/gitlab-org/opstrace/opstrace/-/blob/main/provisioning-api/pkg/provisioningapi/routes.go#L59
async function enableObservability(provisioningUrl) {
try {
- // Note: axios.put(url, undefined, {withCredentials: true}) does not send cookies properly, so need to use the API below for the correct behaviour
- return await axios(provisioningUrl, {
- method: 'put',
- withCredentials: true,
- });
+ console.log('[DEBUG] Enabling Observability');
+ return mockReturnDataWithDelay();
} catch (e) {
return reportErrorAndThrow(e);
}
@@ -32,19 +35,14 @@ async function enableObservability(provisioningUrl) {
// Provisioning API spec: https://gitlab.com/gitlab-org/opstrace/opstrace/-/blob/main/provisioning-api/pkg/provisioningapi/routes.go#L37
async function isObservabilityEnabled(provisioningUrl) {
try {
- const { data } = await axios.get(provisioningUrl, { withCredentials: true });
- if (data && data.status) {
- // we currently ignore the 'status' payload and just check if the request was successful
- // We might improve this as part of https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2315
- return true;
- }
+ console.log('[DEBUG] Checking Observability provisioning');
+ return mockReturnDataWithDelay(true);
} catch (e) {
if (e.response.status === 404) {
return false;
}
return reportErrorAndThrow(e);
}
- return reportErrorAndThrow(new Error('Failed to check provisioning')); // eslint-disable-line @gitlab/require-i18n-strings
}
/** ****
@@ -592,17 +590,158 @@ export async function fetchLogs(logsSearchUrl, { pageToken, pageSize, filters =
if (pageSize) {
params.append('page_size', pageSize);
}
- const { data } = await axios.get(logsSearchUrl, {
- withCredentials: true,
- params,
+
+ console.log(`[DEBUG] Fetching logs with params: ${params.toString()}`);
+
+ const data = [
+ {
+ timestamp: '2024-02-19T16:10:15.4433398Z',
+ trace_id: ']?4ft^T\u0011\ufffd\u0013\ufffd6\ufffdbj\ufffd',
+ span_id: "'\u0013\ufffd\ufffd_\ufffd\ufffd\ufffd",
+ trace_flags: 1,
+ severity_text: 'Information',
+ severity_number: 9,
+ service_name: 'cartservice',
+ body:
+ 'AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity} AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity} AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity} AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity} AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity} AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity} ',
+ resource_attributes: {
+ 'container.id': 'ae165f3300bf7da7cc83b360e955683f31f959268241895747f23ad165014005',
+ 'k8s.deployment.name': 'otel-demo-cartservice',
+ 'k8s.namespace.name': 'otel-demo-app',
+ 'k8s.node.name': 'opstrace-worker2',
+ 'k8s.pod.ip': '192.168.62.215',
+ 'k8s.pod.name': 'otel-demo-cartservice-6dcc867f5f-pcm68',
+ 'k8s.pod.start_time': '2024-02-12T13:32:12Z',
+ 'k8s.pod.uid': 'bb0991d5-d2f9-486d-aed6-fb431da28bcb',
+ 'service.name': 'cartservice',
+ 'service.namespace': 'opentelemetry-demo',
+ 'telemetry.sdk.language': 'dotnet',
+ 'telemetry.sdk.name': 'opentelemetry',
+ 'telemetry.sdk.version': '1.6.0',
+ },
+ log_attributes: {
+ productId: 'LS4PSXUNUM',
+ quantity: '5',
+ userId: '5e22f0f6-cf41-11ee-a97f-768346730ebf',
+ },
+ },
+ {
+ timestamp: '2024-02-19T16:10:15.4488072Z',
+ trace_id: ']?4ft^T\u0011\ufffd\u0013\ufffd6\ufffdbj\ufffd',
+ span_id: '\u0015_\ufffd z\ufffd\ufffd\ufffd',
+ trace_flags: 1,
+ severity_text: 'Information',
+ severity_number: 9,
+ service_name: 'cartservice',
+ body: 'GetCartAsync called with userId={userId}',
+ resource_attributes: {
+ 'container.id': 'ae165f3300bf7da7cc83b360e955683f31f959268241895747f23ad165014005',
+ 'k8s.deployment.name': 'otel-demo-cartservice',
+ 'k8s.namespace.name': 'otel-demo-app',
+ 'k8s.node.name': 'opstrace-worker2',
+ 'k8s.pod.ip': '192.168.62.215',
+ 'k8s.pod.name': 'otel-demo-cartservice-6dcc867f5f-pcm68',
+ 'k8s.pod.start_time': '2024-02-12T13:32:12Z',
+ 'k8s.pod.uid': 'bb0991d5-d2f9-486d-aed6-fb431da28bcb',
+ 'service.name': 'cartservice',
+ 'service.namespace': 'opentelemetry-demo',
+ 'telemetry.sdk.language': 'dotnet',
+ 'telemetry.sdk.name': 'opentelemetry',
+ 'telemetry.sdk.version': '1.6.0',
+ },
+ log_attributes: { userId: '5e22f0f6-cf41-11ee-a97f-768346730ebf' },
+ },
+ {
+ timestamp: '2024-02-19T16:10:18.9380111Z',
+ trace_id: '\ufffd\u000cbi\ufffd\u001c\ufffd\ufffd,4f\ufffd!\ufffd\ufffd\ufffd',
+ span_id: '\ufffdv\ufffd(\ufffdyD\ufffd',
+ trace_flags: 1,
+ severity_text: 'Information',
+ severity_number: 9,
+ service_name: 'cartservice',
+ body: 'GetCartAsync called with userId={userId}',
+ resource_attributes: {
+ 'container.id': 'ae165f3300bf7da7cc83b360e955683f31f959268241895747f23ad165014005',
+ 'k8s.deployment.name': 'otel-demo-cartservice',
+ 'k8s.namespace.name': 'otel-demo-app',
+ 'k8s.node.name': 'opstrace-worker2',
+ 'k8s.pod.ip': '192.168.62.215',
+ 'k8s.pod.name': 'otel-demo-cartservice-6dcc867f5f-pcm68',
+ 'k8s.pod.start_time': '2024-02-12T13:32:12Z',
+ 'k8s.pod.uid': 'bb0991d5-d2f9-486d-aed6-fb431da28bcb',
+ 'service.name': 'cartservice',
+ 'service.namespace': 'opentelemetry-demo',
+ 'telemetry.sdk.language': 'dotnet',
+ 'telemetry.sdk.name': 'opentelemetry',
+ 'telemetry.sdk.version': '1.6.0',
+ },
+ log_attributes: { userId: '' },
+ },
+ {
+ timestamp: '2024-02-19T16:10:19.7969402Z',
+ trace_id: '\ufffdq\ufffd7\ufffd\ufffd\ufffd\ufffd ]2M~7{\ufffd',
+ span_id: '\u0018H\ufffd\ufffd\ufffd.L\ufffd',
+ trace_flags: 1,
+ severity_text: 'Information',
+ severity_number: 9,
+ service_name: 'cartservice',
+ body:
+ 'AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}',
+ resource_attributes: {
+ 'container.id': 'ae165f3300bf7da7cc83b360e955683f31f959268241895747f23ad165014005',
+ 'k8s.deployment.name': 'otel-demo-cartservice',
+ 'k8s.namespace.name': 'otel-demo-app',
+ 'k8s.node.name': 'opstrace-worker2',
+ 'k8s.pod.ip': '192.168.62.215',
+ 'k8s.pod.name': 'otel-demo-cartservice-6dcc867f5f-pcm68',
+ 'k8s.pod.start_time': '2024-02-12T13:32:12Z',
+ 'k8s.pod.uid': 'bb0991d5-d2f9-486d-aed6-fb431da28bcb',
+ 'service.name': 'cartservice',
+ 'service.namespace': 'opentelemetry-demo',
+ 'telemetry.sdk.language': 'dotnet',
+ 'telemetry.sdk.name': 'opentelemetry',
+ 'telemetry.sdk.version': '1.6.0',
+ },
+ log_attributes: {
+ productId: '66VCHSJNUP',
+ quantity: '1',
+ userId: '60bc5708-cf41-11ee-a97f-768346730ebf',
+ },
+ },
+ {
+ timestamp: '2024-02-19T16:10:19.8003226Z',
+ trace_id: '\ufffdq\ufffd7\ufffd\ufffd\ufffd\ufffd ]2M~7{\ufffd',
+ span_id: 'G\u0006\ufffd`B\r\ufffd\ufffd',
+ trace_flags: 1,
+ severity_text: 'Information',
+ severity_number: 9,
+ service_name: 'cartservice',
+ body: 'GetCartAsync called with userId={userId}',
+ resource_attributes: {
+ 'container.id': 'ae165f3300bf7da7cc83b360e955683f31f959268241895747f23ad165014005',
+ 'k8s.deployment.name': 'otel-demo-cartservice',
+ 'k8s.namespace.name': 'otel-demo-app',
+ 'k8s.node.name': 'opstrace-worker2',
+ 'k8s.pod.ip': '192.168.62.215',
+ 'k8s.pod.name': 'otel-demo-cartservice-6dcc867f5f-pcm68',
+ 'k8s.pod.start_time': '2024-02-12T13:32:12Z',
+ 'k8s.pod.uid': 'bb0991d5-d2f9-486d-aed6-fb431da28bcb',
+ 'service.name': 'cartservice',
+ 'service.namespace': 'opentelemetry-demo',
+ 'telemetry.sdk.language': 'dotnet',
+ 'telemetry.sdk.name': 'opentelemetry',
+ 'telemetry.sdk.version': '1.6.0',
+ },
+ log_attributes: { userId: '60bc5708-cf41-11ee-a97f-768346730ebf' },
+ },
+ ];
+
+ return mockReturnDataWithDelay({
+ logs: [...data, ...data, ...data, ...data, ...data, ...data, ...data].map((l, i) => ({
+ ...l,
+ fingerprint: `random-log-${i}`,
+ })),
});
- if (!Array.isArray(data.results)) {
- throw new Error('logs are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
- }
- return {
- logs: data.results,
- nextPageToken: data.next_page_token,
- };
} catch (e) {
return reportErrorAndThrow(e);
}
diff --git a/app/assets/javascripts/observability/components/observability_container.vue b/app/assets/javascripts/observability/components/observability_container.vue
index d0902505ca73..f6cbf7ee771f 100644
--- a/app/assets/javascripts/observability/components/observability_container.vue
+++ b/app/assets/javascripts/observability/components/observability_container.vue
@@ -27,12 +27,12 @@ export default {
// TODO: Improve local GDK dev experience with tracing https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2308
// Uncomment the lines below to to test this locally
- // setTimeout(() => {
- // this.messageHandler({
- // data: { type: 'AUTH_COMPLETION', status: 'success' },
- // origin: new URL(this.apiConfig.oauthUrl).origin,
- // });
- // }, 2000);
+ setTimeout(() => {
+ this.messageHandler({
+ data: { type: 'AUTH_COMPLETION', status: 'success' },
+ origin: new URL(this.apiConfig.oauthUrl).origin,
+ });
+ }, 2000);
},
destroyed() {
window.removeEventListener('message', this.messageHandler);
- Go to https://local.gitlab.com:3443/flightjs/Flight/-/logs
- Play around with the filters and check that the correct params are used in the API calls (logged in the console, as the API are mocked locally)