opentelemetry-js
opentelemetry-js copied to clipboard
CORS requirements on web exporters with navigator.sendBeacon
- [x] This only affects the JavaScript OpenTelemetry library
navigator.sendBeacon with non-simple content-type header like "application/json" requires CORS checks like Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, and Access-Control-Allow-Credentials to be set with appropriate values on preflight requests.
Notably, sendBeacon initialize the request with credential mode set to true with a non-simple content-type, which means the server has to respond to preflight requests with Access-Control-Allow-Credentials true too. This is not configurable nor an option exposed with the sendBeacon API. That is to say, credentials like cookies, authorization headers or TLS client certificates are sent with sendBeacon in this case. However, the credential mode is configurable with fetch and XHR APIs.
We have the following use cases on sendBeacon:
- OTLP exporter: Sent with
application/jsoncontent type, not tested if OTLP server responds with proper CORS headers. - Zipkin exporter: actually, the Zipkin server accepts JSON data sent over content-type as
text/plain, so the problem does not apply to Zipkin backends. - Zipkin exporter sending to Jaeger backend: Not supported without a reverse proxy, Jaeger server rejects
text/plaincontent-type, and doesn't respond withAccess-Control-Allow-CredentialsCORS header. (issue: https://github.com/open-telemetry/opentelemetry-js/issues/1727)
Currently, we don't provide options on how those data are sent, like sent with fetch (environments that don't support XHR or beacon), XHR or beacon, and the CORS credential modes like omit or include. I think it may be worthwhile to add a package that shares all the necessary setups on the web platform.
For the Zipkin compatible mode of the Jaeger server, I'm if it is behaving in an expected way. Maybe @yurishkuro can chime in here?
Zipkin receiver in Jaeger has a config option for cors.
In https://www.jaegertracing.io/docs/1.35/cli/ I see the allowed headers and allowed origins, but I don't see the allow credentials response header. Is there somewhere else I should look for cors configs?
Zipkin receiver in Jaeger has a config option for cors.
Yes, but as stated in the OP it is missing Access-Control-Allow-Credentials CORS header. This behavior is as same as Zipkin's. Yet, Zipkin supports interpreting simple content types like text/plain so navigator.sendBeacon (the API Zipkin exporter uses by default) can successfully send data to Zipkin servers. However, Jaeger (tested with 1.35) does not support simple content types and rejects requests that are not typed as MIME application/json.
With MIME types like application/json, navigator.sendBeacon requires the server to respond with CORS headers, and specifically Access-Control-Allow-Credentials need to be true.
but I don't see the allow credentials response header
Seems like easily added if needed. The full Options struct has a lot more fields than what Jaeger CLI flags currently support - https://github.com/rs/cors/blob/da52b0701de54d82f71776d634d4183b69c3a915/cors.go#L32
Having said that, maybe it's better to implement this in OTLP receiver, then Jaeger could use that as well. I prefer to spend less effort on supporting legacy formats like Zipkin.
This works fine for me for now ~
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const exporter = new OTLPTraceExporter({
headers: {}, // magic here !!!
url: '<your ot receiver host>/v1/traces',
});
After digging into the source code with debugger i found this, with the header: {} option, ot sender will use xhr instead of sendBeacon !
so without credentials: 'include',
this config on ot receiver will work
receivers:
otlp:
protocols:
grpc:
http:
cors:
allowed_origins: ["*"]
allowed_headers: ["*"]
Maybe we should include a way to force xhr or sendbeacon by config
Maybe we should include a way to force xhr or sendbeacon by config
Yeah, this is what I have in my mind too. I'm thinking about introducing a new @opentelemetry/core-web package for web utilities that need to be shared across various web packages. Currently, those utils are located in the @opentelemetry/sdk-trace-web (e.g. https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-web/src/utils.ts#L201).
Maybe consider having xhr (or fetch considering how it's available in node v18+, deno v1 and usually polyfilled in IE) as the default method in general and navigator.sendBeacon as backup when in browser document is being unloaded?
- sendBeacon has a 64KB limit. In firefox this is per sendBeacon call (eg can do 64KB + 64KB + 64KB), but in chrome this limit also applies for requests queue so 64 KB + won't send + won't send

- It's common to send RUM data directly to a vendor endpoint (eg.
https://bam.vendor-data.net/). There is also a rule in EasyPrivacy list used by content blockers like adblock, ublock origin and such:
! Block ping
$ping,third-party
What this rule means is all sendBeacon calls to third-party domains should be blocked, meaning all data from users with this adblock list enabled would be missing
- More of a future looking but should any format introduce support for retrying sending data, it would be necessary to read response from server
In my opinion, xhr or fetch is more predictable ~
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.
This issue was closed because it has been stale for 14 days with no activity.
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.
This issue was closed because it has been stale for 14 days with no activity.