Manually instrument browser-based web applications

To collect additional telemetry or to implement custom actions, manually instrument your applications for Splunk RUM using the Splunk RUM browser agent.

You can add custom instrumentation to web applications using the Splunk RUM browser agent manual instrumentation API. This API supports actions such as collecting additional telemetry, sanitizing personally identifiable information (PII), identifying users, and more. The page provides examples of how to use the API for some of these use cases.

To migrate manual instrumentation created for another vendor, see Migrate existing manual instrumentation.

Instrument your application using the OpenTelemetry API

To instrument your web application manually, use the OpenTelemetry API. The Splunk RUM browser agent automatically registers its TraceProvider using @opentelemetry/api, so that your own instrumentation can access it.

Check the version of the OpenTelemetry API

To manually instrument your application, the version of @opentelemetry/api you use must match the same major version of @opentelemetry/api used by the Splunk RUM browser agent.

To verify this, run window[Symbol.for('opentelemetry.js.api.1')].version in the browser’s console from any page that you’ve instrumented. The command returns the full version of the OpenTelemetry API.

Create a span

The following example shows how to create a span with an attribute:

JAVASCRIPT
import {trace} from '@opentelemetry/api'

const span = trace.getTracer('searchbox').startSpan('search');
span.setAttribute('searchLength', searchString.length);
// Time passes
span.end();

Set the user ID on all spans

The following example shows how to set the user ID globally:

JAVASCRIPT
import SplunkRum from '@splunk/otel-web';

SplunkRum.setGlobalAttributes({
   'enduser.id': 'Test User'
});

Create a workflow span

When you create a span with workflow.name (in other words, when you call trackWorkflow), the data propagates to Splunk RUM's Event Definitions > Custom Events tab.

The following example shows how to create a custom event:

JAVASCRIPT
import {trace} from '@opentelemetry/api'

const tracer = trace.getTracer('appModuleLoader');
const span = tracer.startSpan('test.module.load', {
attributes: {
   'workflow.name': 'test.module.load'
}
});
// time passes
span.end();
Note: To avoid loading problems due to content blockers when using the CDN version of the Splunk RUM browser agent, add if (window.SplunkRum) checks around SplunkRum API calls.
Tip:

What's the difference between a workflow span and a custom event?

The difference is one of terminology and perspective, not technical function.

  • Workflow span: This is the technical term for the underlying OpenTelemetry object you create in your code. It is a span that is specifically decorated with a workflow.name attribute to give it special meaning within Splunk Observability Cloud.
  • Custom event: This is a user-defined event that represents the business-level action or step you're tracking. See Events in Digital Experience Analytics.

Comparison of terminology and perspective:

Term Meaning in Splunk RUM context Perspective
Workflow span The technical implementation: An OpenTelemetry span with a workflow.name attribute. Developer (code-level)
Custom event The feature name for a "workflow span." It's what you see on Events Definitions > Custom Events tab. User (analyst-level)

Sanitize personally identifiable information (PII)

The metadata collected by the Splunk RUM browser agent might include personally identifiable information (PII) if your front-end application injects such data in its code. For example, UI components might include PII in their IDs.

To redact PII in the data collected for Splunk RUM, use the exporter.onAttributesSerializing setting when initializing the Splunk RUM browser instrumentation, as in the following example:

JAVASCRIPT
SplunkRum.init({
// ...
exporter: {
// You can use the entire span as an optional second argument of the sanitizer if needed
   onAttributesSerializing: (attributes) => ({
      ...attributes,
      'http.url': /secret\=/.test(attributes['http.url']) ? '[redacted]' : attributes['http.url'],
   }),
},
});
Note: The Splunk RUM browser agent automatic instrumentation does not collect or report any data from request payloads or POST bodies other than their size.

Add user metadata using global attributes

By default, the Browser RUM agent doesn’t automatically link traces to users of your site. However, you might need to collect user metadata to filter or debug traces.

You can identify users by adding global attributes from the OpenTelemetry specification, such as enduser.id and enduser.role, to your spans.

The following examples show how to add identification metadata as global attributes when initializing the agent or after you’ve initialized it, depending on whether user data is accessible at initialization:

Add identification metadata during initialization

HTML
<script src="https://cdn.signalfx.com/o11y-gdi-rum/latest/splunk-otel-web.js" crossorigin="anonymous"></script>
<script>
SplunkRum.init({
   realm: 'your-splunk-realm',
   rumAccessToken: 'your-splunk-rum-access-token',
   applicationName: 'your-application-name',
   globalAttributes: {
      // The following data is already available
      'enduser.id': 42,
      'enduser.role': 'admin',
   },
});
</script>

Add identification metadata after initialization

JAVASCRIPT
import SplunkRum from '@splunk/otel-web';

const user = await (await fetch('/api/user')).json();
// Spans generated prior to this call don't have user metadata
SplunkRum.setGlobalAttributes({
   'enduser.id': user ? user.id : undefined,
   'enduser.role': user ? user.role : undefined,
});

Add server trace context from Splunk APM

The Splunk RUM browser agent collects server trace context using back-end data provided by APM instrumentation through the Server-Timing header. In some cases, you might want to generate the header manually.

To create the Server-Timing header manually, provide a Server-Timing header with the name traceparent, where the desc field holds the version, the trace ID, the parent ID, and the trace flag.

Consider the following HTTP header:

SHELL
Server-Timing: traceparent;desc="00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"

The example resolves to a context containing the following data:

SHELL
version=00 trace-id=4bf92f3577b34da6a3ce929d0e0e4736
parent-id=00f067aa0ba902b7 trace-flags=01

When generating a value for the traceparent header, make sure that it matches the following regular expression:

SHELL
00-([0-9a-f]{32})-([0-9a-f]{16})-01

Server timing headers with values that don’t match the pattern are automatically discarded. For more information, see the Server-Timing and traceparent documentation on the W3C website.

Note: If you’re using cross-origin resource sharing (CORS) headers, such as Access-Control-*, you might need to grant permission to read the Server-Timing header. For example: Access-Control-Expose-Headers: Server-Timing.

Create workflow spans

With workflow spans you can add metadata to your spans to track the steps happening in your application workflows, such as filling out a form or checking a shopping cart.

Workflow spans have the following attributes:

Name

Type

Description

id

String

Unique ID for the workflow instance.

name

String

Semantic name for the current workflow.

The following snippet shows how to create a workflow span:

JAVASCRIPT
import {trace} from '@opentelemetry/api'

const tracer = trace.getTracer('appModuleLoader');
const span = tracer.startSpan('test.module.load', {
attributes: {

   'workflow.name': 'test.module.load'
}
});

// Time passes
span.end();

To activate error collection for workflow spans, add the error and error.message attributes:

JAVASCRIPT
import {trace} from '@opentelemetry/api'

const tracer = trace.getTracer('appModuleLoader');
const span = tracer.startSpan('test.module.load', {
attributes: {
   'workflow.name': 'test.module.load',
   'error': true,
   'error.message': 'Custom workflow error message'
}
});

span.end();

Create custom spans for single-page applications

You can use the OpenTelemetry API to create custom spans that are specific to the structure of your application. For example, you can generate spans when a user clicks a specific button, or to instrument a custom communication protocol.

Set up the OpenTelemetry API

Add the current version of the OpenTelemetry API package using npm:

SHELL
npm install @opentelemetry/api
Note: Make sure that the version of the OpenTelemetry API matches the major version of the API used by the @splunk/otel-web package. Version information is available in the release notes .

Create custom spans

You can create custom spans by including a tracer. For example:

JAVASCRIPT
import {trace} from '@opentelemetry/api';

// Create a tracer
const tracer = trace.getTracer('my-application', '1.0.0');

// Example of an async/await function
async function processForm(form) {
   const span = tracer.startSpan('process form');

   // Wait for processing to be done
   span.end();
}

// Example of a callback function
function markCompleted(item) {
   const span = tracer.startSpan('item complete');

   processCompletion(item, function() {
      // ... Update item display
      span.end();
   });
}

// Example of hook system provided by another library
router.beforeEach((transition) => {
   transition.span = tracer.startSpan('navigate', {
      attributes: {
         'router.path': transition.path
      }
   });
});

router.afterEach((transition) => {
   if (transition.span) {
      transition.span.end();
   }
});

// For a list of available methods, see the OpenTelemetry API documentation.

To add child spans to the generated spans, use the Context API. For example:

JAVASCRIPT
import {trace, context} from '@opentelemetry/api';

// Create a tracer
const tracer = trace.getTracer('my-application', '1.0.0');

async function processForm(form) {
   const span = tracer.startSpan('process form');
   await context.with(trace.setSpan(context.active(), span), async () => {

      await client.send(form); // client.send would create a XHR span using instrumentation

   });
   span.end();
}
Note: Context might not propagate to child spans that aren’t called directly, for example inside a Promise.then, setTimeout, ... block. To mitigate this issue, activate asynchronous tracing. See Asynchronous trace settings.

Collect errors with single-page application frameworks

To activate the collection of JavaScript errors from single-page application (SPA) frameworks using their own error interceptors or handlers, you need to integrate the Splunk RUM browser agent with the framework.

The following framework-specific examples show how to integrate the Splunk RUM browser agent with the supported frameworks. These examples assume that you installed the Splunk RUM browser agent using npm.

React

Use the Splunk RUM browser agent API in your error boundary component:

JAVASCRIPT
import React from 'react';
import SplunkRum from '@splunk/otel-web';

class ErrorBoundary extends React.Component {
   componentDidCatch(error, errorInfo) {
// To avoid loading issues due to content blockers
// when using the CDN version of the Browser RUM
// agent, add if (window.SplunkRum) checks around
// SplunkRum API calls
      SplunkRum.error(error, errorInfo)
   }

   // Rest of your error boundary component
   render() {
      return this.props.children
   }
}
Vue.js

Add the collect function to your Vue errorHandler.

For Vue.js version 3.x, use the following code:

JAVASCRIPT
import Vue from 'vue';
import SplunkRum from '@splunk/otel-web';

const app = createApp(App);

app.config.errorHandler = function (error, vm, info) {
// To avoid loading issues due to content blockers
// when using the CDN version of the Browser RUM
// agent, add if (window.SplunkRum) checks around
// SplunkRum API calls
   SplunkRum.error(error, info)
}
app.mount('#app')

For Vue.js version 2.x, use the following code:

JAVASCRIPT
import Vue from 'vue';
import SplunkRum from '@splunk/otel-web';

Vue.config.errorHandler = function (error, vm, info) {
// To avoid loading issues due to content blockers
// when using the CDN version of the Browser RUM
// agent, add if (window.SplunkRum) checks around
// SplunkRum API calls
   SplunkRum.error(error, info)
}
Angular

For Angular version 2.x, create an error handler module:

TS
import {NgModule, ErrorHandler} from '@angular/core';
import SplunkRum from '@splunk/otel-web';

class SplunkErrorHandler implements ErrorHandler {
   handleError(error) {
// To avoid loading issues due to content blockers
// when using the CDN version of the Browser RUM
// agent, add if (window.SplunkRum) checks around
// SplunkRum API calls
      SplunkRum.error(error, info)
   }
}

@NgModule({
   providers: [
      {
         provide: ErrorHandler,
         useClass: SplunkErrorHandler
      }
   ]
})
class AppModule {}

For Angular version 1.x, create an exceptionHandler:

JAVASCRIPT
import SplunkRum from '@splunk/otel-web';

angular.module('...')
   .factory('$exceptionHandler', function () {
      return function (exception, cause) {
// To avoid loading issues due to content blockers
// when using the CDN version of the Browser RUM
// agent, add if (window.SplunkRum) checks around
// SplunkRum API calls
         SplunkRum.error(exception, cause)
      }
})
Ember.js

Configure an Ember.onerror hook as in the following example:

JAVASCRIPT
import Ember from 'ember';
import SplunkRum from '@splunk/otel-web';

Ember.onerror = function(error) {
// To avoid loading issues due to content blockers
// when using the CDN version of the Browser RUM
// agent, add if (window.SplunkRum) checks around
// SplunkRum API calls
   SplunkRum.error(error)
}