Skip to content

Make log entry shareable

Daniele Rossetti requested to merge drosse/shareable-log-entry into master

What does this MR do and why?

Make single log entry shareable: upon selecting a single log entry, the URL should be updated with filters required to identify the selected log entry (service_name, severity_number, timestamp, trace_id, fingerprint). Accessing that URL directly, should result in the search bar being populated with the associated filters, which should then be passed to the API to fetch the required log.

Changes:

  • Add severity_number filter to logs filtered search and API
  • Update URL query params to appropriate filters when log is selected

Closes Log entry details should be shareable (gitlab-org/opstrace/opstrace#2676 - closed)

Requires: Add timestamp handling to logs (!151476 - merged)

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

  1. No logs selected. ( e.g. URL is /logs?date_range=7d&not%5BseverityName%5D[]=info )

image

  1. Logs selected, URL is updated to include required filters (e.g. /logs?service[]=cartservice&severityNumber[]=13&traceId[]=00000000-0000-0000-0000-000000000000&fingerprint[]=8cc65979a452e61f&timestamp=2024-04-26T11%3A31%3A16.5841779Z)

image

  1. Accessing selected log URL. Logs are fetched with required filters.

image

Video Recording:

demo-pt1

demo-pt2

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 )

diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js
index d33bdd3eff5f..f0991e9677cb 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
 }

 /** ****
@@ -597,17 +595,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) => ({
+        ...l,
+        fingerprint: crypto.randomUUID(),
+      })),
     });
-    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
  • Select/unselect a log and confirm the URL params changes accordingly
  • After selecting a log, try and open the updated URL on a separate tab
  • Confirm filters are applied correctly on the search bar
  • Confirm from console logs that proper params are passed to the fetch logs API ( since the APIis mocked, the data won't actually update)
Edited by Daniele Rossetti

Merge request reports