From 30c6159f64ccf8cf99b0cc7886c8c9c892097129 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Wed, 26 Nov 2025 19:07:15 +0100 Subject: [PATCH 01/27] Add toc --- .../observability/applications/otel-rum.md | 698 ++++++++++++++++++ solutions/toc.yml | 2 + 2 files changed, 700 insertions(+) create mode 100644 solutions/observability/applications/otel-rum.md diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md new file mode 100644 index 0000000000..0d8df33d5c --- /dev/null +++ b/solutions/observability/applications/otel-rum.md @@ -0,0 +1,698 @@ +--- +navigation_title: OpenTelemetry for Real User Monitoring (RUM) +description: Instrument web applications with OpenTelemetry for Real User Monitoring using Elastic Observability. +applies_to: + stack: + serverless: + observability: +products: + - id: cloud-serverless + - id: observability +--- + +# OpenTelemetry for Real User Monitoring (RUM) + +:::{important} +Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in **Technical Preview**. This feature is provided as-is and has [limitations](#known-limitations). It should not be used in production environments. Elastic provides best-effort support for Technical Preview features and they are not covered under standard SLAs. +::: + +This documentation outlines the process for instrumenting your web application with OpenTelemetry browser instrumentation, using {{product.observability}} as the backend. Unlike the [EDOT SDKs](opentelemetry://reference/edot-sdks/index.md), this approach uses upstream OpenTelemetry JavaScript packages directly. The following sections detail the required components and their proper configuration to acquire traces, logs, and metrics from the application to visualize them within {{kib}}. + +While this guide uses upstream OpenTelemetry instrumentation, you can also use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) components as part of your data ingestion pipeline. + +:::{warning} +Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents in the same application process might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. +::: + +## Prerequisites + +This guide assumes you're using an {{product.observability}} deployment. You can use an existing one or set up a new one. If you're new to {{product.observability}}, follow the guidelines in [Get started with {{product.observability}}](/solutions/observability/get-started.md). + +### OTLP endpoint + +You have two main options for setting up an OTLP endpoint: + +1. **Use an existing OpenTelemetry Collector**: If you already have a Collector deployed in your infrastructure, you can configure it to accept telemetry from browsers. This requires configuring CORS headers either directly on the Collector (if it's publicly accessible) or through a reverse proxy. + +2. **Start from scratch with {{ecloud}}**: If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). This approach requires a reverse proxy to handle CORS configuration. + +#### Use an existing OpenTelemetry Collector [use-an-existing-open-telemetry-collector] + +If you already have an OpenTelemetry Collector in your infrastructure, you can reuse it to ingest the traces, metrics, and logs generated by your instrumented web applications. + +::::{tab-set} + +:::{tab-item} Reverse proxy (recommended) + +If your Collector is not publicly available or you want to control where the data is coming from, use a reverse proxy to forward data from the browsers through your web application. This is the recommended method. + +The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the Collector located at `collector.example.com` from the origin `webapp.example.com`: + +```nginx +server { + listen 80 default_server; + server_name _; + location / { + # Take care of preflight requests + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type' always; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass https://collector.example.com:4318; + } +} +``` +::: + +:::{tab-item} Public Collector with CORS + +If the Collector is publicly available, you can send the telemetry data directly to it. Your Collector should be available under a domain name, for example `collector.example.com:443`. Your web application should send data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. + +To configure CORS in your collector, change the configuration in your HTTP receiver: + +```yaml +receivers: + # Receives data from other Collectors in Agent mode + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 # Listen on all interfaces + http: + endpoint: 0.0.0.0:4318 # Listen on all interfaces + # Configure CORS for RUM + cors: + allowed_origins: + - http://webapp.example.com + - https://webapp.example.com +``` + +For details on the configuration, refer to the [OpenTelemetry Collector HTTP server configuration](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration). + +::: +:::: + +#### Start with {{ecloud}} + +If you're new to {{observability}} or want to start from scratch, create a new {{ecloud}} hosted deployment (ECH) or a {{serverless-short}} project: + +- To create a new ECH deployment, follow the guidelines in [Deploy and manage Elastic Cloud hosted](/deploy-manage/deploy/elastic-cloud/cloud-hosted.md). +- To create a {{serverless-short}} project, follow the guidelines in [Get started with Elastic Observability](/solutions/observability/get-started.md). + +Both options come with a [Managed OTLP Endpoint (mOTLP)](opentelemetry://reference/motlp.md). However, the mOTLP endpoint cannot be configured for CORS. + +The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the mOTLP endpoint from the origin `webapp.example.com`: + +```nginx +server { + listen 80 default_server; + server_name _; + location / { + # Take care of preflight requests + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type,Authorization' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type,Authorization' always; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Authorization $http_authorization; + + proxy_pass https://:443; + } +} +``` + +## Installation + +OpenTelemetry packages for web instrumentation are published to npm. You can install them with the package manager of your choice. + +The following packages hold the necessary components to set up the base for your instrumentations: + +```bash +npm install @opentelemetry/api @opentelemetry/core @opentelemetry/resources @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http @opentelemetry/instrumentation +``` + +You can then install the instrumentations that you're interested in. OpenTelemetry offers several instrumentations for browsers. For example: + +```bash +npm install @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-user-interaction +``` + +## Basic configuration + +The minimal configuration you need to instrument your web application with OpenTelemetry includes: + +- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of an OpenTelemetry Collector where data is sent. When using {{product.observability}}, this is the ingest endpoint of an {{serverless-full}} project or the URL of a deployed [EDOT Collector](elastic-agent://reference/edot-collector/index.md). It is likely that the Collector endpoint is of a different origin. If that's the case, you will encounter [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) issues. Refer to the [OTLP endpoint](#otlp-endpoint) section for more information on how to solve it with different approaches. +- **OTEL_RESOURCE_ATTRIBUTES**: A JavaScript object that will be used to define the resource. The most important attributes to define are: + - `service.name` (string): Name of the application you're instrumenting. + - `service.version` (string, optional): A string representing the version or build of your app. + - `deployment.environment.name` (string, optional): Name of the environment where the app runs (if applicable); for example, "prod", "dev", or "staging". +- **OTEL_LOG_LEVEL**: Use this configuration to set the log level of the OpenTelemetry components you're going to use. + +## Set up OpenTelemetry for the browser + +To begin instrumenting your web application with OpenTelemetry in the browser, you need a script. This script configures the essential components, including the context manager, signal providers, processors, and exporters. After setting up the script, you can register the installed instrumentations so they can observe your application and send traces, metrics, and logs to your designated endpoint. + +The following start script is in plain JavaScript. If you are using TypeScript, you can adapt this script by changing the file extension to `.ts` and adding the necessary type definitions. OpenTelemetry packages are written in TypeScript, so they include the appropriate type definitions. + +:::{note} +Each signal configuration is independent of the others, meaning that you can configure only what you need. The OpenTelemetry API defaults to no-op providers for traces, metrics, and logs. +::: + +### Set the configuration + +First, set the configuration options that to be used by all the signals and the instrumentation code. Also initialize the internal logger at the level defined in the configuration. + +For this part, you need to install the following dependencies: + +- `@opentelemetry/api`: All the packages are included. Each signal configuration uses it to register the providers for each signal. +- `@opentelemetry/core`: Contains core types and some utilities for the rest of the packages. It parses strings to the correct type. + +To install the dependencies, run the following command: + +```bash +npm install @opentelemetry/api @opentelemetry/core +``` + +After the dependencies are installed, configure the following options: + +```javascript +import { diag, DiagConsoleLogger } from '@opentelemetry/api'; +import { diagLogLevelFromString } from '@opentelemetry/core'; + +// Set the configuration options +const OTEL_LOG_LEVEL = 'info'; // Possible values: error, warn, info, debug, verbose +const OTEL_EXPORTER_OTLP_ENDPOINT = 'https://host:port'; +const OTEL_RESOURCE_ATTRIBUTES = { + 'service.name': 'my-web-app', + 'service.version': '1.2.3', + 'deployment.environment.name': 'qa', + // You can add other attributes +}; + +// Set the log level for the OTEL components +// You can raise the level to "debug" if you want more details +diag.setLogger( + new DiagConsoleLogger(), + { logLevel: diagLogLevelFromString(OTEL_LOG_LEVEL) }, +); +diag.info('OTEL bootstrap', config); +``` + +### Define the resource + +A resource is an entity that generates telemetry, with its characteristics captured in resource attributes. An example is a web application operating within a browser that produces telemetry data. Further details are available in [OpenTelemetry Resources](https://opentelemetry.io/docs/concepts/resources/). + +A standardized set of attributes is specified in [Browser resource semantic conventions](https://opentelemetry.io/docs/specs/semconv/resource/browser/), which can be included alongside those outlined in the configuration section. OpenTelemetry offers resource detectors like `browserDetector` to help set these attributes like brands, mobile, and platform. + +To define the resource, install the following dependencies: + +- `@opentelemetry/resources`: This package helps you to define and work with resources because a Resource is not a plain object and has some properties (like immutability) and constraints. +- `@opentelemetry/browser-detector`: Detectors help you to define a resource by querying the runtime and environment and resolving some attributes. In this case, the browser detector resolves the language, brands, and mobile attributes of the browser namespace. + +To install the dependencies, run the following command: + +```bash +npm install @opentelemetry/resources @opentelemetry/browser-detector +``` + +After the dependencies are installed, define the resource for your instrumentation with the following code: + +```javascript +import { resourceFromAttributes, detectResources } from '@opentelemetry/resources'; +import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector'; + +// Append this in the code section +const detectedResources = detectResources({ detectors: [browserDetector] }); +let resource = resourceFromAttributes(OTEL_RESOURCE_ATTRIBUTES); +resource = resource.merge(detectedResources); +``` + +Having this information on spans and errors is useful in diagnostic situations for identifying application and dependency compatibility issues with certain browsers. + +### Configure trace signal + +To enable instrumentations to transmit traces and allow for the creation of custom spans via the OpenTelemetry API, a [TracerProvider](https://opentelemetry.io/docs/concepts/signals/traces/#tracer-provider) must be configured. This provider necessitates the inclusion of several key components: + +- **Resource**: The resource to be associated with the spans created by the tracers (previously defined). +- **Span Processor**: A component that manages the spans generated by the tracers and forwards them to a [SpanExporter](https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter). The exporter should be configured to direct data to an endpoint designated for traces. +- **Span Exporter**: Manages the transmission of spans to the Collector. + +For this part, you need to install the following dependencies: + +- `@opentelemetry/sdk-trace-base`: This package contains all the core components to set up tracing regardless of the runtime they're running in (Node.js or browser). +- `@opentelemetry/sdk-trace-web`: This package contains a tracer provider that runs in web browsers. +- `@opentelemetry/exporter-trace-otlp-http`: This package contains the exporter for the HTTP/JSON protocol. + +```bash +npm install @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http +``` + +Once the dependencies are installed, you can configure and register a tracer provider with the following code: + +```javascript +import { trace } from '@opentelemetry/api'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; + +// Set the traces endpoint based on the config provided +const tracesEndpoint = `${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`; + +// Set the tracer provider for instrumentations and calls to the API to start and end spans +const tracerProvider = new WebTracerProvider({ + resource, // All spans will be associated with this resource + spanProcessors: [ + new BatchSpanProcessor(new OTLPTraceExporter({ + url: tracesEndpoint, + })), + ], +}); +trace.setGlobalTracerProvider(tracerProvider); +``` + +Now you can use the OpenTelemetry API to get a tracer and start creating your own spans. Instrumentations can also do it after you register them. + +### Configure metrics signal + +:::{note} +Metrics from browser-based RUM are primarily used for aggregate analysis across many browser instances. The data becomes useful when processed in the Collector or backend. Consider whether browser-side metrics collection aligns with your observability goals before enabling this signal. +::: + +Similar to traces, you should configure a [MeterProvider](https://opentelemetry.io/docs/concepts/signals/metrics/#meter-provider) for metrics. This provider necessitates the inclusion of several key components: + +- **Resource**: The resource to be associated with the metrics created by the meters. +- **Metric Reader**: Used to determine how often metrics are collected and what destination they should be exported to. In this case, we will use a `PeriodicExportingMetricReader` configured to collect and export metrics at a fixed interval. +- **Metric Exporter**: Responsible for serializing and sending the collected and aggregated metric data to a backend observability platform. We will use the OTLP/HTTP exporter. + +For this part, you need to install the following dependencies: + +- `@opentelemetry/sdk-metrics`: This package contains all the required components to set up metrics. +- `@opentelemetry/exporter-metrics-otlp-http`: This package contains the exporter for the HTTP/JSON protocol. + +To install the dependencies, run the following command: + +```bash +npm install @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http +``` + +After the dependencies are installed, configure and register a meter provider with the following code: + +```javascript +import { metrics } from '@opentelemetry/api'; +import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; +import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; + +// Set the metrics endpoint based on the config provided +const metricsEndpoint = `${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics`; + +// Create metric reader to process metrics and export using OTLP +const metricReader = new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ url: metricsEndpoint }), +}); + +// Create meter provider to send metrics +const meterProvider = new MeterProvider({ + resource: resource, // All metrics will be associated with this resource + readers: [metricReader], +}); +metrics.setGlobalMeterProvider(meterProvider); +``` + +### Configure logs signal + +For RUM log management with OpenTelemetry JavaScript, configure the **Provider** for generation (instantiation, resource, logger creation) and the **Exporter** for transmission (endpoint, headers, interval/batching, registration). + +For this part, you need to install the following dependencies: + +- `@opentelemetry/api-logs`: This package contains the logs API. This API is not included yet in the generic API package because logs are still experimental. +- `@opentelemetry/sdk-logs`: This package contains all the required components to set up logs. +- `@opentelemetry/exporter-logs-otlp-http`: This package contains the exporter for the HTTP/JSON protocol. + +To install the dependencies, run the following command: + +```bash +npm install @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http +``` + +After the dependencies are installed, you can configure and register a logger provider with the following code: + +```javascript +import { logs, SeverityNumber } from '@opentelemetry/api-logs'; +import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; + +// Set the logs endpoint based on the config provided +const logsEndpoint = `${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs`; + +// Configure logging to send to the Collector +const logExporter = new OTLPLogExporter({ url: logsEndpoint }); + +const loggerProvider = new LoggerProvider({ + resource: resource, + processors: [new BatchLogRecordProcessor(logExporter)] +}); +logs.setGlobalLoggerProvider(loggerProvider); +``` + +### Register instrumentations + +The final step for setting up Real User Monitoring (RUM) through OpenTelemetry is registering instrumentations. Instrumentations are modules that automatically capture telemetry data, like network requests or DOM interactions, by using the OpenTelemetry API. + +With the OpenTelemetry SDK, resource attributes, and exporters already configured, all telemetry data generated by these registered instrumentations is automatically processed and exported. + +Install the following dependencies: + +- `@opentelemetry/instrumentation`: This package contains the core components of instrumentations along with some utilities. +- `@opentelemetry/instrumentation-document-load`: This instrumentation package measures the time it took the document to load and also the load timings of its resources. More info at [instrumentation-document-load](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load). +- `@opentelemetry/instrumentation-long-task`: This instrumentation gathers information about long tasks being executed in your browser, helping to spot issues like unresponsive UI in your web application. More info at [instrumentation-long-task](https://www.npmjs.com/package/@opentelemetry/instrumentation-long-task). +- `@opentelemetry/instrumentation-fetch`: This instrumentation keeps track of your web application requests made via the Fetch API. More info at [instrumentation-fetch](https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch). +- `@opentelemetry/instrumentation-xml-http-request`: This instrumentation keeps track of your web application requests made via the XMLHttpRequest API. More info at [instrumentation-xml-http-request](https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request). +- `@opentelemetry/instrumentation-user-interaction`: This instrumentation measures user interactions in your web application. More info at [instrumentation-user-interaction](https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction). + +To install the dependencies, run the following command: + +```bash +npm install @opentelemetry/instrumentation @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-user-interaction +``` + +After the dependencies are installed, you can configure and register instrumentations with the following code: + +```javascript +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; +import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; +import { LongTaskInstrumentation } from '@opentelemetry/instrumentation-long-task'; +import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction' +import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; + +// Register instrumentations +registerInstrumentations({ + instrumentations: [ + new DocumentLoadInstrumentation(), + new LongTaskInstrumentation(), + new FetchInstrumentation(), + new XMLHttpRequestInstrumentation(), + new UserInteractionInstrumentation(), + ], +}); +``` + +### Complete setup script + +All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it can be wrapped within a function that accepts the configuration as a parameter, allowing you to reuse the setup across different UIs. + +```javascript +// file: telemetry.js +import { diag, DiagConsoleLogger, trace, metrics } from '@opentelemetry/api'; +import { diagLogLevelFromString } from '@opentelemetry/core'; +import { resourceFromAttributes, detectResources } from '@opentelemetry/resources'; +import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; +import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; +import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; +import { logs, SeverityNumber } from '@opentelemetry/api-logs'; +import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; +import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; +import { LongTaskInstrumentation } from '@opentelemetry/instrumentation-long-task'; +import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'; +import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; + +const initDone = Symbol('OTEL initialized'); + +// Expected properties of the config object: +// - logLevel +// - endpoint +// - resourceAttributes +export function initOpenTelemetry(config) { + // To avoid multiple calls + if (window[initDone]) { + return; + } + window[initDone] = true; + diag.setLogger( + new DiagConsoleLogger(), + { logLevel: diagLogLevelFromString(config.logLevel) }, + ); + diag.info('OTEL bootstrap', config); + + // Resource definition + const detectedResources = detectResources({ detectors: [browserDetector] }); + const resource = resourceFromAttributes(config.resourceAttributes) + .merge(detectedResources); + + // Trace signal setup + const tracesEndpoint = `${config.endpoint}/v1/traces`; + const tracerProvider = new WebTracerProvider({ + resource, + spanProcessors: [ + new BatchSpanProcessor(new OTLPTraceExporter({ + url: tracesEndpoint, + })), + ], + }); + trace.setGlobalTracerProvider(tracerProvider); + + // Metrics signal setup + const metricsEndpoint = `${config.endpoint}/v1/metrics`; + const metricReader = new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ url: metricsEndpoint }), + }); + const meterProvider = new MeterProvider({ + resource: resource, + readers: [metricReader], + }); + metrics.setGlobalMeterProvider(meterProvider); + + // Logs signal setup + const logsEndpoint = `${config.endpoint}/v1/logs`; + const logExporter = new OTLPLogExporter({ url: logsEndpoint }); + + const loggerProvider = new LoggerProvider({ + resource: resource, + processors: [new BatchLogRecordProcessor(logExporter)] + }); + logs.setGlobalLoggerProvider(loggerProvider); + + // Register instrumentations + registerInstrumentations({ + instrumentations: [ + new DocumentLoadInstrumentation(), + new LongTaskInstrumentation(), + new FetchInstrumentation(), + new XMLHttpRequestInstrumentation(), + new UserInteractionInstrumentation(), + ], + }); +} +``` + +## Integrate with your application + +With the setup done, it's time to apply it to your web application. You can choose from two main approaches: + +1. **Import the code**: Use your build tooling to manage the dependencies and integrate the code into the application bundle. This is the simplest option and is recommended, although it increases the size of your application bundle. + +2. **Bundle in a file**: Use a bundler to generate a separate JavaScript file that you include in the `` section of your HTML page. This approach keeps the telemetry code separate from your application bundle. + +### Import the code + +This approach is recommended. The build tooling manages the dependencies and integrates the code into the application bundle. This might increase the size of your application bundle. + +For example, if you're using Webpack, you can import the code like this: + +```javascript +// file: app.(js|ts) entry point of your application +import { initOpenTelemetry } from 'telemetry.js'; + +initOpenTelemetry({ + logLevel: 'info', + endpoint: 'https://host:port/', + resourceAttributes: { + 'service.name': 'my-web-app', + 'service.version': '1', + } +}); + +// Your app code +``` + +### Bundle in a file + +You can use a bundler to generate a separate JavaScript file. Place the file within the application's assets folder and include it in the `` section of the HTML page. + +Assuming the JavaScript files reside in a folder named "js", the HTML file structure looks like this: + +```html + + + + + + … + + + + + +``` + +## Extend your telemetry with the API + +Automatic instrumentation provides a convenient baseline for web application telemetry, but often lacks the necessary depth to fully understand complex user journeys or correlate technical performance with business outcomes. + +The OpenTelemetry API is essential for filling this gap. By using the OpenTelemetry API directly, you can send highly specific, custom telemetry to augment automatic collection. This custom instrumentation allows you to: + +1. **Define custom spans and traces**: Create explicit spans around unique critical business logic or user interactions (for example, complex calculations, multi-step forms) for granular detail. +2. **Log application-specific events**: Generate high-fidelity logs that directly correlate with the flow of a trace for better debugging. +3. **Create custom metrics**: Record application-specific KPIs not covered by standard RUM metrics (for example, UI component render counts, client-side transaction success rates). + +Leveraging the OpenTelemetry API to augment data collection makes your application's observability truly comprehensive, bridging the gap between technical monitoring and business intelligence. + +### Track request path with traces + +Your web application might initiate several HTTP requests to an associated API. With the instrumentations established in the previous section, a span is generated for each request, each part of a separate trace, meaning they are treated as independent operations. While this provides a clear breakdown of each individual request, there are cases where consolidating multiple related requests within a single, cohesive trace is highly desirable for better observability. + +An example is a recurring task that updates the user interface at regular intervals to display various datasets that fluctuate over time. In this case, grouping all the API calls necessary for a single UI refresh into one trace allows you to view the overall performance and flow of the entire update cycle. + +```javascript +import { trace } from '@opentelemetry/api'; +const tracer = trace.getTracer('app-tracer'); + +// Update the UI +setInterval(function () { + tracer.startActiveSpan('ui-update', async function (span) { + const datasetOne = await fetchDatasetOne(); + // Update the UI with 1st dataset, some other async work + const datasetTwo = await fetchDatasetTwo(); + // Update the UI with 2nd dataset + span.end(); + }); +}, intervalTime) +``` + +By using the `startActiveSpan` callback mechanism, you can wrap the asynchronous data fetching logic within a dedicated active trace. This technique accurately captures the execution flow and performance characteristics of operations that involve multiple steps or services. You get a single root span for the entire operation; this root span established by the callback acts as the primary container for the entire sequence of events. Contained within this root span are two distinct child spans that represent each request from the UI to the API. + +### Record relevant events with logs + +Relevant events occurring within your application can be recorded using a logger. A typical scenario involves documenting business-critical occurrences, such as conversions or purchases. + +```javascript +import { logs, SeverityNumber } from '@opentelemetry/api-logs'; +const logger = logs.getLogger('app-logger'); + +logger.emit({ + eventName: 'purchase', + timestamp: Date.now(), + attributes: { + 'orderId': '12345-54321', + 'amount': '200.56', + } +}); +``` + +## Browser constraints + +Review the following constraints in your web application to avoid any data transmission issues. + +### Content Security Policy + +If your website is making use of Content Security Policies (CSPs), make sure that the domain of your OTLP endpoint is included. If your Collector endpoint is `https://collector.example.com:4318/v1/traces`, add the following directive: + +```text +connect-src collector.example.com:4318/v1/traces +``` + +### Cross-Origin Resource Sharing (CORS) + +If your website and Collector are hosted at a different origin, your browser might block the requests going out to your Collector. To solve this, you need to configure special headers for Cross-Origin Resource Sharing (CORS). This configuration depends on the solution you want to adopt and is described in the [OTLP endpoint](#otlp-endpoint) section. + +## Data ingestion + +The endpoint configured should belong to an OpenTelemetry Collector or a component that forwards data to one. + +### EDOT Collector in gateway mode + +In this approach, the Collector sends the data directly to the {{es}} database. For that purpose, use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) in [Gateway mode](elastic-agent://reference/edot-collector/config/default-config-standalone.md#gateway-mode). + +For CORS configuration, edit the OTLP HTTP receiver to add a `cors` object with the following properties: + +- **allowed_origins** (mandatory): A list of origins allowed to send requests to the receiver. An origin may contain a wildcard (`*`) to replace 0 or more characters (for example, `https://*.example.com`). Do not use a plain wildcard `["*"]`, as the Collector's CORS response includes `Access-Control-Allow-Credentials: true`, which makes browsers disallow a plain wildcard (this is a security standard). To allow any origin, you can specify at least the protocol, for example `["https://*", "http://*"]`. If no origins are listed, CORS will not be enabled. +- **allowed_headers** (optional): Allow CORS requests to include headers outside the default safelist. By default, [safelist headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and `X-Requested-With` will be allowed. To allow any request header, set to `["*"]`. +- **max_age** (optional): Sets the value of the `Access-Control-Max-Age` header, allowing clients to cache the response to CORS preflight requests. If not set, browsers use a default of 5 seconds. + +This is an example of how to configure CORS allowing requests from the origin `*.example.com`: + +```yaml +receivers: + otlp: + protocols: + http: + include_metadata: true + cors: + allowed_origins: + - https://*.example.com + allowed_headers: + - Example-Header + max_age: 7200 +``` + +### Proxy server + +Having a Collector available for your web application means the Collector should also be publicly available. Using a reverse proxy provides better control over security and access. Refer to the [Use an existing OpenTelemetry Collector](#use-an-existing-open-telemetry-collector) section for configuration examples. + +:::{note} +The Managed OTLP endpoint and {{apm-server}} (ECH) are not ideal for RUM OpenTelemetry. The problem relies on CORS configuration: + +- mOTLP cannot be configured for CORS. +- {{apm-server}} allows CORS configuration for requests to `/intake/v2/rum/events` but not for the OTLP endpoints. + +For these reasons, using a reverse proxy in front of the Collector or mOTLP endpoint is the recommended approach. +::: + +## Known limitations + +- The Managed OTLP endpoint (mOTLP) cannot be directly configured for CORS. A reverse proxy is required. +- {{apm-server}} does not support CORS configuration for OTLP endpoints. +- Metrics from browser-based RUM might have limited utility compared to backend metrics. +- Some OpenTelemetry instrumentations for browsers are still experimental. +- Performance impact on the browser should be monitored, especially when using multiple instrumentations. +- Authentication using API keys requires special handling in the reverse proxy configuration. diff --git a/solutions/toc.yml b/solutions/toc.yml index 3efa563111..8ee62a6298 100644 --- a/solutions/toc.yml +++ b/solutions/toc.yml @@ -358,6 +358,8 @@ toc: - file: observability/synthetics/support-matrix.md - file: observability/synthetics/encryption-security.md - file: observability/applications/user-experience.md + children: + - file: observability/applications/otel-rum.md - file: observability/applications/llm-observability.md - file: observability/uptime/index.md children: From 00d0ad9c2db42be57ca2b47f31c242452e4c2566 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Wed, 26 Nov 2025 19:10:16 +0100 Subject: [PATCH 02/27] Remove Latinisms --- solutions/observability/applications/otel-rum.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 0d8df33d5c..ebc0a867f1 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -256,7 +256,7 @@ Having this information on spans and errors is useful in diagnostic situations f ### Configure trace signal -To enable instrumentations to transmit traces and allow for the creation of custom spans via the OpenTelemetry API, a [TracerProvider](https://opentelemetry.io/docs/concepts/signals/traces/#tracer-provider) must be configured. This provider necessitates the inclusion of several key components: +To enable instrumentations to transmit traces and allow for the creation of custom spans through the OpenTelemetry API, a [TracerProvider](https://opentelemetry.io/docs/concepts/signals/traces/#tracer-provider) must be configured. This provider necessitates the inclusion of several key components: - **Resource**: The resource to be associated with the spans created by the tracers (previously defined). - **Span Processor**: A component that manages the spans generated by the tracers and forwards them to a [SpanExporter](https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter). The exporter should be configured to direct data to an endpoint designated for traces. @@ -390,8 +390,8 @@ Install the following dependencies: - `@opentelemetry/instrumentation`: This package contains the core components of instrumentations along with some utilities. - `@opentelemetry/instrumentation-document-load`: This instrumentation package measures the time it took the document to load and also the load timings of its resources. More info at [instrumentation-document-load](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load). - `@opentelemetry/instrumentation-long-task`: This instrumentation gathers information about long tasks being executed in your browser, helping to spot issues like unresponsive UI in your web application. More info at [instrumentation-long-task](https://www.npmjs.com/package/@opentelemetry/instrumentation-long-task). -- `@opentelemetry/instrumentation-fetch`: This instrumentation keeps track of your web application requests made via the Fetch API. More info at [instrumentation-fetch](https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch). -- `@opentelemetry/instrumentation-xml-http-request`: This instrumentation keeps track of your web application requests made via the XMLHttpRequest API. More info at [instrumentation-xml-http-request](https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request). +- `@opentelemetry/instrumentation-fetch`: This instrumentation keeps track of your web application requests made through the Fetch API. More info at [instrumentation-fetch](https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch). +- `@opentelemetry/instrumentation-xml-http-request`: This instrumentation keeps track of your web application requests made through the XMLHttpRequest API. More info at [instrumentation-xml-http-request](https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request). - `@opentelemetry/instrumentation-user-interaction`: This instrumentation measures user interactions in your web application. More info at [instrumentation-user-interaction](https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction). To install the dependencies, run the following command: From a12a6b89eac9f46b4394527c481523288b6a5987 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Thu, 27 Nov 2025 10:22:52 +0100 Subject: [PATCH 03/27] Update solutions/observability/applications/otel-rum.md Co-authored-by: Christoph Heger --- solutions/observability/applications/otel-rum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index ebc0a867f1..764bbcea7c 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -185,7 +185,7 @@ Each signal configuration is independent of the others, meaning that you can con ### Set the configuration -First, set the configuration options that to be used by all the signals and the instrumentation code. Also initialize the internal logger at the level defined in the configuration. +First, set the configuration options that are to be used by all the signals and the instrumentation code. Also initialize the internal logger at the level defined in the configuration. For this part, you need to install the following dependencies: From b7f0c1a713eb3b6e2ef1bc66aa73dbc9d683d793 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 27 Nov 2025 11:23:14 +0100 Subject: [PATCH 04/27] Remove redundant section --- .../observability/applications/otel-rum.md | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 764bbcea7c..59c97abc28 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -645,49 +645,6 @@ connect-src collector.example.com:4318/v1/traces If your website and Collector are hosted at a different origin, your browser might block the requests going out to your Collector. To solve this, you need to configure special headers for Cross-Origin Resource Sharing (CORS). This configuration depends on the solution you want to adopt and is described in the [OTLP endpoint](#otlp-endpoint) section. -## Data ingestion - -The endpoint configured should belong to an OpenTelemetry Collector or a component that forwards data to one. - -### EDOT Collector in gateway mode - -In this approach, the Collector sends the data directly to the {{es}} database. For that purpose, use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) in [Gateway mode](elastic-agent://reference/edot-collector/config/default-config-standalone.md#gateway-mode). - -For CORS configuration, edit the OTLP HTTP receiver to add a `cors` object with the following properties: - -- **allowed_origins** (mandatory): A list of origins allowed to send requests to the receiver. An origin may contain a wildcard (`*`) to replace 0 or more characters (for example, `https://*.example.com`). Do not use a plain wildcard `["*"]`, as the Collector's CORS response includes `Access-Control-Allow-Credentials: true`, which makes browsers disallow a plain wildcard (this is a security standard). To allow any origin, you can specify at least the protocol, for example `["https://*", "http://*"]`. If no origins are listed, CORS will not be enabled. -- **allowed_headers** (optional): Allow CORS requests to include headers outside the default safelist. By default, [safelist headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and `X-Requested-With` will be allowed. To allow any request header, set to `["*"]`. -- **max_age** (optional): Sets the value of the `Access-Control-Max-Age` header, allowing clients to cache the response to CORS preflight requests. If not set, browsers use a default of 5 seconds. - -This is an example of how to configure CORS allowing requests from the origin `*.example.com`: - -```yaml -receivers: - otlp: - protocols: - http: - include_metadata: true - cors: - allowed_origins: - - https://*.example.com - allowed_headers: - - Example-Header - max_age: 7200 -``` - -### Proxy server - -Having a Collector available for your web application means the Collector should also be publicly available. Using a reverse proxy provides better control over security and access. Refer to the [Use an existing OpenTelemetry Collector](#use-an-existing-open-telemetry-collector) section for configuration examples. - -:::{note} -The Managed OTLP endpoint and {{apm-server}} (ECH) are not ideal for RUM OpenTelemetry. The problem relies on CORS configuration: - -- mOTLP cannot be configured for CORS. -- {{apm-server}} allows CORS configuration for requests to `/intake/v2/rum/events` but not for the OTLP endpoints. - -For these reasons, using a reverse proxy in front of the Collector or mOTLP endpoint is the recommended approach. -::: - ## Known limitations - The Managed OTLP endpoint (mOTLP) cannot be directly configured for CORS. A reverse proxy is required. From cb274bebacfbec3b1255700cbd58aa171947cf31 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 27 Nov 2025 11:29:54 +0100 Subject: [PATCH 05/27] Add npm install --- solutions/observability/applications/otel-rum.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 59c97abc28..d138fb8312 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -426,6 +426,14 @@ registerInstrumentations({ All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it can be wrapped within a function that accepts the configuration as a parameter, allowing you to reuse the setup across different UIs. +To install all the dependencies needed for the complete setup, run the following command: + +```bash +npm install @opentelemetry/api @opentelemetry/core @opentelemetry/resources @opentelemetry/browser-detector @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/instrumentation @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-user-interaction +``` + +After the dependencies are installed, you can wrap the setup in a function with the following code: + ```javascript // file: telemetry.js import { diag, DiagConsoleLogger, trace, metrics } from '@opentelemetry/api'; From 4e9b8e1f0d3e57580072388547dda566d32e6dbe Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 27 Nov 2025 11:32:28 +0100 Subject: [PATCH 06/27] Change port --- solutions/observability/applications/otel-rum.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index d138fb8312..2874464106 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -80,7 +80,7 @@ server { :::{tab-item} Public Collector with CORS -If the Collector is publicly available, you can send the telemetry data directly to it. Your Collector should be available under a domain name, for example `collector.example.com:443`. Your web application should send data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. +If the Collector is publicly available, you can send the telemetry data directly to it. Your Collector should be available under a domain name, for example `collector.example.com:4318` (4318 is the default port for the OTLP HTTP/JSON protocol). Your web application should send data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. To configure CORS in your collector, change the configuration in your HTTP receiver: @@ -583,7 +583,7 @@ Assuming the JavaScript files reside in a folder named "js", the HTML file struc ``` -## Extend your telemetry with the API +## Manual instrumentation to extend your telemetry Automatic instrumentation provides a convenient baseline for web application telemetry, but often lacks the necessary depth to fully understand complex user journeys or correlate technical performance with business outcomes. From dacd207f62b4e907cdfafcf37b1f463fe2a07235 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 27 Nov 2025 11:44:39 +0100 Subject: [PATCH 07/27] Reorganize --- .../observability/applications/otel-rum.md | 83 ++++++++++++++++--- 1 file changed, 70 insertions(+), 13 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 2874464106..b7d0ffaefb 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -20,14 +20,14 @@ This documentation outlines the process for instrumenting your web application w While this guide uses upstream OpenTelemetry instrumentation, you can also use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) components as part of your data ingestion pipeline. -:::{warning} -Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents in the same application process might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. -::: - ## Prerequisites This guide assumes you're using an {{product.observability}} deployment. You can use an existing one or set up a new one. If you're new to {{product.observability}}, follow the guidelines in [Get started with {{product.observability}}](/solutions/observability/get-started.md). +:::{warning} +Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents in the same application process might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. +::: + ### OTLP endpoint You have two main options for setting up an OTLP endpoint: @@ -46,6 +46,8 @@ If you already have an OpenTelemetry Collector in your infrastructure, you can r If your Collector is not publicly available or you want to control where the data is coming from, use a reverse proxy to forward data from the browsers through your web application. This is the recommended method. +:::{dropdown} Example NGINX reverse proxy configuration + The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the Collector located at `collector.example.com` from the origin `webapp.example.com`: ```nginx @@ -76,6 +78,8 @@ server { } } ``` + +::: ::: :::{tab-item} Public Collector with CORS @@ -114,6 +118,8 @@ If you're new to {{observability}} or want to start from scratch, create a new { Both options come with a [Managed OTLP Endpoint (mOTLP)](opentelemetry://reference/motlp.md). However, the mOTLP endpoint cannot be configured for CORS. +:::{dropdown} Example NGINX reverse proxy configuration for mOTLP + The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the mOTLP endpoint from the origin `webapp.example.com`: ```nginx @@ -146,6 +152,8 @@ server { } ``` +::: + ## Installation OpenTelemetry packages for web instrumentation are published to npm. You can install them with the package manager of your choice. @@ -158,9 +166,8 @@ npm install @opentelemetry/api @opentelemetry/core @opentelemetry/resources @ope You can then install the instrumentations that you're interested in. OpenTelemetry offers several instrumentations for browsers. For example: -```bash +- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of an OpenTelemetry Collector where data is sent. When using {{product.observability}}, this is the {{motlp}} of an {{serverless-full}} project or the URL of a deployed [EDOT Collector](elastic-agent://reference/edot-collector/index.md). Refer to the [OTLP endpoint](#otlp-endpoint) section for more information on how to solve [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) issues. npm install @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-user-interaction -``` ## Basic configuration @@ -183,7 +190,9 @@ The following start script is in plain JavaScript. If you are using TypeScript, Each signal configuration is independent of the others, meaning that you can configure only what you need. The OpenTelemetry API defaults to no-op providers for traces, metrics, and logs. ::: -### Set the configuration +::::::{stepper} + +:::::{step} Set the configuration First, set the configuration options that are to be used by all the signals and the instrumentation code. Also initialize the internal logger at the level defined in the configuration. @@ -200,6 +209,8 @@ npm install @opentelemetry/api @opentelemetry/core After the dependencies are installed, configure the following options: +:::{dropdown} Configuration example + ```javascript import { diag, DiagConsoleLogger } from '@opentelemetry/api'; import { diagLogLevelFromString } from '@opentelemetry/core'; @@ -223,7 +234,11 @@ diag.setLogger( diag.info('OTEL bootstrap', config); ``` -### Define the resource +::: + +::::: + +:::::{step} Define the resource A resource is an entity that generates telemetry, with its characteristics captured in resource attributes. An example is a web application operating within a browser that produces telemetry data. Further details are available in [OpenTelemetry Resources](https://opentelemetry.io/docs/concepts/resources/). @@ -254,7 +269,9 @@ resource = resource.merge(detectedResources); Having this information on spans and errors is useful in diagnostic situations for identifying application and dependency compatibility issues with certain browsers. -### Configure trace signal +::::: + +:::::{step} Configure trace To enable instrumentations to transmit traces and allow for the creation of custom spans through the OpenTelemetry API, a [TracerProvider](https://opentelemetry.io/docs/concepts/signals/traces/#tracer-provider) must be configured. This provider necessitates the inclusion of several key components: @@ -274,6 +291,8 @@ npm install @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentele Once the dependencies are installed, you can configure and register a tracer provider with the following code: +:::{dropdown} Tracer provider configuration + ```javascript import { trace } from '@opentelemetry/api'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; @@ -295,9 +314,13 @@ const tracerProvider = new WebTracerProvider({ trace.setGlobalTracerProvider(tracerProvider); ``` +::: + Now you can use the OpenTelemetry API to get a tracer and start creating your own spans. Instrumentations can also do it after you register them. -### Configure metrics signal +::::: + +:::::{step} Configure metrics :::{note} Metrics from browser-based RUM are primarily used for aggregate analysis across many browser instances. The data becomes useful when processed in the Collector or backend. Consider whether browser-side metrics collection aligns with your observability goals before enabling this signal. @@ -322,6 +345,8 @@ npm install @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http After the dependencies are installed, configure and register a meter provider with the following code: +:::{dropdown} Meter provider configuration + ```javascript import { metrics } from '@opentelemetry/api'; import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; @@ -343,7 +368,11 @@ const meterProvider = new MeterProvider({ metrics.setGlobalMeterProvider(meterProvider); ``` -### Configure logs signal +::: + +::::: + +:::::{step} Configure logs For RUM log management with OpenTelemetry JavaScript, configure the **Provider** for generation (instantiation, resource, logger creation) and the **Exporter** for transmission (endpoint, headers, interval/batching, registration). @@ -361,6 +390,8 @@ npm install @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/expor After the dependencies are installed, you can configure and register a logger provider with the following code: +:::{dropdown} Logger provider configuration + ```javascript import { logs, SeverityNumber } from '@opentelemetry/api-logs'; import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; @@ -379,7 +410,11 @@ const loggerProvider = new LoggerProvider({ logs.setGlobalLoggerProvider(loggerProvider); ``` -### Register instrumentations +::: + +::::: + +:::::{step} Register instrumentations The final step for setting up Real User Monitoring (RUM) through OpenTelemetry is registering instrumentations. Instrumentations are modules that automatically capture telemetry data, like network requests or DOM interactions, by using the OpenTelemetry API. @@ -402,6 +437,8 @@ npm install @opentelemetry/instrumentation @opentelemetry/instrumentation-docume After the dependencies are installed, you can configure and register instrumentations with the following code: +:::{dropdown} Instrumentations registration + ```javascript import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; @@ -422,7 +459,11 @@ registerInstrumentations({ }); ``` -### Complete setup script +::: + +::::: + +:::::{step} Complete setup script All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it can be wrapped within a function that accepts the configuration as a parameter, allowing you to reuse the setup across different UIs. @@ -434,6 +475,8 @@ npm install @opentelemetry/api @opentelemetry/core @opentelemetry/resources @ope After the dependencies are installed, you can wrap the setup in a function with the following code: +:::{dropdown} Complete setup script example + ```javascript // file: telemetry.js import { diag, DiagConsoleLogger, trace, metrics } from '@opentelemetry/api'; @@ -524,6 +567,12 @@ export function initOpenTelemetry(config) { } ``` +::: + +::::: + +:::::: + ## Integrate with your application With the setup done, it's time to apply it to your web application. You can choose from two main approaches: @@ -560,6 +609,8 @@ You can use a bundler to generate a separate JavaScript file. Place the file wit Assuming the JavaScript files reside in a folder named "js", the HTML file structure looks like this: +:::{dropdown} HTML example + ```html @@ -583,6 +634,8 @@ Assuming the JavaScript files reside in a folder named "js", the HTML file struc ``` +::: + ## Manual instrumentation to extend your telemetry Automatic instrumentation provides a convenient baseline for web application telemetry, but often lacks the necessary depth to fully understand complex user journeys or correlate technical performance with business outcomes. @@ -601,6 +654,8 @@ Your web application might initiate several HTTP requests to an associated API. An example is a recurring task that updates the user interface at regular intervals to display various datasets that fluctuate over time. In this case, grouping all the API calls necessary for a single UI refresh into one trace allows you to view the overall performance and flow of the entire update cycle. +:::{dropdown} Example: Group API calls in a trace + ```javascript import { trace } from '@opentelemetry/api'; const tracer = trace.getTracer('app-tracer'); @@ -617,6 +672,8 @@ setInterval(function () { }, intervalTime) ``` +::: + By using the `startActiveSpan` callback mechanism, you can wrap the asynchronous data fetching logic within a dedicated active trace. This technique accurately captures the execution flow and performance characteristics of operations that involve multiple steps or services. You get a single root span for the entire operation; this root span established by the callback acts as the primary container for the entire sequence of events. Contained within this root span are two distinct child spans that represent each request from the UI to the API. ### Record relevant events with logs From d573399f73c47bdeace4c01e14c7519d00368016 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 27 Nov 2025 12:19:03 +0100 Subject: [PATCH 08/27] Add troubleshooting --- .../observability/applications/otel-rum.md | 253 +++++++++++++++++- 1 file changed, 252 insertions(+), 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index b7d0ffaefb..30d5c71219 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -32,7 +32,7 @@ Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic { You have two main options for setting up an OTLP endpoint: -1. **Use an existing OpenTelemetry Collector**: If you already have a Collector deployed in your infrastructure, you can configure it to accept telemetry from browsers. This requires configuring CORS headers either directly on the Collector (if it's publicly accessible) or through a reverse proxy. +1. Use an existing OpenTelemetry Collector: If you already have a Collector deployed in your infrastructure, you can configure it to accept telemetry from browsers. This requires configuring CORS headers either directly on the Collector (if it's publicly accessible) or through a reverse proxy. 2. **Start from scratch with {{ecloud}}**: If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). This approach requires a reverse proxy to handle CORS configuration. @@ -587,6 +587,8 @@ This approach is recommended. The build tooling manages the dependencies and int For example, if you're using Webpack, you can import the code like this: +:::{dropdown} Example: Import telemetry.js in your app + ```javascript // file: app.(js|ts) entry point of your application import { initOpenTelemetry } from 'telemetry.js'; @@ -603,6 +605,8 @@ initOpenTelemetry({ // Your app code ``` +::: + ### Bundle in a file You can use a bundler to generate a separate JavaScript file. Place the file within the application's assets folder and include it in the `` section of the HTML page. @@ -718,3 +722,250 @@ If your website and Collector are hosted at a different origin, your browser mig - Some OpenTelemetry instrumentations for browsers are still experimental. - Performance impact on the browser should be monitored, especially when using multiple instrumentations. - Authentication using API keys requires special handling in the reverse proxy configuration. + +## Troubleshooting + +This section provides solutions to common issues you might encounter when setting up OpenTelemetry for RUM with {{product.observability}}. + +:::{dropdown} Module import or bundler errors + +If you see errors like "Cannot find module" or bundler-specific issues: + +1. Ensure all required packages are installed and listed in `package.json`. + +2. Different bundlers (Webpack, Rollup, Vite) may require specific configuration for OpenTelemetry packages. + +3. For Webpack, you may need to add polyfills for Node.js modules. Add to your webpack config: + +```javascript +resolve: { + fallback: { + "process": require.resolve("process/browser"), + "buffer": require.resolve("buffer/") + } +} +``` + +4. For Vite, add to your `vite.config.js`: + +```javascript +optimizeDeps: { + include: ['@opentelemetry/api', '@opentelemetry/sdk-trace-web'] +} +``` + +5. If using TypeScript, ensure `tsconfig.json` has appropriate module resolution: + +```json +{ + "compilerOptions": { + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true + } +} +``` + +::: + +:::{dropdown} Reverse proxy configuration issues + +If your reverse proxy is not forwarding requests correctly: + +1. Ensure the reverse proxy (NGINX, Apache, etc.) is running and accessible. + +2. Use curl to test the proxy endpoint directly: + +```bash +curl -X POST https://your-proxy/v1/traces \ + -H "Content-Type: application/json" \ + -H "Origin: https://your-webapp.example.com" \ + -d '{"test": "data"}' \ + -v +``` + +3. Review proxy logs for errors or blocked requests. + +4. Ensure the proxy can reach the backend Collector or mOTLP endpoint. + +::: + +:::{dropdown} Configuration issues + +If your OpenTelemetry setup isn't initializing correctly: + +1. Ensure `OTEL_EXPORTER_OTLP_ENDPOINT` doesn't include the signal path (like `/v1/traces`). The SDK adds this automatically: + +```javascript +// Correct +const OTEL_EXPORTER_OTLP_ENDPOINT = 'https://collector.example.com:4318'; + +// Incorrect +const OTEL_EXPORTER_OTLP_ENDPOINT = 'https://collector.example.com:4318/v1/traces'; +``` + +2. Verify `service.name` is set and doesn't contain special characters: + +```javascript +const OTEL_RESOURCE_ATTRIBUTES = { + 'service.name': 'my-web-app', // Required + 'service.version': '1.0.0', +}; +``` + +3. Ensure providers are registered before instrumentations: + +```javascript +// Correct order: +// 1. Configure and register tracer provider +trace.setGlobalTracerProvider(tracerProvider); +// 2. Then register instrumentations +registerInstrumentations({...}); +``` + +::: + +:::{dropdown} Data not appearing in {{kib}} + +If you've instrumented your application but don't see data in {{kib}}, check the following: + +1. Ensure `OTEL_EXPORTER_OTLP_ENDPOINT` points to the correct endpoint. Test the endpoint connectivity using browser developer tools. + +2. Open your browser's developer console (F12) and look for network errors or OpenTelemetry-related error messages. Common issues include failed requests to the OTLP endpoint. + +3. Ensure `service.name` is set in your resource attributes. Without this attribute, data might not be properly categorized in {{kib}}. + +4. In {{kib}}, navigate to **{{stack-manage-app}}** > **{{index-manage-app}}** > **Data Streams** and verify that OpenTelemetry data streams are being created (for example, `traces-*`, `logs-*`, `metrics-*`). + +5. Set `OTEL_LOG_LEVEL` to `debug` to get detailed information about what's happening: + +```javascript +const OTEL_LOG_LEVEL = 'debug'; +``` + +::: + +:::{dropdown} CORS errors + +CORS errors are the most common issue with browser-based RUM. Symptoms include: + +- Network requests blocked in the browser console +- Error messages like "Access to fetch at '...' from origin '...' has been blocked by CORS policy" + +1. Verify CORS configuration: If using a reverse proxy, ensure the CORS headers are correctly configured. The `Access-Control-Allow-Origin` header must match your web application's origin. + +2. Check allowed headers: Ensure all necessary headers are included in `Access-Control-Allow-Headers`, especially `Authorization` if using authentication. + +3. Verify preflight requests: CORS requires preflight OPTIONS requests. Ensure your reverse proxy or Collector handles these correctly with a 204 response. + +4. Test with a request: Try sending a test request using `curl` or a tool like Postman to verify the endpoint is accessible: + +```bash +curl -X POST https://your-proxy-endpoint/v1/traces \ + -H "Content-Type: application/json" \ + -H "Origin: https://your-webapp.example.com" \ + -v +``` + +::: + +:::{dropdown} Content Security Policy (CSP) violations + +If you get CSP violation errors in the browser console, your Content Security Policy is blocking connections to the OTLP endpoint. + +Add the Collector endpoint to your CSP `connect-src` directive: + +```text +Content-Security-Policy: connect-src 'self' https://collector.example.com:4318 +``` + +::: + +:::{dropdown} Authentication failures + +If using mOTLP or a Collector with authentication requirements: + +1. Ensure your authentication credentials are valid and not expired. + +2. If using a reverse proxy, verify it's correctly forwarding the `Authorization` header: + +```nginx +proxy_set_header Authorization $http_authorization; +``` + +3. The `Authorization` header must be listed in `Access-Control-Allow-Headers` for preflight requests. + +::: + +:::{dropdown} Spans or traces not correlating correctly + +If you get disconnected spans or traces that should be related: + +1. Ensure you're using `startActiveSpan` correctly for creating parent-child span relationships. + +2. All spans in a trace should have the same resource attributes, especially `service.name`. + +3. Register instrumentations after configuring the tracer provider, not before. + +::: + +:::{dropdown} Instrumentation not capturing data + +If specific instrumentations aren't working: + +1. Ensure instrumentations are registered after the tracer provider is configured. + +2. Some instrumentations have specific browser requirements. Check the console for warnings. + +3. Register instrumentations one at a time to identify which ones are causing issues. + +::: + +:::{dropdown} Integration method issues + +If you're having issues with how you've integrated the telemetry code: + +1. Ensure the telemetry initialization happens before your application code: + +```javascript +// Top of your entry file +import { initOpenTelemetry } from './telemetry.js'; +initOpenTelemetry({...}); + +// Then your app code +import { MyApp } from './app.js'; +``` + +2. Some bundlers may remove OpenTelemetry code if it appears unused. Use `/* @preserve */` comments or configure your bundler to keep it. + +3. Verify the script path is correct and the file is being served: + +```html + + +``` + +4. If `initOpenTelemetry` is not defined, ensure your bundler is exposing it globally. For Webpack: + +```javascript +output: { + library: 'initOpenTelemetry', + libraryTarget: 'window', + libraryExport: 'default' +} +``` + +::: + +If you continue to experience issues: + +1. Ensure your target browsers support the OpenTelemetry features you're using. +2. Consult the [OpenTelemetry JavaScript documentation](https://opentelemetry.io/docs/languages/js/) for additional troubleshooting guidance. +3. Set the log level to `verbose` for maximum detail: + +```javascript +const OTEL_LOG_LEVEL = 'verbose'; +``` + +4. Start with only traces (no metrics or logs) and one instrumentation to isolate the issue. +5. Review the code examples throughout this guide and compare with your implementation. From 1e7fb1252492a70fef9228efb22c3821e849ed72 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 27 Nov 2025 13:19:35 +0100 Subject: [PATCH 09/27] Remove leak --- solutions/observability/applications/otel-rum.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 30d5c71219..4f415a5601 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -164,10 +164,7 @@ The following packages hold the necessary components to set up the base for your npm install @opentelemetry/api @opentelemetry/core @opentelemetry/resources @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http @opentelemetry/instrumentation ``` -You can then install the instrumentations that you're interested in. OpenTelemetry offers several instrumentations for browsers. For example: - -- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of an OpenTelemetry Collector where data is sent. When using {{product.observability}}, this is the {{motlp}} of an {{serverless-full}} project or the URL of a deployed [EDOT Collector](elastic-agent://reference/edot-collector/index.md). Refer to the [OTLP endpoint](#otlp-endpoint) section for more information on how to solve [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) issues. -npm install @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-user-interaction +You can then install the instrumentations that you're interested in. ## Basic configuration From 328323558dcce6d748be2067f0b0d81cd0f9125c Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Thu, 27 Nov 2025 13:23:40 +0100 Subject: [PATCH 10/27] Update solutions/observability/applications/otel-rum.md Co-authored-by: David Luna --- solutions/observability/applications/otel-rum.md | 1 - 1 file changed, 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 4f415a5601..d8c8a90eb8 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -258,7 +258,6 @@ After the dependencies are installed, define the resource for your instrumentati import { resourceFromAttributes, detectResources } from '@opentelemetry/resources'; import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector'; -// Append this in the code section const detectedResources = detectResources({ detectors: [browserDetector] }); let resource = resourceFromAttributes(OTEL_RESOURCE_ATTRIBUTES); resource = resource.merge(detectedResources); From 8282b87534168b598f0aed348facefb8367c3e74 Mon Sep 17 00:00:00 2001 From: David Luna Date: Mon, 1 Dec 2025 12:21:40 +0100 Subject: [PATCH 11/27] chore: EDOT rum doc updates (#4144) --- .../observability/applications/otel-rum.md | 164 ++++++++++-------- 1 file changed, 95 insertions(+), 69 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index d8c8a90eb8..5ef0318c77 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -30,21 +30,23 @@ Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic { ### OTLP endpoint -You have two main options for setting up an OTLP endpoint: +You need an OTLP Collector to ingest data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). -1. Use an existing OpenTelemetry Collector: If you already have a Collector deployed in your infrastructure, you can configure it to accept telemetry from browsers. This requires configuring CORS headers either directly on the Collector (if it's publicly accessible) or through a reverse proxy. +Depeding on where the collector is placed in your infrastructure you might have two setup options: -2. **Start from scratch with {{ecloud}}**: If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). This approach requires a reverse proxy to handle CORS configuration. +1. Reverse proxy (recommended): Take this approach if you're using {{motlp}} or if your collector is not publicly available. Refer to the next section for further information. -#### Use an existing OpenTelemetry Collector [use-an-existing-open-telemetry-collector] - -If you already have an OpenTelemetry Collector in your infrastructure, you can reuse it to ingest the traces, metrics, and logs generated by your instrumented web applications. +2. Setup Cross-Origin Resource Sharing (CORS): You can opt in for this setup if your collector is public and you can modify its configuration. ::::{tab-set} :::{tab-item} Reverse proxy (recommended) -If your Collector is not publicly available or you want to control where the data is coming from, use a reverse proxy to forward data from the browsers through your web application. This is the recommended method. +Use a reverse proxy to redirect the requests from your web app to the collector for these reasons: + +- There is no option in {{motlp}} to configure [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) and without the proper configuration browsers can't send data to it. +- Don't expose API keys or other authentication tokens in your web app, because they would be visible. Append the proper `Authorization` header in the proxied request to keep them valid and at the same time not leaking any secret to the public. +- You can apply rate limiting or any other mechanisms to control traffic before it reaches the collector. :::{dropdown} Example NGINX reverse proxy configuration @@ -52,14 +54,13 @@ The following snippet shows the configuration for an NGINX reverse proxy to forw ```nginx server { - listen 80 default_server; - server_name _; + # Configuration for HTTP/HTTPS goes here location / { # Take care of preflight requests if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; - add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; @@ -68,92 +69,117 @@ server { add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - + # Set the auth and proxy the request + proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; proxy_pass https://collector.example.com:4318; } } ``` - ::: ::: -:::{tab-item} Public Collector with CORS +:::{tab-item} Configure Collector for CORS + +If the collector is publicly available, you can send the telemetry data directly to it. Your collector must be available under a domain name, for example `collector.example.com:4318`, 4318 being the default port for the OTLP HTTP/JSON protocol). Your web application sends data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. + +Take these aspects into consideration: -If the Collector is publicly available, you can send the telemetry data directly to it. Your Collector should be available under a domain name, for example `collector.example.com:4318` (4318 is the default port for the OTLP HTTP/JSON protocol). Your web application should send data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. +- Every time you add a new application, you need to update the CORS configuration to add the new origin. Using a wildcard value like `https://*` is discouraged because you are allowing any website to be able to send data to the collector. A more convenient configuration is to have a wildcard per subdomains like `https://*.example.com`. +- Your collector requires an authorization header and the OpenTelemetry instrumentation is sending data directly to it. This means you should add the required header with the API key value in the instrumentation script. This script is visible to anyone that has access to the app. -To configure CORS in your collector, change the configuration in your HTTP receiver: +This is a basic EDOT Collector configuration file that activates CORS: ```yaml receivers: - # Receives data from other Collectors in Agent mode + # Receives data from other Collectors in Agent mode or OTEL SDKs otlp: protocols: grpc: - endpoint: 0.0.0.0:4317 # Listen on all interfaces + endpoint: 0.0.0.0:4317 # Listen on all interfaces + auth: + authenticator: apikeyauth http: - endpoint: 0.0.0.0:4318 # Listen on all interfaces - # Configure CORS for RUM + endpoint: 0.0.0.0:4318 # Listen on all interfaces + auth: + authenticator: apikeyauth + # configure CORS for RUM + # ref: https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration cors: allowed_origins: - - http://webapp.example.com - - https://webapp.example.com + - http://*.example.com + - https://*.example.com +connectors: + elasticapm: {} # Elastic APM Connector + +processors: + batch: + send_batch_size: 1000 + timeout: 1s + send_batch_max_size: 1500 + batch/metrics: + send_batch_max_size: 0 # Explicitly set to 0 to avoid splitting metrics requests + timeout: 1s + elastictrace: {} # Elastic Trace Processor + +exporters: + debug: {} + elasticsearch/otel: + endpoints: + - http://elasticsearch:9200 + user: elastic + password: ${ES_LOCAL_PASSWORD} + tls: + insecure_skip_verify: true + mapping: + mode: otel + +service: + pipelines: + metrics: + receivers: [otlp] + processors: [batch/metrics] + exporters: [debug, elasticsearch/otel] + logs: + receivers: [otlp] + processors: [batch] + exporters: [debug, elasticapm, elasticsearch/otel] + traces: + receivers: [otlp] + processors: [batch, elastictrace] + exporters: [debug, elasticapm, elasticsearch/otel] + metrics/aggregated-otel-metrics: + receivers: + - elasticapm + processors: [] # No processors defined in the original for this pipeline + exporters: + - debug + - elasticsearch/otel + extensions: [apikeyauth] # Enable auth extension + +extensions: + # Auth via Elastic API key + # ref: https://www.elastic.co/docs/reference/edot-collector/config/authentication-methods + apikeyauth: + endpoint: "http://elasticsearch:9200" + application_privileges: + - application: "apm" + privileges: ["config_agent:read"] + resources: ["*"] + cache: + capacity: 1000 + ttl: "5m" + pbkdf2_iterations: 10000 + key_headers: [] ``` -For details on the configuration, refer to the [OpenTelemetry Collector HTTP server configuration](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration). - ::: :::: -#### Start with {{ecloud}} - -If you're new to {{observability}} or want to start from scratch, create a new {{ecloud}} hosted deployment (ECH) or a {{serverless-short}} project: - -- To create a new ECH deployment, follow the guidelines in [Deploy and manage Elastic Cloud hosted](/deploy-manage/deploy/elastic-cloud/cloud-hosted.md). -- To create a {{serverless-short}} project, follow the guidelines in [Get started with Elastic Observability](/solutions/observability/get-started.md). - -Both options come with a [Managed OTLP Endpoint (mOTLP)](opentelemetry://reference/motlp.md). However, the mOTLP endpoint cannot be configured for CORS. - -:::{dropdown} Example NGINX reverse proxy configuration for mOTLP - -The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the mOTLP endpoint from the origin `webapp.example.com`: - -```nginx -server { - listen 80 default_server; - server_name _; - location / { - # Take care of preflight requests - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; - add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type,Authorization' always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - - add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Language,Content-Type,Authorization' always; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Authorization $http_authorization; - - proxy_pass https://:443; - } -} -``` - -::: - ## Installation OpenTelemetry packages for web instrumentation are published to npm. You can install them with the package manager of your choice. From c63e637457f47f70b4863fe2688171a0de9f76c3 Mon Sep 17 00:00:00 2001 From: David Luna Date: Mon, 1 Dec 2025 12:45:56 +0100 Subject: [PATCH 12/27] chore: fix typo and add comments --- solutions/observability/applications/otel-rum.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 5ef0318c77..9157b9826e 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -74,7 +74,8 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # Set the auth and proxy the request + # Set the Authorization header in the proxyed request. It's recommended to get it from a secrets manager + # instead of harcoding it here. proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; proxy_pass https://collector.example.com:4318; } @@ -85,7 +86,7 @@ server { :::{tab-item} Configure Collector for CORS -If the collector is publicly available, you can send the telemetry data directly to it. Your collector must be available under a domain name, for example `collector.example.com:4318`, 4318 being the default port for the OTLP HTTP/JSON protocol). Your web application sends data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. +If the collector is publicly available, you can send the telemetry data directly to it. Your collector must be available under a domain name, for example `collector.example.com:4318` (4318 being the default port for the OTLP HTTP/JSON protocol). Your web application sends data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. Take these aspects into consideration: @@ -107,9 +108,7 @@ receivers: endpoint: 0.0.0.0:4318 # Listen on all interfaces auth: authenticator: apikeyauth - # configure CORS for RUM - # ref: https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration - cors: + cors: # Configure CORS for RUM. ref: https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration allowed_origins: - http://*.example.com - https://*.example.com From 3258e1bd3f55c5e5b3b91ccb0912aaa03f52083b Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Tue, 2 Dec 2025 09:15:29 +0100 Subject: [PATCH 13/27] Apply suggestions from code review Co-authored-by: Gil Raphaelli Co-authored-by: Vignesh Shanmugam --- solutions/observability/applications/otel-rum.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 9157b9826e..8f5f074024 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -13,26 +13,26 @@ products: # OpenTelemetry for Real User Monitoring (RUM) :::{important} -Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in **Technical Preview**. This feature is provided as-is and has [limitations](#known-limitations). It should not be used in production environments. Elastic provides best-effort support for Technical Preview features and they are not covered under standard SLAs. +Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in **Technical Preview**. This feature may be changed or removed in a future release and has [limitations](#known-limitations). It should not be used in production environments. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. ::: -This documentation outlines the process for instrumenting your web application with OpenTelemetry browser instrumentation, using {{product.observability}} as the backend. Unlike the [EDOT SDKs](opentelemetry://reference/edot-sdks/index.md), this approach uses upstream OpenTelemetry JavaScript packages directly. The following sections detail the required components and their proper configuration to acquire traces, logs, and metrics from the application to visualize them within {{kib}}. +This documentation outlines the process for instrumenting your web application with OpenTelemetry browser instrumentation for use with {{product.observability}}. This approach uses upstream OpenTelemetry packages directly unlike the [EDOT SDKs](opentelemetry://reference/edot-sdks/index.md). The following sections detail the required components and their proper configuration to acquire traces, logs, and metrics from the application to visualize them within {{kib}}. -While this guide uses upstream OpenTelemetry instrumentation, you can also use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) components as part of your data ingestion pipeline. +While this guide uses upstream OpenTelemetry instrumentation, you can use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) components as part of your data ingestion pipeline. ## Prerequisites This guide assumes you're using an {{product.observability}} deployment. You can use an existing one or set up a new one. If you're new to {{product.observability}}, follow the guidelines in [Get started with {{product.observability}}](/solutions/observability/get-started.md). :::{warning} -Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents in the same application process might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. +Avoid using OTel RUM agent alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents in the same application process might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. ::: ### OTLP endpoint -You need an OTLP Collector to ingest data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). +You need an OpenTelemetry Collector to receive data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). -Depeding on where the collector is placed in your infrastructure you might have two setup options: +Depending on where the collector is placed in your infrastructure you might have two setup options: 1. Reverse proxy (recommended): Take this approach if you're using {{motlp}} or if your collector is not publicly available. Refer to the next section for further information. @@ -91,7 +91,7 @@ If the collector is publicly available, you can send the telemetry data directly Take these aspects into consideration: - Every time you add a new application, you need to update the CORS configuration to add the new origin. Using a wildcard value like `https://*` is discouraged because you are allowing any website to be able to send data to the collector. A more convenient configuration is to have a wildcard per subdomains like `https://*.example.com`. -- Your collector requires an authorization header and the OpenTelemetry instrumentation is sending data directly to it. This means you should add the required header with the API key value in the instrumentation script. This script is visible to anyone that has access to the app. +- Your collector requires an `Authorization` header and the OpenTelemetry instrumentation is sending data directly to it. This means you should add the required header with the API key value in the instrumentation script. This script is visible to anyone that has access to the app. This is a basic EDOT Collector configuration file that activates CORS: From 7ca9013d0a73fafd12baf4eca8db68495b1377aa Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 3 Dec 2025 11:26:36 +0100 Subject: [PATCH 14/27] chore: remove collector config --- .../observability/applications/otel-rum.md | 190 ++++++------------ 1 file changed, 56 insertions(+), 134 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 9157b9826e..2279e82fa3 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -30,27 +30,16 @@ Avoid using OpenTelemetry alongside any other {{apm-agent}}, including Elastic { ### OTLP endpoint -You need an OTLP Collector to ingest data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). +You need a OTLP endpoint in order to ingest data from the OpenTelemetry RUM instrumentation. Also if you want to get the most of {{product.observability}} such endpoint should belong to an [EDOT Collector](elastic-agent://reference/edot-collector/index.md) in gateway mode. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self hosted stack or your deployment does not have the {{motlp}} you should configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). -Depeding on where the collector is placed in your infrastructure you might have two setup options: +Once you have your OTLP endpoint ready the recommendation is to set up a reverse proxy to forward the telemetry from your web application origin to the collector. The primary reasons for having such set up are: -1. Reverse proxy (recommended): Take this approach if you're using {{motlp}} or if your collector is not publicly available. Refer to the next section for further information. +- EDOT Collector requires an `Authorization` header with an ApiKey in order to accept OTLP exports. Setting up the required key in a web application makes it publicly available and its not advised to have this kind of secrets available to anyone with a browser. +- You can apply rate limiting or any other mechanisms to control traffic before it reaches the EDOT Collector. +- If you have set up your own EDOT Collector it's likely to have a different origin than your web application. In this scenario you will have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in EDOT Collector configuration file. This procerdure can be cumbersome if you have to manage a large numned of applications. -2. Setup Cross-Origin Resource Sharing (CORS): You can opt in for this setup if your collector is public and you can modify its configuration. -::::{tab-set} - -:::{tab-item} Reverse proxy (recommended) - -Use a reverse proxy to redirect the requests from your web app to the collector for these reasons: - -- There is no option in {{motlp}} to configure [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) and without the proper configuration browsers can't send data to it. -- Don't expose API keys or other authentication tokens in your web app, because they would be visible. Append the proper `Authorization` header in the proxied request to keep them valid and at the same time not leaking any secret to the public. -- You can apply rate limiting or any other mechanisms to control traffic before it reaches the collector. - -:::{dropdown} Example NGINX reverse proxy configuration - -The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the Collector located at `collector.example.com` from the origin `webapp.example.com`: +The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the EDOT Collector located at `collector.example.com` from the origin `webapp.example.com`: ```nginx server { @@ -59,7 +48,7 @@ server { # Take care of preflight requests if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; - add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set te allowed origins add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Content-Type' 'text/plain charset=UTF-8'; @@ -67,117 +56,18 @@ server { return 204; } - add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set te allowed origins add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # Set the Authorization header in the proxyed request. It's recommended to get it from a secrets manager - # instead of harcoding it here. - proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; + proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; # Set the auth header here. It's recommended to get it from a secrets manager. proxy_pass https://collector.example.com:4318; } } ``` -::: -::: - -:::{tab-item} Configure Collector for CORS - -If the collector is publicly available, you can send the telemetry data directly to it. Your collector must be available under a domain name, for example `collector.example.com:4318` (4318 being the default port for the OTLP HTTP/JSON protocol). Your web application sends data from its own origin `webapp.example.com` to a different one, and [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) (CORS) should be configured so browsers allow sending data to a different origin. - -Take these aspects into consideration: - -- Every time you add a new application, you need to update the CORS configuration to add the new origin. Using a wildcard value like `https://*` is discouraged because you are allowing any website to be able to send data to the collector. A more convenient configuration is to have a wildcard per subdomains like `https://*.example.com`. -- Your collector requires an authorization header and the OpenTelemetry instrumentation is sending data directly to it. This means you should add the required header with the API key value in the instrumentation script. This script is visible to anyone that has access to the app. - -This is a basic EDOT Collector configuration file that activates CORS: - -```yaml -receivers: - # Receives data from other Collectors in Agent mode or OTEL SDKs - otlp: - protocols: - grpc: - endpoint: 0.0.0.0:4317 # Listen on all interfaces - auth: - authenticator: apikeyauth - http: - endpoint: 0.0.0.0:4318 # Listen on all interfaces - auth: - authenticator: apikeyauth - cors: # Configure CORS for RUM. ref: https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration - allowed_origins: - - http://*.example.com - - https://*.example.com -connectors: - elasticapm: {} # Elastic APM Connector - -processors: - batch: - send_batch_size: 1000 - timeout: 1s - send_batch_max_size: 1500 - batch/metrics: - send_batch_max_size: 0 # Explicitly set to 0 to avoid splitting metrics requests - timeout: 1s - elastictrace: {} # Elastic Trace Processor - -exporters: - debug: {} - elasticsearch/otel: - endpoints: - - http://elasticsearch:9200 - user: elastic - password: ${ES_LOCAL_PASSWORD} - tls: - insecure_skip_verify: true - mapping: - mode: otel - -service: - pipelines: - metrics: - receivers: [otlp] - processors: [batch/metrics] - exporters: [debug, elasticsearch/otel] - logs: - receivers: [otlp] - processors: [batch] - exporters: [debug, elasticapm, elasticsearch/otel] - traces: - receivers: [otlp] - processors: [batch, elastictrace] - exporters: [debug, elasticapm, elasticsearch/otel] - metrics/aggregated-otel-metrics: - receivers: - - elasticapm - processors: [] # No processors defined in the original for this pipeline - exporters: - - debug - - elasticsearch/otel - extensions: [apikeyauth] # Enable auth extension - -extensions: - # Auth via Elastic API key - # ref: https://www.elastic.co/docs/reference/edot-collector/config/authentication-methods - apikeyauth: - endpoint: "http://elasticsearch:9200" - application_privileges: - - application: "apm" - privileges: ["config_agent:read"] - resources: ["*"] - cache: - capacity: 1000 - ttl: "5m" - pbkdf2_iterations: 10000 - key_headers: [] -``` - -::: -:::: ## Installation @@ -195,7 +85,7 @@ You can then install the instrumentations that you're interested in. The minimal configuration you need to instrument your web application with OpenTelemetry includes: -- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of an OpenTelemetry Collector where data is sent. When using {{product.observability}}, this is the ingest endpoint of an {{serverless-full}} project or the URL of a deployed [EDOT Collector](elastic-agent://reference/edot-collector/index.md). It is likely that the Collector endpoint is of a different origin. If that's the case, you will encounter [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) issues. Refer to the [OTLP endpoint](#otlp-endpoint) section for more information on how to solve it with different approaches. +- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of the proxy you have configured in the [OTLP endpoint](#otlp-endpoint) section. - **OTEL_RESOURCE_ATTRIBUTES**: A JavaScript object that will be used to define the resource. The most important attributes to define are: - `service.name` (string): Name of the application you're instrumenting. - `service.version` (string, optional): A string representing the version or build of your app. @@ -239,7 +129,7 @@ import { diagLogLevelFromString } from '@opentelemetry/core'; // Set the configuration options const OTEL_LOG_LEVEL = 'info'; // Possible values: error, warn, info, debug, verbose -const OTEL_EXPORTER_OTLP_ENDPOINT = 'https://host:port'; +const OTEL_EXPORTER_OTLP_ENDPOINT = 'https://proxy.example.com'; // Point to your reverse proxy configured in the section above const OTEL_RESOURCE_ATTRIBUTES = { 'service.name': 'my-web-app', 'service.version': '1.2.3', @@ -299,15 +189,20 @@ To enable instrumentations to transmit traces and allow for the creation of cust - **Resource**: The resource to be associated with the spans created by the tracers (previously defined). - **Span Processor**: A component that manages the spans generated by the tracers and forwards them to a [SpanExporter](https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter). The exporter should be configured to direct data to an endpoint designated for traces. - **Span Exporter**: Manages the transmission of spans to the Collector. +- **Context Manager**: A crucial mechanism for managing the active Span context across asynchronous operations and threads. It ensures that when a new Span is created, it correctly identifies its parent Span, thereby maintaining the integrity of the trace structure. For this part, you need to install the following dependencies: - `@opentelemetry/sdk-trace-base`: This package contains all the core components to set up tracing regardless of the runtime they're running in (Node.js or browser). - `@opentelemetry/sdk-trace-web`: This package contains a tracer provider that runs in web browsers. - `@opentelemetry/exporter-trace-otlp-http`: This package contains the exporter for the HTTP/JSON protocol. +- `@opentelemetry/context-zone`: This package contains a context manager which uses on [zone.js](https://www.npmjs.com/package/zone.js) library. ```bash -npm install @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http +npm install @opentelemetry/sdk-trace-base\ + @opentelemetry/sdk-trace-web\ + @opentelemetry/exporter-trace-otlp-http\ + @opentelemetry/context-zone ``` Once the dependencies are installed, you can configure and register a tracer provider with the following code: @@ -316,6 +211,7 @@ Once the dependencies are installed, you can configure and register a tracer pro ```javascript import { trace } from '@opentelemetry/api'; +import { ZoneContextManager } from '@opentelemetry/context-zone'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; @@ -332,7 +228,7 @@ const tracerProvider = new WebTracerProvider({ })), ], }); -trace.setGlobalTracerProvider(tracerProvider); +tracerProvider.register({ contextManager: new ZoneContextManager() }); ``` ::: @@ -343,10 +239,6 @@ Now you can use the OpenTelemetry API to get a tracer and start creating your ow :::::{step} Configure metrics -:::{note} -Metrics from browser-based RUM are primarily used for aggregate analysis across many browser instances. The data becomes useful when processed in the Collector or backend. Consider whether browser-side metrics collection aligns with your observability goals before enabling this signal. -::: - Similar to traces, you should configure a [MeterProvider](https://opentelemetry.io/docs/concepts/signals/metrics/#meter-provider) for metrics. This provider necessitates the inclusion of several key components: - **Resource**: The resource to be associated with the metrics created by the meters. @@ -395,7 +287,13 @@ metrics.setGlobalMeterProvider(meterProvider); :::::{step} Configure logs -For RUM log management with OpenTelemetry JavaScript, configure the **Provider** for generation (instantiation, resource, logger creation) and the **Exporter** for transmission (endpoint, headers, interval/batching, registration). +Like traces and metrics you need to configure a [LoggerProvider](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider) if you want to record relevant events that are happening in your application via API or instrumenations. This provider necessitates the inclusion of several key components: + +- **Resource**: The resource to be associated with the log records created by the loggers. +- **LogRecord Processor**: A component that manages the log records generated by the loggers and forwards them to a [LogExporter](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter). The exporter should be configured to direct data to an endpoint designated for logs. +- **Log Exporter**: Manages the transmission of log records to the Collector. + + For this part, you need to install the following dependencies: @@ -453,7 +351,12 @@ Install the following dependencies: To install the dependencies, run the following command: ```bash -npm install @opentelemetry/instrumentation @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-user-interaction +npm install @opentelemetry/instrumentation\ + @opentelemetry/instrumentation-document-load\ + @opentelemetry/instrumentation-long-task\ + @opentelemetry/instrumentation-fetch\ + @opentelemetry/instrumentation-xml-http-request\ + @opentelemetry/instrumentation-user-interaction ``` After the dependencies are installed, you can configure and register instrumentations with the following code: @@ -486,12 +389,30 @@ registerInstrumentations({ :::::{step} Complete setup script -All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it can be wrapped within a function that accepts the configuration as a parameter, allowing you to reuse the setup across different UIs. +All these pieces together give you a complete setup of all the signals for your web site or application. For conveninence its better to have it in a separate file which can be named `telemetry.js`. This file should export a function that accepts the configuration allowing you to reuse the setup across different UIs. To install all the dependencies needed for the complete setup, run the following command: ```bash -npm install @opentelemetry/api @opentelemetry/core @opentelemetry/resources @opentelemetry/browser-detector @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/instrumentation @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-long-task @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-user-interaction +npm install @opentelemetry/api\ + @opentelemetry/core\ + @opentelemetry/resources\ + @opentelemetry/browser-detector\ + @opentelemetry/sdk-trace-base\ + @opentelemetry/sdk-trace-web\ + @opentelemetry/context-zone\ + @opentelemetry/exporter-trace-otlp-http\ + @opentelemetry/sdk-metrics\ + @opentelemetry/exporter-metrics-otlp-http\ + @opentelemetry/api-logs\ + @opentelemetry/sdk-logs\ + @opentelemetry/exporter-logs-otlp-http\ + @opentelemetry/instrumentation\ + @opentelemetry/instrumentation-document-load\ + @opentelemetry/instrumentation-long-task\ + @opentelemetry/instrumentation-fetch\ + @opentelemetry/instrumentation-xml-http-request\ + @opentelemetry/instrumentation-user-interaction ``` After the dependencies are installed, you can wrap the setup in a function with the following code: @@ -504,6 +425,7 @@ import { diag, DiagConsoleLogger, trace, metrics } from '@opentelemetry/api'; import { diagLogLevelFromString } from '@opentelemetry/core'; import { resourceFromAttributes, detectResources } from '@opentelemetry/resources'; import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector'; +import { ZoneContextManager } from '@opentelemetry/context-zone'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; @@ -552,7 +474,7 @@ export function initOpenTelemetry(config) { })), ], }); - trace.setGlobalTracerProvider(tracerProvider); + tracerProvider.register({ contextManager: new ZoneContextManager() }) // Metrics signal setup const metricsEndpoint = `${config.endpoint}/v1/metrics`; @@ -596,7 +518,7 @@ export function initOpenTelemetry(config) { ## Integrate with your application -With the setup done, it's time to apply it to your web application. You can choose from two main approaches: +With the setup placed in a file, it's time to apply it to your web application. You can choose from two main approaches: 1. **Import the code**: Use your build tooling to manage the dependencies and integrate the code into the application bundle. This is the simplest option and is recommended, although it increases the size of your application bundle. @@ -616,7 +538,7 @@ import { initOpenTelemetry } from 'telemetry.js'; initOpenTelemetry({ logLevel: 'info', - endpoint: 'https://host:port/', + endpoint: 'https://proxy.example.com', resourceAttributes: { 'service.name': 'my-web-app', 'service.version': '1', From f8e195db49c2ad515020a51f3b64b15a66132c36 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 3 Dec 2025 18:03:53 +0100 Subject: [PATCH 15/27] chore: add bundlers config examples --- .../observability/applications/otel-rum.md | 67 +++++++++++++++++-- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 2279e82fa3..92e2b5a52c 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -526,14 +526,13 @@ With the setup placed in a file, it's time to apply it to your web application. ### Import the code -This approach is recommended. The build tooling manages the dependencies and integrates the code into the application bundle. This might increase the size of your application bundle. +This approach is recommended and specially if you're using a web framemork. The build tooling manages the dependencies and integrates the code into the application bundle. This approach increases the size of your application bundle. For example, if you're using Webpack, you can import the code like this: :::{dropdown} Example: Import telemetry.js in your app ```javascript -// file: app.(js|ts) entry point of your application import { initOpenTelemetry } from 'telemetry.js'; initOpenTelemetry({ @@ -544,15 +543,69 @@ initOpenTelemetry({ 'service.version': '1', } }); +``` +::: + +If you're using a framework there are some suitable places for it depending on which one you're using: + +- React: You can create a component which initializes the instrumentation when mounted. The component should be added as child of the `` component. +- VueJs: You can create a plugin which doesn the initialization when installed in the app. Check how to create plugins in [VueJS docs](https://vuejs.org/guide/reusability/plugins.html). +- Angular: You can add the initialiation snipped in `./src/main.ts` which is the entry point of the application. More details in [Angular docs](https://v17.angular.io/guide/file-structure#application-source-files). + + +### Bundle in a file + +You can use a bundler like webpack, rollup or vite to generate a separate JavaScript file. + +::::{tab-set} + +:::{tab-item} Wepack + -// Your app code +This is an exmaple of a `webpack.config.js` to author a library as described in [the docs](https://webpack.js.org/guides/author-libraries/) in UMD format. +```javascript +const path = require('path'); + +module.exports = { + entry: './path/to/telemetry.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'telemetry.umd.js', + library: { + type: 'umd' + } + }, +}; ``` +::: +:::{tab-item} Vite +This is an exmaple of a `vite.config.js` file in [library mode](https://vite.dev/guide/build#library-mode) to get a bundle in UMD format. +```javascript +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; + +const __dirname = dirname(fileURLToPath(import.meta.url)) +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, './path/to/telemetry.js'), + formats: ['umd'], + name: 'telemetry', + fileName: (format) => `telemetry.${format}.js` + }, + sourcemap: true, + } +}); + +``` ::: -### Bundle in a file +:::: + -You can use a bundler to generate a separate JavaScript file. Place the file within the application's assets folder and include it in the `` section of the HTML page. +Place the file within the application's assets folder and include it in the `` section of the HTML page. Assuming the JavaScript files reside in a folder named "js", the HTML file structure looks like this: @@ -562,11 +615,11 @@ Assuming the JavaScript files reside in a folder named "js", the HTML file struc - + - - … - - - - - + + ``` +::: +:::{tab-item} Asynchronous / Non-Blocking Pattern + +Loading the script asynchronously ensures the agent script will not block other resources on the page, however, it will still block browsers onload event. +```html + +``` + +Even though this is the recommended pattern, there is a caveat to be aware of. Because the downloading and initializing of the instrumentations happens asynchronously, distributed tracing will not work for requests that occur before the agent is initialized. ::: +:::: + ## Manual instrumentation to extend your telemetry From d4dc21eb5483f39d01611e80e2d584dc8ed6ce54 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 3 Dec 2025 19:25:38 +0100 Subject: [PATCH 17/27] chore: review troubleshooting section --- .../observability/applications/otel-rum.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 3f3398abcf..500683f1c2 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -616,7 +616,7 @@ Add a ` ``` -Even though this is the recommended pattern, there is a caveat to be aware of. Because the downloading and initializing of the instrumentations happens asynchronously, distributed tracing will not work for requests that occur before the agent is initialized. +Even though this is the recommended pattern, there is a caveat to be aware of. Because the downloading and initializing of the instrumentations happens asynchronously, distributed tracing does not work for requests that occur before the agent is initialized. ::: :::: @@ -715,12 +715,11 @@ connect-src collector.example.com:4318/v1/traces ### Cross-Origin Resource Sharing (CORS) -If your website and Collector are hosted at a different origin, your browser might block the requests going out to your Collector. To solve this, you need to configure special headers for Cross-Origin Resource Sharing (CORS). This configuration depends on the solution you want to adopt and is described in the [OTLP endpoint](#otlp-endpoint) section. +If your website and the configured endpoing have a differene origin your browser might block the export requests. If you have followed the instructions in the [OTLP endpoint](#otlp-endpoint) section you've already done the necessary setup for CORS. Otherwise you need to configure special headers for Cross-Origin Resource Sharing (CORS) in te receiveng endpoint. + ## Known limitations -- The Managed OTLP endpoint (mOTLP) cannot be directly configured for CORS. A reverse proxy is required. -- {{apm-server}} does not support CORS configuration for OTLP endpoints. - Metrics from browser-based RUM might have limited utility compared to backend metrics. - Some OpenTelemetry instrumentations for browsers are still experimental. - Performance impact on the browser should be monitored, especially when using multiple instrumentations. @@ -789,7 +788,7 @@ curl -X POST https://your-proxy/v1/traces \ 3. Review proxy logs for errors or blocked requests. -4. Ensure the proxy can reach the backend Collector or mOTLP endpoint. +4. Ensure the proxy can reach the backend EDOT Collector or mOTLP endpoint. ::: @@ -797,7 +796,7 @@ curl -X POST https://your-proxy/v1/traces \ If your OpenTelemetry setup isn't initializing correctly: -1. Ensure `OTEL_EXPORTER_OTLP_ENDPOINT` doesn't include the signal path (like `/v1/traces`). The SDK adds this automatically: +1. Ensure `OTEL_EXPORTER_OTLP_ENDPOINT` doesn't include the signal path (like `/v1/traces`). The script provided adds this automatically: ```javascript // Correct @@ -886,14 +885,14 @@ Content-Security-Policy: connect-src 'self' https://collector.example.com:4318 :::{dropdown} Authentication failures -If using mOTLP or a Collector with authentication requirements: +If using mOTLP or an EDOT Collector with authentication requirements: 1. Ensure your authentication credentials are valid and not expired. 2. If using a reverse proxy, verify it's correctly forwarding the `Authorization` header: ```nginx -proxy_set_header Authorization $http_authorization; +proxy_set_header Authorization 'ApiKey _elastic_api_key_'; ``` 3. The `Authorization` header must be listed in `Access-Control-Allow-Headers` for preflight requests. @@ -945,7 +944,7 @@ import { MyApp } from './app.js'; ```html - + ``` 4. If `initOpenTelemetry` is not defined, ensure your bundler is exposing it globally. For Webpack: From fd95f38ace4e1a6a6e553e1dec8bd01dd0a4c251 Mon Sep 17 00:00:00 2001 From: David Luna Date: Thu, 4 Dec 2025 09:31:17 +0100 Subject: [PATCH 18/27] chore: fix some style warnings --- solutions/observability/applications/otel-rum.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 500683f1c2..1cebc9e110 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -30,13 +30,13 @@ Avoid using OTel RUM agent alongside any other {{apm-agent}}, including Elastic ### OTLP endpoint -You need a OTLP endpoint in order to ingest data from the OpenTelemetry RUM instrumentation. Also if you want to get the most of {{product.observability}} such endpoint should belong to an [EDOT Collector](elastic-agent://reference/edot-collector/index.md) in gateway mode. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self hosted stack or your deployment does not have the {{motlp}} you should configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). +You need a OTLP endpoint to ingest data from the OpenTelemetry RUM instrumentation. Also if you want to get the most of {{product.observability}} such endpoint should belong to an [EDOT Collector](elastic-agent://reference/edot-collector/index.md) in gateway mode. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self hosted stack or your deployment does not have the {{motlp}} you should configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). Once you have your OTLP endpoint ready the recommendation is to set up a reverse proxy to forward the telemetry from your web application origin to the collector. The primary reasons for having such set up are: -- EDOT Collector requires an `Authorization` header with an ApiKey in order to accept OTLP exports. Setting up the required key in a web application makes it publicly available and its not advised to have this kind of secrets available to anyone with a browser. +- EDOT Collector requires an `Authorization` header with an ApiKey to accept OTLP exports. Setting up the required key in a web application makes it publicly available and its not advised to have this kind of secrets available to anyone with a browser. - You can apply rate limiting or any other mechanisms to control traffic before it reaches the EDOT Collector. -- If you have set up your own EDOT Collector it's likely to have a different origin than your web application. In this scenario you will have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in EDOT Collector configuration file. This procerdure can be cumbersome if you have to manage a large numned of applications. +- If you have set up your own EDOT Collector it's likely to have a different origin than your web application. In this scenario you have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in EDOT Collector configuration file. This procerdure can be cumbersome if you have to manage a large numned of applications. The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the EDOT Collector located at `collector.example.com` from the origin `webapp.example.com`: @@ -275,7 +275,7 @@ metrics.setGlobalMeterProvider(meterProvider); :::::{step} Configure logs -Like traces and metrics you need to configure a [LoggerProvider](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider) if you want to record relevant events that are happening in your application via API or instrumenations. This provider necessitates the inclusion of several key components: +Like traces and metrics you need to configure a [LoggerProvider](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider) if you want to record relevant events that are happening in your application using the API or instrumenations. This provider necessitates the inclusion of several key components: - **Resource**: The resource to be associated with the log records created by the loggers. - **LogRecord Processor**: A component that manages the log records generated by the loggers and forwards them to a [LogExporter](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter). The exporter should be configured to direct data to an endpoint designated for logs. From 6e949caa0f5aabd99d9518be5646415f35498549 Mon Sep 17 00:00:00 2001 From: David Luna Date: Fri, 5 Dec 2025 10:36:30 +0100 Subject: [PATCH 19/27] Apply suggestions from code review Co-authored-by: Christoph Heger --- solutions/observability/applications/otel-rum.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 1cebc9e110..98dda9ffe5 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -36,7 +36,7 @@ Once you have your OTLP endpoint ready the recommendation is to set up a reverse - EDOT Collector requires an `Authorization` header with an ApiKey to accept OTLP exports. Setting up the required key in a web application makes it publicly available and its not advised to have this kind of secrets available to anyone with a browser. - You can apply rate limiting or any other mechanisms to control traffic before it reaches the EDOT Collector. -- If you have set up your own EDOT Collector it's likely to have a different origin than your web application. In this scenario you have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in EDOT Collector configuration file. This procerdure can be cumbersome if you have to manage a large numned of applications. +- If you have set up your own EDOT Collector it's likely to have a different origin than your web application. In this scenario you have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in EDOT Collector configuration file. This procerdure can be cumbersome if you have to manage a large number of applications. The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the EDOT Collector located at `collector.example.com` from the origin `webapp.example.com`: @@ -48,7 +48,7 @@ server { # Take care of preflight requests if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; - add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set te allowed origins + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set the allowed origins for preflight requests add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Content-Type' 'text/plain charset=UTF-8'; @@ -56,7 +56,7 @@ server { return 204; } - add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set te allowed origins + add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set the allowed origins for requests add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; proxy_http_version 1.1; From e04a67dc8009aa3ff17a75614a2dd4eb7e40ba3c Mon Sep 17 00:00:00 2001 From: David Luna Date: Tue, 9 Dec 2025 17:02:43 +0100 Subject: [PATCH 20/27] chore: apply suggestions --- .../observability/applications/otel-rum.md | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 98dda9ffe5..f4444eabb2 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -221,7 +221,7 @@ tracerProvider.register({ contextManager: new ZoneContextManager() }); ::: -Now you can use the OpenTelemetry API to get a tracer and start creating your own spans. Instrumentations can also do it after you register them. +Now you can use the OpenTelemetry API to get a tracer and start creating your own spans. Instrumentations are also using OpenTelemetry API to get tracers and send telemetry data. Registering intrumentations after having the tracer provider set up ensures they will have the right tracers when requested to the API. ::::: @@ -330,21 +330,20 @@ With the OpenTelemetry SDK, resource attributes, and exporters already configure Install the following dependencies: - `@opentelemetry/instrumentation`: This package contains the core components of instrumentations along with some utilities. -- `@opentelemetry/instrumentation-document-load`: This instrumentation package measures the time it took the document to load and also the load timings of its resources. More info at [instrumentation-document-load](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load). -- `@opentelemetry/instrumentation-long-task`: This instrumentation gathers information about long tasks being executed in your browser, helping to spot issues like unresponsive UI in your web application. More info at [instrumentation-long-task](https://www.npmjs.com/package/@opentelemetry/instrumentation-long-task). -- `@opentelemetry/instrumentation-fetch`: This instrumentation keeps track of your web application requests made through the Fetch API. More info at [instrumentation-fetch](https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch). -- `@opentelemetry/instrumentation-xml-http-request`: This instrumentation keeps track of your web application requests made through the XMLHttpRequest API. More info at [instrumentation-xml-http-request](https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request). -- `@opentelemetry/instrumentation-user-interaction`: This instrumentation measures user interactions in your web application. More info at [instrumentation-user-interaction](https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction). +- `@opentelemetry/auto-instrumentations-web`: This package contains the more common instrumentations for web apps. Which are: + - `@opentelemetry/instrumentation-document-load`: This instrumentation package measures the time it took the document to load and also the load timings of its resources. More info at [instrumentation-document-load](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load). + - `@opentelemetry/instrumentation-fetch`: This instrumentation keeps track of your web application requests made through the Fetch API. More info at [instrumentation-fetch](https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch). + - `@opentelemetry/instrumentation-xml-http-request`: This instrumentation keeps track of your web application requests made through the XMLHttpRequest API. More info at [instrumentation-xml-http-request](https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request). + - `@opentelemetry/instrumentation-user-interaction`: This instrumentation measures user interactions in your web application. More info at [instrumentation-user-interaction](https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction). +- `@opentelemetry/instrumentation-long-task`: This instrumentation is not part of the auto instrumentations package. It gathers information about long tasks being executed in your browser, helping to spot issues like unresponsive UI in your web application. More info at [instrumentation-long-task](https://www.npmjs.com/package/@opentelemetry/instrumentation-long-task). + To install the dependencies, run the following command: ```bash npm install @opentelemetry/instrumentation\ - @opentelemetry/instrumentation-document-load\ - @opentelemetry/instrumentation-long-task\ - @opentelemetry/instrumentation-fetch\ - @opentelemetry/instrumentation-xml-http-request\ - @opentelemetry/instrumentation-user-interaction + @opentelemetry/auto-instrumentations-web\ + @opentelemetry/instrumentation-long-task ``` After the dependencies are installed, you can configure and register instrumentations with the following code: @@ -353,20 +352,15 @@ After the dependencies are installed, you can configure and register instrumenta ```javascript import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; -import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; +import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; import { LongTaskInstrumentation } from '@opentelemetry/instrumentation-long-task'; -import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction' -import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; + // Register instrumentations registerInstrumentations({ instrumentations: [ - new DocumentLoadInstrumentation(), + getWebAutoInstrumentations(), new LongTaskInstrumentation(), - new FetchInstrumentation(), - new XMLHttpRequestInstrumentation(), - new UserInteractionInstrumentation(), ], }); ``` @@ -396,11 +390,8 @@ npm install @opentelemetry/api\ @opentelemetry/sdk-logs\ @opentelemetry/exporter-logs-otlp-http\ @opentelemetry/instrumentation\ - @opentelemetry/instrumentation-document-load\ - @opentelemetry/instrumentation-long-task\ - @opentelemetry/instrumentation-fetch\ - @opentelemetry/instrumentation-xml-http-request\ - @opentelemetry/instrumentation-user-interaction + @opentelemetry/auto-instrumentations-web\ + @opentelemetry/instrumentation-long-task ``` After the dependencies are installed, you can wrap the setup in a function with the following code: @@ -423,11 +414,8 @@ import { logs, SeverityNumber } from '@opentelemetry/api-logs'; import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; -import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; +import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; import { LongTaskInstrumentation } from '@opentelemetry/instrumentation-long-task'; -import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'; -import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; const initDone = Symbol('OTEL initialized'); @@ -488,11 +476,8 @@ export function initOpenTelemetry(config) { // Register instrumentations registerInstrumentations({ instrumentations: [ - new DocumentLoadInstrumentation(), + getWebAutoInstrumentations(), new LongTaskInstrumentation(), - new FetchInstrumentation(), - new XMLHttpRequestInstrumentation(), - new UserInteractionInstrumentation(), ], }); } From 2eaea5d4deb20184f717a9a19e96e10e3b5a577e Mon Sep 17 00:00:00 2001 From: David Luna Date: Tue, 9 Dec 2025 17:57:33 +0100 Subject: [PATCH 21/27] chore: add comment --- solutions/observability/applications/otel-rum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index f4444eabb2..7d2c904ba2 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -47,7 +47,7 @@ server { location / { # Take care of preflight requests if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Max-Age' 1728000; + add_header 'Access-Control-Max-Age' 1728000; # 20 days in seconds add_header 'Access-Control-Allow-Origin' 'webapp.example.com' always; # Set the allowed origins for preflight requests add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Authorization,Content-Language,Content-Type' always; add_header 'Access-Control-Allow-Credentials' 'true' always; From f8e2176fe82b1c91301eb620064f75beaf45db96 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 10 Dec 2025 09:30:54 +0100 Subject: [PATCH 22/27] chore: add link to secrets manager options --- solutions/observability/applications/otel-rum.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 7d2c904ba2..754bf6c6a3 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -63,7 +63,10 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; # Set the auth header here. It's recommended to get it from a secrets manager. + # Set the auth header here. It's recommended to get it from a secrets manager. + # ref for Docker: https://docs.docker.com/engine/swarm/secrets/#intermediate-example-use-secrets-with-a-nginx-service + # ref for K8s: https://kubernetes.io/docs/concepts/configuration/secret/ + proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; proxy_pass https://collector.example.com:4318; } } From 0deded174fbf1ef5973262844d1b2b6558b6fa04 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 10 Dec 2025 09:52:45 +0100 Subject: [PATCH 23/27] chore: expand comment for auth header --- solutions/observability/applications/otel-rum.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 754bf6c6a3..ffcde7cc36 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -63,9 +63,10 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # Set the auth header here. It's recommended to get it from a secrets manager. - # ref for Docker: https://docs.docker.com/engine/swarm/secrets/#intermediate-example-use-secrets-with-a-nginx-service - # ref for K8s: https://kubernetes.io/docs/concepts/configuration/secret/ + # Set the auth header for the Collector here. It's recommended to follow the security best practices + # for adding secrets into services according to your infrastructure and company policy. A couple of references: + # for Docker: https://docs.docker.com/engine/swarm/secrets/#intermediate-example-use-secrets-with-a-nginx-service + # for K8s: https://kubernetes.io/docs/concepts/configuration/secret/ proxy_set_header Authorization 'ApiKey ...your Elastic API key...'; proxy_pass https://collector.example.com:4318; } From 6d4b59efaa0bb85e02a92ad238b2d93c1f7d290e Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Wed, 10 Dec 2025 13:17:19 +0100 Subject: [PATCH 24/27] Writer edits --- .../observability/applications/otel-rum.md | 206 ++++++++---------- 1 file changed, 92 insertions(+), 114 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index ffcde7cc36..38daa39510 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -2,6 +2,7 @@ navigation_title: OpenTelemetry for Real User Monitoring (RUM) description: Instrument web applications with OpenTelemetry for Real User Monitoring using Elastic Observability. applies_to: + product: preview stack: serverless: observability: @@ -13,34 +14,24 @@ products: # OpenTelemetry for Real User Monitoring (RUM) :::{important} -Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in **Technical Preview**. This feature may be changed or removed in a future release and has [limitations](#known-limitations). It should not be used in production environments. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. +Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in **Technical Preview** and should not be used in production environments. This feature may be changed or removed in a future release and has [known limitations](#known-limitations). Features in preview are not subject to support SLAs like GA features. ::: -This documentation outlines the process for instrumenting your web application with OpenTelemetry browser instrumentation for use with {{product.observability}}. This approach uses upstream OpenTelemetry packages directly unlike the [EDOT SDKs](opentelemetry://reference/edot-sdks/index.md). The following sections detail the required components and their proper configuration to acquire traces, logs, and metrics from the application to visualize them within {{kib}}. +You can instrument your web application with OpenTelemetry browser instrumentation for use with {{product.observability}}. The following sections detail the required components and their proper configuration to acquire traces, logs, and metrics from the application to visualize them within {{kib}}. -While this guide uses upstream OpenTelemetry instrumentation, you can use the [EDOT Collector](elastic-agent://reference/edot-collector/index.md) components as part of your data ingestion pipeline. +## Before you begin [before-you-begin] -## Prerequisites +You need a OTLP endpoint to ingest data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, [create](/solutions/observability/get-started.md) an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self-hosted stack or your deployment does not have the {{motlp}}, configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). -This guide assumes you're using an {{product.observability}} deployment. You can use an existing one or set up a new one. If you're new to {{product.observability}}, follow the guidelines in [Get started with {{product.observability}}](/solutions/observability/get-started.md). +After you have prepared the OTLP endpoint, set up a reverse proxy to forward the telemetry from your web application origin to the Collector. You need a reverse proxy for these reasons: -:::{warning} -Avoid using OTel RUM agent alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents in the same application process might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. -::: - -### OTLP endpoint - -You need a OTLP endpoint to ingest data from the OpenTelemetry RUM instrumentation. Also if you want to get the most of {{product.observability}} such endpoint should belong to an [EDOT Collector](elastic-agent://reference/edot-collector/index.md) in gateway mode. If you're setting up a new deployment, you can create an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self hosted stack or your deployment does not have the {{motlp}} you should configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). - -Once you have your OTLP endpoint ready the recommendation is to set up a reverse proxy to forward the telemetry from your web application origin to the collector. The primary reasons for having such set up are: - -- EDOT Collector requires an `Authorization` header with an ApiKey to accept OTLP exports. Setting up the required key in a web application makes it publicly available and its not advised to have this kind of secrets available to anyone with a browser. +- EDOT Collector requires an `Authorization` header with an ApiKey to accept OTLP exports. Setting up the required key in a web application makes it publicly available, which is not advisable. A reverse proxy can help you manage this key securely. +- If you have set up your own EDOT Collector, it's likely to have a different origin than your web application. In this scenario you have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in the EDOT Collector configuration file. This procedure can be cumbersome if you have to manage many applications. - You can apply rate limiting or any other mechanisms to control traffic before it reaches the EDOT Collector. -- If you have set up your own EDOT Collector it's likely to have a different origin than your web application. In this scenario you have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in EDOT Collector configuration file. This procerdure can be cumbersome if you have to manage a large number of applications. - The following snippet shows the configuration for an NGINX reverse proxy to forward all telemetry to the EDOT Collector located at `collector.example.com` from the origin `webapp.example.com`: +:::{dropdown} NGINX reverse proxy configuration ```nginx server { # Configuration for HTTP/HTTPS goes here @@ -72,35 +63,40 @@ server { } } ``` +::: + +:::{warning} +Avoid using the OTel RUM agent alongside any other {{apm-agent}}, including Elastic {{product.apm}} agents. Running multiple agents might lead to conflicting instrumentation, duplicate telemetry, or other unexpected behavior. +::: -## Basic configuration +## Define the basic settings [otel-rum-basic-settings] The minimal configuration you need to instrument your web application with OpenTelemetry includes: -- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of the proxy you have configured in the [OTLP endpoint](#otlp-endpoint) section. +- **OTEL_EXPORTER_OTLP_ENDPOINT**: The full URL of the proxy you have configured in the [OTLP endpoint](#before-you-begin) section. - **OTEL_RESOURCE_ATTRIBUTES**: A JavaScript object that will be used to define the resource. The most important attributes to define are: - `service.name` (string): Name of the application you're instrumenting. - `service.version` (string, optional): A string representing the version or build of your app. - `deployment.environment.name` (string, optional): Name of the environment where the app runs (if applicable); for example, "prod", "dev", or "staging". - **OTEL_LOG_LEVEL**: Use this configuration to set the log level of the OpenTelemetry components you're going to use. -## Set up OpenTelemetry for the browser +## Set up OpenTelemetry for the browser [otel-rum-set-up-open-telemetry-for-the-browser] -To begin instrumenting your web application with OpenTelemetry in the browser, you need a script. This script configures the essential components, including the context manager, signal providers, processors, and exporters. After setting up the script, you can register the installed instrumentations so they can observe your application and send traces, metrics, and logs to your designated endpoint. +To instrument your web application with OpenTelemetry in the browser, you need to add a script that configures the essential components, including the context manager, signal providers, processors, and exporters. After adding the script, you can register the instrumentations so they can observe your app and send telemetry data to your endpoint. -The following start script is in plain JavaScript. If you are using TypeScript, you can adapt this script by changing the file extension to `.ts` and adding the necessary type definitions. OpenTelemetry packages are written in TypeScript, so they include the appropriate type definitions. +The following starter script is in plain JavaScript. If you use TypeScript, you can adapt this script by changing the file extension to `.ts` and adding the necessary type definitions. OpenTelemetry packages are written in TypeScript, so they include the appropriate type definitions for TypeScript. :::{note} -Each signal configuration is independent of the others, meaning that you can configure only what you need. The OpenTelemetry API defaults to no-op providers for traces, metrics, and logs. +Each signal configuration is independent of the others and be configured independently. The OpenTelemetry API defaults to no-op providers for traces, metrics, and logs. ::: ::::::{stepper} :::::{step} Set the configuration -First, set the configuration options that are to be used by all the signals and the instrumentation code. Also initialize the internal logger at the level defined in the configuration. +Start by setting the options to be used by all the signals and the instrumentation code, as well as initializing the internal logger. -For this part, you need to install the following dependencies: +For this step, you need the following dependencies: - `@opentelemetry/api`: All the packages are included. Each signal configuration uses it to register the providers for each signal. - `@opentelemetry/core`: Contains core types and some utilities for the rest of the packages. It parses strings to the correct type. @@ -144,11 +140,11 @@ diag.info('OTEL bootstrap', config); :::::{step} Define the resource -A resource is an entity that generates telemetry, with its characteristics captured in resource attributes. An example is a web application operating within a browser that produces telemetry data. Further details are available in [OpenTelemetry Resources](https://opentelemetry.io/docs/concepts/resources/). +A [resource](https://opentelemetry.io/docs/concepts/resources/) is an entity that generates telemetry, with its characteristics captured in resource attributes. An example is a web application operating within a browser that produces telemetry data. A standardized set of attributes is specified in [Browser resource semantic conventions](https://opentelemetry.io/docs/specs/semconv/resource/browser/), which can be included alongside those outlined in the configuration section. OpenTelemetry offers resource detectors like `browserDetector` to help set these attributes like brands, mobile, and platform. -To define the resource, install the following dependencies: +To define the resource, you need the following dependencies: - `@opentelemetry/resources`: This package helps you to define and work with resources because a Resource is not a plain object and has some properties (like immutability) and constraints. - `@opentelemetry/browser-detector`: Detectors help you to define a resource by querying the runtime and environment and resolving some attributes. In this case, the browser detector resolves the language, brands, and mobile attributes of the browser namespace. @@ -161,6 +157,7 @@ npm install @opentelemetry/resources @opentelemetry/browser-detector After the dependencies are installed, define the resource for your instrumentation with the following code: +:::{dropdown} Resource definition ```javascript import { resourceFromAttributes, detectResources } from '@opentelemetry/resources'; import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector'; @@ -169,21 +166,19 @@ const detectedResources = detectResources({ detectors: [browserDetector] }); let resource = resourceFromAttributes(OTEL_RESOURCE_ATTRIBUTES); resource = resource.merge(detectedResources); ``` - -Having this information on spans and errors is useful in diagnostic situations for identifying application and dependency compatibility issues with certain browsers. - +::: ::::: -:::::{step} Configure trace +:::::{step} (Optional) Configure tracing -To enable instrumentations to transmit traces and allow for the creation of custom spans through the OpenTelemetry API, a [TracerProvider](https://opentelemetry.io/docs/concepts/signals/traces/#tracer-provider) must be configured. This provider necessitates the inclusion of several key components: +Configure a [TracerProvider](https://opentelemetry.io/docs/concepts/signals/traces/#tracer-provider) to enable instrumentations to transmit traces and allow for the creation of custom spans. A TracerProvider requires several components: -- **Resource**: The resource to be associated with the spans created by the tracers (previously defined). +- **Resource**: The resource to be associated with the spans created by the tracers. You defined this in the second step. - **Span Processor**: A component that manages the spans generated by the tracers and forwards them to a [SpanExporter](https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter). The exporter should be configured to direct data to an endpoint designated for traces. - **Span Exporter**: Manages the transmission of spans to the Collector. -- **Context Manager**: A crucial mechanism for managing the active Span context across asynchronous operations and threads. It ensures that when a new Span is created, it correctly identifies its parent Span, thereby maintaining the integrity of the trace structure. +- **Context Manager**: A mechanism for managing the active Span context across asynchronous operations and threads. It ensures that when a new Span is created, it correctly identifies its parent Span. -For this part, you need to install the following dependencies: +For this step, you need the following dependencies: - `@opentelemetry/sdk-trace-base`: This package contains all the core components to set up tracing regardless of the runtime they're running in (Node.js or browser). - `@opentelemetry/sdk-trace-web`: This package contains a tracer provider that runs in web browsers. @@ -197,10 +192,9 @@ npm install @opentelemetry/sdk-trace-base\ @opentelemetry/context-zone ``` -Once the dependencies are installed, you can configure and register a tracer provider with the following code: +After the dependencies are installed, configure and register a tracer provider with the following code: :::{dropdown} Tracer provider configuration - ```javascript import { trace } from '@opentelemetry/api'; import { ZoneContextManager } from '@opentelemetry/context-zone'; @@ -222,22 +216,21 @@ const tracerProvider = new WebTracerProvider({ }); tracerProvider.register({ contextManager: new ZoneContextManager() }); ``` - ::: -Now you can use the OpenTelemetry API to get a tracer and start creating your own spans. Instrumentations are also using OpenTelemetry API to get tracers and send telemetry data. Registering intrumentations after having the tracer provider set up ensures they will have the right tracers when requested to the API. +Now you can use the OpenTelemetry API to get a tracer and start creating your own spans. Instrumentations are also using OpenTelemetry API to get tracers and send telemetry data. Registering intrumentations after having the tracer provider set up ensures they have the right tracers when requested to the API. ::::: -:::::{step} Configure metrics +:::::{step} (Optional) Configure metrics -Similar to traces, you should configure a [MeterProvider](https://opentelemetry.io/docs/concepts/signals/metrics/#meter-provider) for metrics. This provider necessitates the inclusion of several key components: +Similar to traces, configure a [MeterProvider](https://opentelemetry.io/docs/concepts/signals/metrics/#meter-provider) for metrics. A MeterProvider requires several components: -- **Resource**: The resource to be associated with the metrics created by the meters. -- **Metric Reader**: Used to determine how often metrics are collected and what destination they should be exported to. In this case, we will use a `PeriodicExportingMetricReader` configured to collect and export metrics at a fixed interval. -- **Metric Exporter**: Responsible for serializing and sending the collected and aggregated metric data to a backend observability platform. We will use the OTLP/HTTP exporter. +- **Resource**: The resource to be associated with the metrics created by the meters. You defined this in the second step. +- **Metric Reader**: Used to determine how often metrics are collected and what destination they should be exported to. In this case, use a `PeriodicExportingMetricReader` configured to collect and export metrics at a fixed interval. +- **Metric Exporter**: Responsible for serializing and sending the collected and aggregated metric data to a backend observability platform. Use the OTLP/HTTP exporter. -For this part, you need to install the following dependencies: +For this step, you need the following dependencies: - `@opentelemetry/sdk-metrics`: This package contains all the required components to set up metrics. - `@opentelemetry/exporter-metrics-otlp-http`: This package contains the exporter for the HTTP/JSON protocol. @@ -251,7 +244,6 @@ npm install @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http After the dependencies are installed, configure and register a meter provider with the following code: :::{dropdown} Meter provider configuration - ```javascript import { metrics } from '@opentelemetry/api'; import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; @@ -272,22 +264,18 @@ const meterProvider = new MeterProvider({ }); metrics.setGlobalMeterProvider(meterProvider); ``` - ::: - ::::: -:::::{step} Configure logs +:::::{step} (Optional) Configure logs -Like traces and metrics you need to configure a [LoggerProvider](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider) if you want to record relevant events that are happening in your application using the API or instrumenations. This provider necessitates the inclusion of several key components: +Like traces and metrics, configure a [LoggerProvider](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider) if you want to record relevant events that are happening in your application using the API or instrumentations. A LoggerProvider requires several components: -- **Resource**: The resource to be associated with the log records created by the loggers. +- **Resource**: The resource to be associated with the log records created by the loggers. You defined this in the second step. - **LogRecord Processor**: A component that manages the log records generated by the loggers and forwards them to a [LogExporter](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter). The exporter should be configured to direct data to an endpoint designated for logs. - **Log Exporter**: Manages the transmission of log records to the Collector. - - -For this part, you need to install the following dependencies: +For this step, you need the following dependencies: - `@opentelemetry/api-logs`: This package contains the logs API. This API is not included yet in the generic API package because logs are still experimental. - `@opentelemetry/sdk-logs`: This package contains all the required components to set up logs. @@ -299,10 +287,9 @@ To install the dependencies, run the following command: npm install @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http ``` -After the dependencies are installed, you can configure and register a logger provider with the following code: +After the dependencies are installed, configure and register a logger provider with the following code: :::{dropdown} Logger provider configuration - ```javascript import { logs, SeverityNumber } from '@opentelemetry/api-logs'; import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; @@ -320,14 +307,12 @@ const loggerProvider = new LoggerProvider({ }); logs.setGlobalLoggerProvider(loggerProvider); ``` - ::: - ::::: -:::::{step} Register instrumentations +:::::{step} Register the instrumentations -The final step for setting up Real User Monitoring (RUM) through OpenTelemetry is registering instrumentations. Instrumentations are modules that automatically capture telemetry data, like network requests or DOM interactions, by using the OpenTelemetry API. +The final step for setting up Real User Monitoring (RUM) through OpenTelemetry is registering the instrumentations. Instrumentations are modules that automatically capture telemetry data, like network requests or DOM interactions, by using the OpenTelemetry API. With the OpenTelemetry SDK, resource attributes, and exporters already configured, all telemetry data generated by these registered instrumentations is automatically processed and exported. @@ -341,7 +326,6 @@ Install the following dependencies: - `@opentelemetry/instrumentation-user-interaction`: This instrumentation measures user interactions in your web application. More info at [instrumentation-user-interaction](https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction). - `@opentelemetry/instrumentation-long-task`: This instrumentation is not part of the auto instrumentations package. It gathers information about long tasks being executed in your browser, helping to spot issues like unresponsive UI in your web application. More info at [instrumentation-long-task](https://www.npmjs.com/package/@opentelemetry/instrumentation-long-task). - To install the dependencies, run the following command: ```bash @@ -350,10 +334,9 @@ npm install @opentelemetry/instrumentation\ @opentelemetry/instrumentation-long-task ``` -After the dependencies are installed, you can configure and register instrumentations with the following code: +After the dependencies are installed, configure and register instrumentations with the following code: :::{dropdown} Instrumentations registration - ```javascript import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; @@ -368,17 +351,16 @@ registerInstrumentations({ ], }); ``` - ::: - ::::: -:::::{step} Complete setup script +:::::{step} Complete the setup script -All these pieces together give you a complete setup of all the signals for your web site or application. For conveninence its better to have it in a separate file which can be named `telemetry.js`. This file should export a function that accepts the configuration allowing you to reuse the setup across different UIs. +All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it's better to have it in a separate file which can be named `telemetry.js`. This file should export a function that accepts the configuration allowing you to reuse the setup across different UIs. To install all the dependencies needed for the complete setup, run the following command: +:::{dropdown} Install setup script dependencies ```bash npm install @opentelemetry/api\ @opentelemetry/core\ @@ -397,11 +379,11 @@ npm install @opentelemetry/api\ @opentelemetry/auto-instrumentations-web\ @opentelemetry/instrumentation-long-task ``` +::: After the dependencies are installed, you can wrap the setup in a function with the following code: :::{dropdown} Complete setup script example - ```javascript // file: telemetry.js import { diag, DiagConsoleLogger, trace, metrics } from '@opentelemetry/api'; @@ -486,22 +468,19 @@ export function initOpenTelemetry(config) { }); } ``` - ::: - ::::: - :::::: ## Integrate with your application -With the setup placed in a file, it's time to apply it to your web application. You can choose from two main approaches: +With the setup script in a single file, you can now apply it to your web application. You can choose from two main approaches: 1. **Import the code**: Use your build tooling to manage the dependencies and integrate the code into the application bundle. This is the simplest option and is recommended, although it increases the size of your application bundle. 2. **Bundle in a file**: Use a bundler to generate a separate JavaScript file that you include in the `` section of your HTML page. This approach keeps the telemetry code separate from your application bundle. -### Import the code +### Import the code This approach is recommended and specially if you're using a web framemork. The build tooling manages the dependencies and integrates the code into the application bundle. This approach increases the size of your application bundle. @@ -525,20 +504,21 @@ initOpenTelemetry({ If you're using a framework there are some suitable places for it depending on which one you're using: -- **React**: You can create a component which initializes the instrumentation when mounted. The component should be added as child of the `` component. -- **VueJs**: You can create a plugin which doesn the initialization when installed in the app. Check how to create plugins in [VueJS docs](https://vuejs.org/guide/reusability/plugins.html). -- **Angular**: You can add the initialiation snipped in `./src/main.ts` which is the entry point of the application. More details in [Angular docs](https://v17.angular.io/guide/file-structure#application-source-files). - +| Framework | Method | +|-----------|-------------| +| **React** | Create a component which initializes the instrumentation when mounted. The component should be added as child of the `` component. | +| **VueJs** | Create a plugin which does the initialization when installed in the app. Refer to the [VueJS docs](https://vuejs.org/guide/reusability/plugins.html) for more details. | +| **Angular** | Add the initialization snippet in `./src/main.ts` which is the entry point of the application. Refer to the [Angular docs](https://v17.angular.io/guide/file-structure#application-source-files) for more details. | ### Bundle in a file -You can use a bundler like webpack, rollup or vite to generate a separate JavaScript file. +You can use a bundler like Webpack, Rollup, or Vite to generate a separate JavaScript file. -::::{tab-set} +#### Webpack -:::{tab-item} Wepack +This is an example of a `webpack.config.js` to author a library as described in the [Webpack documentation](https://webpack.js.org/guides/author-libraries/) in UMD format. -This is an exmaple of a `webpack.config.js` to author a library as described in [the docs](https://webpack.js.org/guides/author-libraries/) in UMD format. +:::{dropdown} Example: Webpack configuration ```javascript const path = require('path'); @@ -555,8 +535,11 @@ module.exports = { ``` ::: -:::{tab-item} Vite -This is an exmaple of a `vite.config.js` file in [library mode](https://vite.dev/guide/build#library-mode) to get a bundle in UMD format. +#### Vite + +This is an example of a `vite.config.js` file in [library mode](https://vite.dev/guide/build#library-mode) to get a bundle in UMD format. + +:::{dropdown} Example: Vite configuration ```javascript import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -578,15 +561,9 @@ export default defineConfig({ ``` ::: -:::: - - -Place the file within the application's assets folder and include it in the `` section of the HTML page. Assuming the JavaScript files reside in a folder named "js" you can load the telemetry file in a sync or async way. - -::::{tab-set} - -:::{tab-item} Synchronous / Blocking pattern +Place the file within the application's assets folder and include it in the `` section of the HTML page. Assuming the files are in a folder named `js` you can load the telemetry file in a synchronous or asynchronous way. +::::{dropdown} Example: Synchronous / Blocking pattern Add a ` ``` -::: -:::{tab-item} Asynchronous / Non-Blocking Pattern - -Loading the script asynchronously ensures the agent script does not block other resources on the page, however, it still blocks browsers onload event. +:::: +::::{dropdown} Example: Asynchronous / Non-Blocking pattern +Loading the script asynchronously ensures the agent script doesn't block other resources on the page. However, it still blocks the browser's `onload` event. ```html ``` -Even though this is the recommended pattern, there is a caveat to be aware of. Because the downloading and initializing of the instrumentations happens asynchronously, distributed tracing does not work for requests that occur before the agent is initialized. +:::{note} +Because the downloading and initializing of the instrumentations happens asynchronously, distributed tracing doesn't work for requests that occur before the agent is initialized. ::: -:::: - -## Manual instrumentation to extend your telemetry +:::: -Automatic instrumentation provides a convenient baseline for web application telemetry, but often lacks the necessary depth to fully understand complex user journeys or correlate technical performance with business outcomes. -The OpenTelemetry API is essential for filling this gap. By using the OpenTelemetry API directly, you can send highly specific, custom telemetry to augment automatic collection. This custom instrumentation allows you to: +## Extend your telemetry with manual instrumentation -1. **Define custom spans and traces**: Create explicit spans around unique critical business logic or user interactions (for example, complex calculations, multi-step forms) for granular detail. -2. **Log application-specific events**: Generate high-fidelity logs that directly correlate with the flow of a trace for better debugging. -3. **Create custom metrics**: Record application-specific KPIs not covered by standard RUM metrics (for example, UI component render counts, client-side transaction success rates). +By using the OpenTelemetry API directly, you can send highly specific, custom telemetry to augment automatic collection. This custom instrumentation allows you to: -Leveraging the OpenTelemetry API to augment data collection makes your application's observability truly comprehensive, bridging the gap between technical monitoring and business intelligence. +- Create explicit spans around unique critical business logic or user interactions (for example, complex calculations, multi-step forms) for granular detail. +- Generate high-fidelity logs that directly correlate with the flow of a trace for better debugging. +- Record application-specific KPIs not covered by standard RUM metrics (for example, UI component render counts, client-side transaction success rates). ### Track request path with traces -Your web application might initiate several HTTP requests to an associated API. With the instrumentations established in the previous section, a span is generated for each request, each part of a separate trace, meaning they are treated as independent operations. While this provides a clear breakdown of each individual request, there are cases where consolidating multiple related requests within a single, cohesive trace is highly desirable for better observability. +With the instrumentations configured in the previous section, a span is generated for each request, each part of a separate trace, meaning they're treated as independent operations. While this provides a clear breakdown of each individual request, you might need to consolidate multiple related requests within a single, cohesive trace. -An example is a recurring task that updates the user interface at regular intervals to display various datasets that fluctuate over time. In this case, grouping all the API calls necessary for a single UI refresh into one trace allows you to view the overall performance and flow of the entire update cycle. +An example is a recurring task that updates the user interface at regular intervals to display various datasets that fluctuate over time. Grouping all the API calls necessary for a single UI refresh into one trace allows you to view the overall performance and flow of the entire update cycle. :::{dropdown} Example: Group API calls in a trace @@ -667,15 +641,15 @@ setInterval(function () { }); }, intervalTime) ``` - ::: -By using the `startActiveSpan` callback mechanism, you can wrap the asynchronous data fetching logic within a dedicated active trace. This technique accurately captures the execution flow and performance characteristics of operations that involve multiple steps or services. You get a single root span for the entire operation; this root span established by the callback acts as the primary container for the entire sequence of events. Contained within this root span are two distinct child spans that represent each request from the UI to the API. +By using the `startActiveSpan` callback mechanism, you can wrap the asynchronous data fetching logic within a dedicated active trace. ### Record relevant events with logs Relevant events occurring within your application can be recorded using a logger. A typical scenario involves documenting business-critical occurrences, such as conversions or purchases. +:::{dropdown} Example: Record relevant events with logs ```javascript import { logs, SeverityNumber } from '@opentelemetry/api-logs'; const logger = logs.getLogger('app-logger'); @@ -689,12 +663,13 @@ logger.emit({ } }); ``` +::: ## Browser constraints Review the following constraints in your web application to avoid any data transmission issues. -### Content Security Policy +### Content security policies (CSP) If your website is making use of Content Security Policies (CSPs), make sure that the domain of your OTLP endpoint is included. If your Collector endpoint is `https://collector.example.com:4318/v1/traces`, add the following directive: @@ -702,13 +677,14 @@ If your website is making use of Content Security Policies (CSPs), make sure tha connect-src collector.example.com:4318/v1/traces ``` -### Cross-Origin Resource Sharing (CORS) - -If your website and the configured endpoing have a differene origin your browser might block the export requests. If you have followed the instructions in the [OTLP endpoint](#otlp-endpoint) section you've already done the necessary setup for CORS. Otherwise you need to configure special headers for Cross-Origin Resource Sharing (CORS) in te receiveng endpoint. +### Cross-origin resource sharing (CORS) +If your website and the configured endpoing have a different origin, your browser might block the export requests. If you followed the instructions in the [OTLP endpoint](#before-you-begin) section, you already set up the necessary CORS headers. Otherwise you need to configure special headers for CORS in the receiving endpoint. ## Known limitations +These are the known limitations of using OpenTelemetry for RUM with {{product.observability}}: + - Metrics from browser-based RUM might have limited utility compared to backend metrics. - Some OpenTelemetry instrumentations for browsers are still experimental. - Performance impact on the browser should be monitored, especially when using multiple instrumentations. @@ -948,6 +924,8 @@ output: { ::: +### General troubleshooting steps + If you continue to experience issues: 1. Ensure your target browsers support the OpenTelemetry features you're using. From f397d72b7b0edea47ca1f60c2ce3d134a7e7bed5 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Wed, 10 Dec 2025 13:40:24 +0100 Subject: [PATCH 25/27] Update solutions/observability/applications/otel-rum.md Co-authored-by: David Luna --- solutions/observability/applications/otel-rum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 38daa39510..261d5154ea 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -785,7 +785,7 @@ const OTEL_RESOURCE_ATTRIBUTES = { ```javascript // Correct order: // 1. Configure and register tracer provider -trace.setGlobalTracerProvider(tracerProvider); +tracerProvider.register({ contextManager: new ZoneContextManager() }); // 2. Then register instrumentations registerInstrumentations({...}); ``` From 0b3ce79f26cebda2688ef8a43bc9807505017622 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Wed, 10 Dec 2025 15:36:31 +0100 Subject: [PATCH 26/27] Apply suggestions from code review Co-authored-by: Aleksandra Spilkowska <96738481+alexandra5000@users.noreply.github.com> --- .../observability/applications/otel-rum.md | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index 261d5154ea..e20be388e0 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -14,16 +14,16 @@ products: # OpenTelemetry for Real User Monitoring (RUM) :::{important} -Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in **Technical Preview** and should not be used in production environments. This feature may be changed or removed in a future release and has [known limitations](#known-limitations). Features in preview are not subject to support SLAs like GA features. +Using OpenTelemetry for Real User Monitoring (RUM) with {{product.observability}} is currently in technical preview and should not be used in production environments. This feature may be changed or removed in a future release and has [known limitations](#known-limitations). Features in preview are not subject to support SLAs like GA features. ::: You can instrument your web application with OpenTelemetry browser instrumentation for use with {{product.observability}}. The following sections detail the required components and their proper configuration to acquire traces, logs, and metrics from the application to visualize them within {{kib}}. ## Before you begin [before-you-begin] -You need a OTLP endpoint to ingest data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, [create](/solutions/observability/get-started.md) an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self-hosted stack or your deployment does not have the {{motlp}}, configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). +You need an OTLP endpoint to ingest data from the OpenTelemetry RUM instrumentation. If you're setting up a new deployment, [create](/solutions/observability/get-started.md) an {{ecloud}} hosted deployment or {{serverless-short}} project, which includes the [{{motlp}}](opentelemetry://reference/motlp.md). If you own a self-hosted stack or your deployment does not have the {{motlp}}, configure an [EDOT Collector in Gateway mode](https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway). -After you have prepared the OTLP endpoint, set up a reverse proxy to forward the telemetry from your web application origin to the Collector. You need a reverse proxy for these reasons: +After you have prepared the OTLP endpoint, set up a reverse proxy to forward the telemetry from your web application origin to the Collector. You need a reverse proxy for the following reasons: - EDOT Collector requires an `Authorization` header with an ApiKey to accept OTLP exports. Setting up the required key in a web application makes it publicly available, which is not advisable. A reverse proxy can help you manage this key securely. - If you have set up your own EDOT Collector, it's likely to have a different origin than your web application. In this scenario you have to set up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for the web application in the EDOT Collector configuration file. This procedure can be cumbersome if you have to manage many applications. @@ -87,7 +87,7 @@ To instrument your web application with OpenTelemetry in the browser, you need t The following starter script is in plain JavaScript. If you use TypeScript, you can adapt this script by changing the file extension to `.ts` and adding the necessary type definitions. OpenTelemetry packages are written in TypeScript, so they include the appropriate type definitions for TypeScript. :::{note} -Each signal configuration is independent of the others and be configured independently. The OpenTelemetry API defaults to no-op providers for traces, metrics, and logs. +Each signal configuration is independent of the others and has to be configured independently. The OpenTelemetry API defaults to no-op providers for traces, metrics, and logs. ::: ::::::{stepper} @@ -192,7 +192,7 @@ npm install @opentelemetry/sdk-trace-base\ @opentelemetry/context-zone ``` -After the dependencies are installed, configure and register a tracer provider with the following code: +After the dependencies are installed, configure and register a TracerProvider with the following code: :::{dropdown} Tracer provider configuration ```javascript @@ -241,7 +241,7 @@ To install the dependencies, run the following command: npm install @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http ``` -After the dependencies are installed, configure and register a meter provider with the following code: +After the dependencies are installed, configure and register a MeterProvider with the following code: :::{dropdown} Meter provider configuration ```javascript @@ -277,7 +277,7 @@ Like traces and metrics, configure a [LoggerProvider](https://opentelemetry.io/d For this step, you need the following dependencies: -- `@opentelemetry/api-logs`: This package contains the logs API. This API is not included yet in the generic API package because logs are still experimental. +- `@opentelemetry/api-logs`: This package contains the logs API. This API is not yet included in the generic API package because logs are still experimental. - `@opentelemetry/sdk-logs`: This package contains all the required components to set up logs. - `@opentelemetry/exporter-logs-otlp-http`: This package contains the exporter for the HTTP/JSON protocol. @@ -287,7 +287,7 @@ To install the dependencies, run the following command: npm install @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http ``` -After the dependencies are installed, configure and register a logger provider with the following code: +After the dependencies are installed, configure and register a LoggerProvider with the following code: :::{dropdown} Logger provider configuration ```javascript @@ -356,7 +356,7 @@ registerInstrumentations({ :::::{step} Complete the setup script -All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it's better to have it in a separate file which can be named `telemetry.js`. This file should export a function that accepts the configuration allowing you to reuse the setup across different UIs. +All these pieces together give you a complete setup of all the signals for your web site or application. For convenience, it's better to have it in a separate file which can be named, for example, `telemetry.js`. This file should export a function that accepts the configuration allowing you to reuse the setup across different UIs. To install all the dependencies needed for the complete setup, run the following command: @@ -482,7 +482,7 @@ With the setup script in a single file, you can now apply it to your web applica ### Import the code -This approach is recommended and specially if you're using a web framemork. The build tooling manages the dependencies and integrates the code into the application bundle. This approach increases the size of your application bundle. +This approach is recommended, especially if you're using a web framework. The build tooling manages the dependencies and integrates the code into the application bundle. However, this approach increases the size of your application bundle. For example, if you're using Webpack, you can import the code like this: @@ -502,7 +502,7 @@ initOpenTelemetry({ ``` ::: -If you're using a framework there are some suitable places for it depending on which one you're using: +If you're using a framework, there are some suitable places for it, depending on which one you're using: | Framework | Method | |-----------|-------------| @@ -564,7 +564,7 @@ export default defineConfig({ Place the file within the application's assets folder and include it in the `` section of the HTML page. Assuming the files are in a folder named `js` you can load the telemetry file in a synchronous or asynchronous way. ::::{dropdown} Example: Synchronous / Blocking pattern -Add a ` @@ -679,7 +679,7 @@ connect-src collector.example.com:4318/v1/traces ### Cross-origin resource sharing (CORS) -If your website and the configured endpoing have a different origin, your browser might block the export requests. If you followed the instructions in the [OTLP endpoint](#before-you-begin) section, you already set up the necessary CORS headers. Otherwise you need to configure special headers for CORS in the receiving endpoint. +If your website and the configured endpoint have a different origin, your browser might block the export requests. If you followed the instructions in the [OTLP endpoint](#before-you-begin) section, you already set up the necessary CORS headers. Otherwise you need to configure special headers for CORS in the receiving endpoint. ## Known limitations @@ -702,7 +702,7 @@ If you see errors like "Cannot find module" or bundler-specific issues: 2. Different bundlers (Webpack, Rollup, Vite) may require specific configuration for OpenTelemetry packages. -3. For Webpack, you may need to add polyfills for Node.js modules. Add to your webpack config: +3. For Webpack, you may need to add polyfills for Node.js modules. Add this to your Webpack config: ```javascript resolve: { @@ -739,9 +739,9 @@ optimizeDeps: { If your reverse proxy is not forwarding requests correctly: -1. Ensure the reverse proxy (NGINX, Apache, etc.) is running and accessible. +1. Ensure the reverse proxy (NGINX, Apache, and so on) is running and accessible. -2. Use curl to test the proxy endpoint directly: +2. Use `curl` to test the proxy endpoint directly: ```bash curl -X POST https://your-proxy/v1/traces \ @@ -912,7 +912,7 @@ import { MyApp } from './app.js'; ``` -4. If `initOpenTelemetry` is not defined, ensure your bundler is exposing it globally. For Webpack: +4. If `initOpenTelemetry` is not defined, ensure your bundler is exposing it globally. For example, for Webpack: ```javascript output: { From 4c943f758613b6c23131da1aa15920026ed2df0b Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Wed, 10 Dec 2025 15:40:24 +0100 Subject: [PATCH 27/27] Update solutions/observability/applications/otel-rum.md --- solutions/observability/applications/otel-rum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/observability/applications/otel-rum.md b/solutions/observability/applications/otel-rum.md index e20be388e0..3cca6803a5 100644 --- a/solutions/observability/applications/otel-rum.md +++ b/solutions/observability/applications/otel-rum.md @@ -78,7 +78,7 @@ The minimal configuration you need to instrument your web application with OpenT - `service.name` (string): Name of the application you're instrumenting. - `service.version` (string, optional): A string representing the version or build of your app. - `deployment.environment.name` (string, optional): Name of the environment where the app runs (if applicable); for example, "prod", "dev", or "staging". -- **OTEL_LOG_LEVEL**: Use this configuration to set the log level of the OpenTelemetry components you're going to use. +- **OTEL_LOG_LEVEL**: Use this configuration to set the log level of the OpenTelemetry components you're going to use. Supported values are: `error`, `warn`, `info`, `debug`, `verbose`. ## Set up OpenTelemetry for the browser [otel-rum-set-up-open-telemetry-for-the-browser]