Skip to content

Use another OpenTelemetry client

FuseScope’s browser SDK and Android SDK are recommended, but not required. The RUM gateway accepts OTLP from any client and never checks which SDK sent it. If your app already uses the OpenTelemetry web SDK, point its exporters at FuseScope and add the attributes below; the Frontend screens then work as with @fusescope/browser.

Create the application first, as in Set up a web application, to get its public key and allowed origins. The SDK settings, such as the sample rate and consent, are then yours to implement in your client.

Signal Endpoint
Traces POST <FuseScope URL>/rum/v1/traces
Logs POST <FuseScope URL>/rum/v1/logs
  • Encoding: OTLP/HTTP, with Content-Type: application/json or application/x-protobuf. Content-Encoding: gzip is accepted.
  • Key: the application’s public key in the FuseScope-RUM-Key header. For navigator.sendBeacon, which can’t set headers, pass it as the fusescope_rum_key query parameter instead.
  • Size: 4 MiB per request at most, before and after gzip.
  • Response: an OTLP export response, 200, in the request’s encoding.

The gateway is not the collector’s OTLP endpoint (port 4318): browsers never talk to the collector directly. It authenticates the key, checks the origin, rate-limits, scrubs URLs, adds the application’s attributes and forwards to the collector.

OpenTelemetry web SDK
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
const headers = { 'FuseScope-RUM-Key': 'rum_pub_…' };
const traceExporter = new OTLPTraceExporter({
url: 'https://fusescope.example.com/rum/v1/traces',
headers,
});
const logExporter = new OTLPLogExporter({
url: 'https://fusescope.example.com/rum/v1/logs',
headers,
});

The gateway answers CORS itself, from the application’s Allowed origins:

  • The custom header makes every export a non-simple request, so the browser sends an OPTIONS preflight first. The gateway allows POST with the headers content-type, content-encoding and fusescope-rum-key, and caches the answer for 24 hours.
  • Don’t send credentials (withCredentials, cookies): they aren’t needed and aren’t allowed.
  • Requests without an Origin header, such as from a server or a test script, skip the origin check but still need a valid key.

The gateway sets these on every resource, so you don’t need to:

  • fusescope.rum.application.id and fusescope.rum.platform (web or android, from the application), always, overwriting any value the client sent;
  • service.name and deployment.environment.name, from the application’s name and environment, when the client didn’t set them;
  • event.name on log records, copied from the record’s event name when the attribute is missing. FuseScope’s log storage keeps only the attribute.

Your client must send the rest:

Where Attribute Used for
Every span and log record session.id Sessions, session error rate, recent sessions. A random id per visit, shared by the visit’s spans and logs.
Every span and log record fusescope.rum.view.route Routes: the route template, such as /product/:id.
Resource service.version The Release filter and column.
Resource user_agent.original; on Android device.manufacturer, device.model.name, os.name and os.version Browser · OS, or Device · OS on Android, in sessions.
Resource telemetry.distro.name and .version, or telemetry.sdk.name and .version The SDK badge. Any name other than @fusescope/browser or io.fuseone.fusescope:android shows as Other client.
Span named view url.path, fusescope.rum.view.route Views: one span per page or route the user saw. End it when the user leaves the view or hides the page: its duration is the time on view.
Span named view fusescope.rum.view.loading_type (initial_load or route_change) Page loads and route changes in the Views tab. Without it, views are counted apart, marked *.
Every span and log record, optional session.previous_id, user.id The after link between sessions, and the user column and search. Hash user.id yourself if it identifies a person.
Log record, event browser.user_action.click, optional fusescope.rum.action.target The element named on Click events in the timeline and breadcrumbs.
HTTP client spans http.request.method, http.response.status_code, url.full, with traceparent sent to the backend View backend trace in recent sessions.
Log record, event exception exception.type, exception.message, exception.stacktrace, fusescope.rum.error.source (onerror, unhandledrejection or manual) Errors, their grouping and the error rate. Send the stack raw, as the browser wrote it: FuseScope symbolicates it with your source maps, looked up by service.version.
Log record, event rum.vital, Android fusescope.rum.vital.name = app_start, fusescope.rum.vital.value (ms), fusescope.rum.vital.rating, start.type (cold, warm or hot) App start p75 on the Android overview.
Log record, event browser.web_vital browser.web_vital.name (lcp, inp, cls, fcp, ttfb), browser.web_vital.value, browser.web_vital.rating (good, needs-improvement, poor) The vitals, their split bars and Top routes by LCP. Values in milliseconds; CLS unitless.

The upstream @opentelemetry/browser-instrumentation package emits the browser.web_vital events and HTTP spans with these names; you add the session, the route and the view spans. The SDK’s README lists every attribute @fusescope/browser sends.

A plain OpenTelemetry Android client, or any OTLP client in an Android app, sends to the same endpoints with the same FuseScope-RUM-Key header. Create an Android application first, as in Set up an Android application, to get its key and allowed package names. Then:

  • Set app.package.name on the resource, to the app’s applicationId. The gateway refuses a request when any resource in it has no app.package.name, or one that is not in Allowed package names. No semantic convention names the package, so OpenTelemetry Android doesn’t send it on its own.
  • Send no Origin header. An Android application never serves a browser, so the gateway refuses requests that carry one. OkHttp and the OpenTelemetry Java exporters send none.
  • Set service.version to the app’s versionName, the release you upload R8 mappings for.
OpenTelemetry Java exporters
val resource = Resource.getDefault().toBuilder()
.put("service.version", BuildConfig.VERSION_NAME)
.put("app.package.name", BuildConfig.APPLICATION_ID)
.build()
val spanExporter = OtlpHttpSpanExporter.builder()
.setEndpoint("https://fusescope.example.com/rum/v1/traces")
.addHeader("FuseScope-RUM-Key", "rum_pub_…")
.build()
val logExporter = OtlpHttpLogRecordExporter.builder()
.setEndpoint("https://fusescope.example.com/rum/v1/logs")
.addHeader("FuseScope-RUM-Key", "rum_pub_…")
.build()

The gateway maps what OpenTelemetry Android sends onto the RUM model, filling only what the client left unset:

OpenTelemetry Android sends The gateway stores
Log event app.crash An exception event with fusescope.rum.error.source = crash.
Log event device.anr An exception event with fusescope.rum.error.source = anr, exception.type = ANR and exception.message = Application Not Responding.
app.screen.name on a span or log record The same value as fusescope.rum.view.route.
The record’s event name The event.name attribute, as for every client.

Crashes, ANRs and screens therefore show up without extra code. For the rest of the screens, add what the table in Attributes the screens need lists: session.id (OpenTelemetry Android sets it), spans named view with fusescope.rum.view.loading_type, and HTTP client spans with traceparent sent to your backend. Send exception.stacktrace as Java prints it (Throwable.printStackTrace() output): FuseScope parses it, retraces it with the R8 mapping of service.version, and groups it as described in Grouping Android errors. The Android SDK’s README lists every attribute io.fuseone.fusescope:android sends.