Frontend

The frontend is responsible to provide a UI for users, as well as an API leveraged by the UI or other clients. The application is based on Next.JS to provide a React web-based UI and API routes.

Frontend source

Server Instrumentation

It is recommended to use a Node required module when starting your Node.js application to initialize the SDK and auto-instrumentation. When initializing the OpenTelemetry Node.js SDK, you optionally specify which auto-instrumentation libraries to leverage, or make use of the getNodeAutoInstrumentations() function which includes most popular frameworks. The utils/telemetry/Instrumentation.js file contains all code required to initialize the SDK and auto-instrumentation based on standard OpenTelemetry environment variables for OTLP export, resource attributes, and service name.

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({ // only instrument fs if it is part of another trace '@opentelemetry/instrumentation-fs': { requireParentSpan: true, }, }), ], metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter(), }), resourceDetectors: [ containerDetector, envDetector, hostDetector, osDetector, processDetector, alibabaCloudEcsDetector, awsEksDetector, awsEc2Detector, gcpDetector, ], }); sdk.start();

Node required modules are loaded using the --require command line argument. This can be done in the scripts.start section of package.json and starting the application using npm start.

"scripts": { "start": "node --require ./Instrumentation.js server.js", },

Traces

Span Exceptions and status

You can use the span object’s recordException function to create a span event with the full stack trace of a handled error. When recording an exception also be sure to set the span’s status accordingly. You can see this in the catch block of the NextApiHandler function in the utils/telemetry/InstrumentationMiddleware.ts file.

span.recordException(error as Exception); span.setStatus({ code: SpanStatusCode.ERROR });

Create new spans

New spans can be created and started using Tracer.startSpan("spanName", options). Several options can be used to specify how the span can be created.

  • root: true will create a new trace, setting this span as the root.
  • links are used to specify links to other spans (even within another trace) that should be referenced.
  • attributes are key/value pairs added to a span, typically used for application context.
span = tracer.startSpan(`HTTP ${method}`, { root: true, kind: SpanKind.SERVER, links: [{ context: syntheticSpan.spanContext() }], attributes: { 'app.synthetic_request': true, [SEMATTRS_HTTP_TARGET]: target, [SEMATTRS_HTTP_STATUS_CODE]: response.statusCode, [SEMATTRS_HTTP_METHOD]: method, [SEMATTRS_HTTP_USER_AGENT]: headers['user-agent'] || '', [SEMATTRS_HTTP_URL]: `${headers.host}${url}`, [SEMATTRS_HTTP_FLAVOR]: httpVersion, }, });

Browser Instrumentation

The web-based UI that the frontend provides is also instrumented for web browsers. OpenTelemetry instrumentation is included as part of the Next.js App component in pages/_app.tsx. Here instrumentation is imported and initialized.

import FrontendTracer from '../utils/telemetry/FrontendTracer'; if (typeof window !== 'undefined') FrontendTracer();

The utils/telemetry/FrontendTracer.ts file contains code to initialize a TracerProvider, establish an OTLP export, register trace context propagators, and register web specific auto-instrumentation libraries. Since the browser will send data to an OpenTelemetry Collector that will likely be on a separate domain, CORS headers are also setup accordingly.

As part of the changes to carry over the synthetic_request attribute flag for the backend services, the applyCustomAttributesOnSpan configuration function has been added to the instrumentation-fetch library custom span attributes logic that way every browser-side span will include it.

import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator, } from '@opentelemetry/core'; import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; import { Resource } from '@opentelemetry/resources'; import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; const FrontendTracer = async () => { const { ZoneContextManager } = await import('@opentelemetry/context-zone'); const provider = new WebTracerProvider({ resource: new Resource({ [SEMRESATTRS_SERVICE_NAME]: process.env.NEXT_PUBLIC_OTEL_SERVICE_NAME, }), }); provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter())); 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', 'false'); }, }, }), ], }); }; export default FrontendTracer;

Metrics

TBD

Logs

TBD

Baggage

OpenTelemetry Baggage is leveraged in the frontend to check if the request is synthetic (from the load generator). Synthetic requests will force the creation of a new trace. The root span from the new trace will contain many of the same attributes as an HTTP request instrumented span.

To determine if a Baggage item is set, you can leverage the propagation API to parse the Baggage header, and leverage the baggage API to get or set entries.

const baggage = propagation.getBaggage(context.active()); if (baggage?.getEntry("synthetic_request")?.value == "true") {...}