summaryrefslogtreecommitdiff
path: root/src/frontend/utils
diff options
context:
space:
mode:
authorSaumit <justsaumit@protonmail.com>2025-09-27 02:14:26 +0530
committerSaumit <justsaumit@protonmail.com>2025-09-27 02:14:26 +0530
commit82e03978b89938219958032efb1448cc76baa181 (patch)
tree626f3e54d52ecd49be0ed3bee30abacc0453d081 /src/frontend/utils
Initial snapshot - OpenTelemetry demo 2.1.3 -f
Diffstat (limited to 'src/frontend/utils')
-rw-r--r--src/frontend/utils/Cypress.ts7
-rw-r--r--src/frontend/utils/Request.ts34
-rw-r--r--src/frontend/utils/enums/AttributeNames.ts6
-rw-r--r--src/frontend/utils/enums/CypressFields.ts28
-rw-r--r--src/frontend/utils/imageLoader.js20
-rw-r--r--src/frontend/utils/telemetry/FrontendTracer.ts72
-rw-r--r--src/frontend/utils/telemetry/Instrumentation.js41
-rw-r--r--src/frontend/utils/telemetry/InstrumentationMiddleware.ts40
-rw-r--r--src/frontend/utils/telemetry/SessionIdProcessor.ts27
9 files changed, 275 insertions, 0 deletions
diff --git a/src/frontend/utils/Cypress.ts b/src/frontend/utils/Cypress.ts
new file mode 100644
index 0000000..3e673f4
--- /dev/null
+++ b/src/frontend/utils/Cypress.ts
@@ -0,0 +1,7 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+import { CypressFields } from './enums/CypressFields';
+
+export const getElementByField = (field: CypressFields, context: Cypress.Chainable = cy) =>
+ context.get(`[data-cy="${field}"]`);
diff --git a/src/frontend/utils/Request.ts b/src/frontend/utils/Request.ts
new file mode 100644
index 0000000..b0ff20e
--- /dev/null
+++ b/src/frontend/utils/Request.ts
@@ -0,0 +1,34 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+interface IRequestParams {
+ url: string;
+ body?: object;
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
+ queryParams?: Record<string, any>;
+ headers?: Record<string, string>;
+}
+
+const request = async <T>({
+ url = '',
+ method = 'GET',
+ body,
+ queryParams = {},
+ headers = {
+ 'content-type': 'application/json',
+ },
+}: IRequestParams): Promise<T> => {
+ const response = await fetch(`${url}?${new URLSearchParams(queryParams).toString()}`, {
+ method,
+ body: body ? JSON.stringify(body) : undefined,
+ headers,
+ });
+
+ const responseText = await response.text();
+
+ if (!!responseText) return JSON.parse(responseText);
+
+ return undefined as unknown as T;
+};
+
+export default request;
diff --git a/src/frontend/utils/enums/AttributeNames.ts b/src/frontend/utils/enums/AttributeNames.ts
new file mode 100644
index 0000000..e0820ba
--- /dev/null
+++ b/src/frontend/utils/enums/AttributeNames.ts
@@ -0,0 +1,6 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+export enum AttributeNames {
+ SESSION_ID = 'session.id'
+}
diff --git a/src/frontend/utils/enums/CypressFields.ts b/src/frontend/utils/enums/CypressFields.ts
new file mode 100644
index 0000000..be85b82
--- /dev/null
+++ b/src/frontend/utils/enums/CypressFields.ts
@@ -0,0 +1,28 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+export enum CypressFields {
+ Ad = 'ad',
+ CartDropdown = 'cart-dropdown',
+ CartDropdownItem = 'cart-dropdown-item',
+ CartDropdownItemQuantity = 'cart-dropdown-item-quantity',
+ CartGoToShopping = 'cart-go-to-shopping',
+ CartIcon = 'cart-icon',
+ CartItemCount = 'cart-item-count',
+ CheckoutPlaceOrder = 'checkout-place-order',
+ CheckoutItem = 'checkout-item',
+ CurrencySwitcher = 'currency-switcher',
+ SessionId = 'session-id',
+ ProductCard = 'product-card',
+ ProductList = 'product-list',
+ ProductPrice = 'product-price',
+ RecommendationList = 'recommendation-list',
+ HomePage = 'home-page',
+ ProductDetail = 'product-detail',
+ HotProducts = 'hot-products',
+ ProductPicture = 'product-picture',
+ ProductName = 'product-name',
+ ProductDescription = 'product-description',
+ ProductQuantity = 'product-quantity',
+ ProductAddToCart = 'product-add-to-cart',
+}
diff --git a/src/frontend/utils/imageLoader.js b/src/frontend/utils/imageLoader.js
new file mode 100644
index 0000000..3718b0a
--- /dev/null
+++ b/src/frontend/utils/imageLoader.js
@@ -0,0 +1,20 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * We connect to image-provider through the envoy proxy, straight from the browser, for this we need to know the current hostname and port.
+ * During building and serverside rendering, these are undefined so we use some conditionals and default values.
+ */
+let hostname = "localhost";
+let port = 8080;
+let protocol = "http";
+
+if (typeof window !== "undefined" && window.location) {
+ hostname = window.location.hostname;
+ port = window.location.port ? parseInt(window.location.port, 10) : (window.location.protocol === "https:" ? 443 : 80);
+ protocol = window.location.protocol.slice(0, -1); // Remove trailing ':'
+}
+
+export default function imageLoader({ src, width, quality }) {
+ // We pass down the optimisation request to the image-provider service here, without this, nextJs would try to use internal optimiser which is not working with the external image-provider.
+ return `${protocol}://${hostname}:${port}/${src}?w=${width}&q=${quality || 75}`
+}
diff --git a/src/frontend/utils/telemetry/FrontendTracer.ts b/src/frontend/utils/telemetry/FrontendTracer.ts
new file mode 100644
index 0000000..d52412c
--- /dev/null
+++ b/src/frontend/utils/telemetry/FrontendTracer.ts
@@ -0,0 +1,72 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
+import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
+import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
+import { registerInstrumentations } from '@opentelemetry/instrumentation';
+import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
+import { resourceFromAttributes, detectResources } from '@opentelemetry/resources';
+import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector';
+import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
+import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
+import { SessionIdProcessor } from './SessionIdProcessor';
+
+const {
+ NEXT_PUBLIC_OTEL_SERVICE_NAME = '',
+ NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = '',
+ IS_SYNTHETIC_REQUEST = '',
+} = typeof window !== 'undefined' ? window.ENV : {};
+
+const FrontendTracer = async () => {
+ const { ZoneContextManager } = await import('@opentelemetry/context-zone');
+
+ let resource = resourceFromAttributes({
+ [ATTR_SERVICE_NAME]: NEXT_PUBLIC_OTEL_SERVICE_NAME,
+ });
+ const detectedResources = detectResources({detectors: [browserDetector]});
+ resource = resource.merge(detectedResources);
+
+ const provider = new WebTracerProvider({
+ resource,
+ spanProcessors: [
+ new SessionIdProcessor(),
+ new BatchSpanProcessor(
+ new OTLPTraceExporter({
+ url: NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'http://localhost:4318/v1/traces',
+ }),
+ {
+ scheduledDelayMillis: 500,
+ }
+ ),
+ ],
+ });
+
+ const contextManager = new ZoneContextManager();
+
+ provider.register({
+ contextManager,
+ propagator: new CompositePropagator({
+ propagators: [
+ new W3CBaggagePropagator(),
+ new W3CTraceContextPropagator()],
+ }),
+ });
+
+ registerInstrumentations({
+ tracerProvider: provider,
+ instrumentations: [
+ getWebAutoInstrumentations({
+ '@opentelemetry/instrumentation-fetch': {
+ propagateTraceHeaderCorsUrls: /.*/,
+ clearTimingResources: true,
+ applyCustomAttributesOnSpan(span) {
+ span.setAttribute('app.synthetic_request', IS_SYNTHETIC_REQUEST);
+ },
+ },
+ }),
+ ],
+ });
+};
+
+export default FrontendTracer;
diff --git a/src/frontend/utils/telemetry/Instrumentation.js b/src/frontend/utils/telemetry/Instrumentation.js
new file mode 100644
index 0000000..39b0b85
--- /dev/null
+++ b/src/frontend/utils/telemetry/Instrumentation.js
@@ -0,0 +1,41 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+const opentelemetry = require('@opentelemetry/sdk-node');
+const {getNodeAutoInstrumentations} = require('@opentelemetry/auto-instrumentations-node');
+const {OTLPTraceExporter} = require('@opentelemetry/exporter-trace-otlp-grpc');
+const {OTLPMetricExporter} = require('@opentelemetry/exporter-metrics-otlp-grpc');
+const {PeriodicExportingMetricReader} = require('@opentelemetry/sdk-metrics');
+const {alibabaCloudEcsDetector} = require('@opentelemetry/resource-detector-alibaba-cloud');
+const {awsEc2Detector, awsEksDetector} = require('@opentelemetry/resource-detector-aws');
+const {containerDetector} = require('@opentelemetry/resource-detector-container');
+const {gcpDetector} = require('@opentelemetry/resource-detector-gcp');
+const {envDetector, hostDetector, osDetector, processDetector} = require('@opentelemetry/resources');
+
+const sdk = new opentelemetry.NodeSDK({
+ traceExporter: new OTLPTraceExporter(),
+ instrumentations: [
+ getNodeAutoInstrumentations({
+ // disable fs instrumentation to reduce noise
+ '@opentelemetry/instrumentation-fs': {
+ enabled: false,
+ },
+ })
+ ],
+ metricReader: new PeriodicExportingMetricReader({
+ exporter: new OTLPMetricExporter(),
+ }),
+ resourceDetectors: [
+ containerDetector,
+ envDetector,
+ hostDetector,
+ osDetector,
+ processDetector,
+ alibabaCloudEcsDetector,
+ awsEksDetector,
+ awsEc2Detector,
+ gcpDetector,
+ ],
+});
+
+sdk.start();
diff --git a/src/frontend/utils/telemetry/InstrumentationMiddleware.ts b/src/frontend/utils/telemetry/InstrumentationMiddleware.ts
new file mode 100644
index 0000000..ed389af
--- /dev/null
+++ b/src/frontend/utils/telemetry/InstrumentationMiddleware.ts
@@ -0,0 +1,40 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+import { NextApiHandler } from 'next';
+import {context, Exception, Span, SpanStatusCode, trace} from '@opentelemetry/api';
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+import { metrics } from '@opentelemetry/api';
+
+const meter = metrics.getMeter('frontend');
+const requestCounter = meter.createCounter('app.frontend.requests');
+
+const InstrumentationMiddleware = (handler: NextApiHandler): NextApiHandler => {
+ return async (request, response) => {
+ const {method, url = ''} = request;
+ const [target] = url.split('?');
+
+ const span = trace.getSpan(context.active()) as Span;
+
+ let httpStatus = 200;
+ try {
+ await runWithSpan(span, async () => handler(request, response));
+ httpStatus = response.statusCode;
+ } catch (error) {
+ span.recordException(error as Exception);
+ span.setStatus({ code: SpanStatusCode.ERROR });
+ httpStatus = 500;
+ throw error;
+ } finally {
+ requestCounter.add(1, { method, target, status: httpStatus });
+ span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, httpStatus);
+ }
+ };
+};
+
+async function runWithSpan(parentSpan: Span, fn: () => Promise<unknown>) {
+ const ctx = trace.setSpan(context.active(), parentSpan);
+ return await context.with(ctx, fn);
+}
+
+export default InstrumentationMiddleware;
diff --git a/src/frontend/utils/telemetry/SessionIdProcessor.ts b/src/frontend/utils/telemetry/SessionIdProcessor.ts
new file mode 100644
index 0000000..cd89c0b
--- /dev/null
+++ b/src/frontend/utils/telemetry/SessionIdProcessor.ts
@@ -0,0 +1,27 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+import { Context } from "@opentelemetry/api";
+import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-web";
+import SessionGateway from "../../gateways/Session.gateway";
+import { AttributeNames } from "../enums/AttributeNames";
+
+const { userId } = SessionGateway.getSession();
+
+export class SessionIdProcessor implements SpanProcessor {
+ forceFlush(): Promise<void> {
+ return Promise.resolve();
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onStart(span: Span, parentContext: Context): void {
+ span.setAttribute(AttributeNames.SESSION_ID, userId);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
+ onEnd(span: ReadableSpan): void {}
+
+ shutdown(): Promise<void> {
+ return Promise.resolve();
+ }
+}