WebSocket Long Connection Tracking
# WebSocket Persistent Connection Collection
Starting from RUM SDK 3.3.6, native WebSocket connections in the browser can be aggregated as RUM Resources, used to analyze handshake, message traffic, inbound idle, send backlog, and close status.
This capability is currently an experimental feature and is disabled by default. The SDK does not read or upload WebSocket message bodies.
## Enable Collection
### NPM
```javascript
import { datafluxRum } from "@cloudcare/browser-rum"
datafluxRum.init({
applicationId: "<APPLICATION_ID>",
site: "<PUBLIC_OPENWAY_URL>",
clientToken: "<CLIENT_TOKEN>",
service: "websocket-client",
env: "production",
version: "1.0.0",
sessionSampleRate: 100,
enableExperimentalFeatures: ["track_websockets"],
})
The slim RUM package uses the same configuration:
CDN¶
<script
src="https://static.guance.com/browser-sdk/v3/dataflux-rum.js"
type="text/javascript"
></script>
<script>
window.DATAFLUX_RUM &&
window.DATAFLUX_RUM.init({
applicationId: "<APPLICATION_ID>",
site: "<PUBLIC_OPENWAY_URL>",
clientToken: "<CLIENT_TOKEN>",
service: "websocket-client",
env: "production",
version: "1.0.0",
sessionSampleRate: 100,
enableExperimentalFeatures: ["track_websockets"],
})
</script>
The above example uses the public OpenWay. When connecting directly via DataKit, replace site and clientToken with datakitOrigin, and do not configure both reporting addresses simultaneously.
enableExperimentalFeatures must be an array. Writing it directly as a string "track_websockets" will not enable collection.
Initialization Timing¶
RUM must be initialized before the business creates WebSocket connections:
datafluxRum.init({
// other configurations
enableExperimentalFeatures: ["track_websockets"],
})
const socket = new WebSocket("wss://example.com/socket")
The following connections will not be collected:
- Connections created before RUM initialization;
- Connections created using the cached native
WebSocketconstructor before initialization; - Connections created within Web Workers or Service Workers;
- Other transport implementations that do not go through
window.WebSocket; - Connections created by
WebSocketStream.
Enabling collection does not change the native WebSocket construction method, static constants, instanceof, business event listeners, or send() return behavior.
Collection Model¶
Each WebSocket Session segment generates one RUM Resource:
Resources are generated at the following times:
- When the browser receives a WebSocket
closeevent; - When the current RUM Session expires;
- When the page triggers
beforeunload; - When the SDK stops the current collection instance.
WebSocket is a persistent connection. While the connection is still open, it is normal that no final RUM Resource appears in the Network; the SDK does not periodically report connection snapshots.
When the page is refreshed, closed, or navigated away, the SDK will attempt to settle and send data during the beforeunload phase. If the page process is forcibly terminated, the browser crashes, or the device loses power, JavaScript may not have a chance to execute, and the last connection Resource may still be lost.
Normal Close¶
When the browser receives close:
The event includes the close code, close reason, and was_clean.
Session Expiration¶
When a session expires, connections still open are settled based on current data:
The business WebSocket is not closed at this point, so the close code, close reason, and was_clean may not exist. After a new session is established, the SDK will start a new statistical segment for the same physical connection, retaining the same connection_id, and resetting message counts and segment duration.
Messages between session expiration and the next renewal are not counted in any session. Connections created during this period and still open at renewal time will start being counted from the renewal moment of the new session.
Page Unload¶
When the page triggers beforeunload:
The connection may not trigger the browser's close event, so the close fields may not exist.
Handshake Failure¶
When a connection enters close without ever triggering open:
In this case, setup_duration represents the time from connection construction to close or session settlement, not the successful handshake duration. The browser typically uses close code 1006 to indicate abnormal closure, and the specific value depends on the browser event.
Message Statistics¶
The SDK only counts message counts and byte sizes, and does not collect message bodies:
| Message Type | Byte Calculation Method |
|---|---|
| string | UTF-8 byte count |
ArrayBuffer |
byteLength |
TypedArray, DataView |
Current view's byteLength |
Blob |
size |
| Unrecognized types | 0 |
For example, the string 你好 is counted as 6 bytes in UTF-8, not as JavaScript string length 2.
View Attribution¶
A WebSocket Session segment may span multiple RUM Views. The Resource additionally records:
start_view_id: The View where the current segment started;end_view_id: The View where the connection closed or settled.
When a segment spans pages, these two IDs can be different. The Resource still enters the RUM event pipeline based on the segment start time.
Reported Fields¶
In beforeSend, you can read event.resource.websocket; the final intake will convert the fields to resource_websocket_*.
Basic Resource Fields¶
beforeSend path |
intake field | description | unit |
|---|---|---|---|
resource.type |
resource_type |
Fixed as websocket |
- |
resource.url |
resource_url |
Browser-resolved ws:// or wss:// URL |
- |
resource.url_host |
resource_url_host |
URL host | - |
resource.url_path |
resource_url_path |
URL path | - |
resource.url_query |
resource_url_query |
URL query parameters | - |
resource.duration |
duration |
Current session segment duration | ns |
WebSocket Resources have no HTTP response, so there is no resource_status, resource_method, TTFB, download size, or HTTP timing.
Connection Fields¶
resource.websocket.* |
intake field | description | unit |
|---|---|---|---|
connection_id |
resource_websocket_connection_id |
Unique physical connection ID, unchanged across session segments | - |
handshake_succeeded |
resource_websocket_handshake_succeeded |
Whether open was received |
boolean |
start_time |
resource_websocket_start_time |
Current segment start time | Unix ms |
end_time |
resource_websocket_end_time |
Close or settlement time | Unix ms |
start_view_id |
resource_websocket_start_view_id |
View ID at segment start | - |
end_view_id |
resource_websocket_end_view_id |
View ID at connection end | - |
tracking_end_reason |
resource_websocket_tracking_end_reason |
close_event, session_end, or page_exit |
- |
protocol |
resource_websocket_protocol |
Server-negotiated sub-protocol | - |
setup_duration |
resource_websocket_setup_duration |
First segment: time from construction to open; renewal segments: 0 |
ns |
Message Fields¶
resource.websocket.* |
intake field | description | unit |
|---|---|---|---|
messages_in.count |
resource_websocket_messages_in_count |
Number of inbound messages | count |
messages_in.size |
resource_websocket_messages_in_size |
Total inbound message bytes | byte |
messages_out.count |
resource_websocket_messages_out_count |
Number of successful send() calls |
count |
messages_out.size |
resource_websocket_messages_out_size |
Total outbound message bytes | byte |
time_to_first_message_in |
resource_websocket_time_to_first_message_in |
Time from open to first inbound message |
ns |
time_to_first_message_out |
resource_websocket_time_to_first_message_out |
Time from open to first outbound message |
ns |
last_message_in_at |
resource_websocket_last_message_in_at |
Last inbound message time | Unix ms |
longest_inbound_silence |
resource_websocket_longest_inbound_silence |
Longest interval between consecutive inbound messages | ns |
inbound_idle_duration_before_close |
resource_websocket_inbound_idle_duration_before_close |
Time from last inbound message to close or settlement | ns |
buffered_amount_max |
resource_websocket_buffered_amount_max |
Peak bufferedAmount observed before each send() call |
byte |
buffered_amount_max is a sampled peak before calling send(), not a continuous monitoring value of the browser's send queue.
Close Fields¶
resource.websocket.* |
intake field | description |
|---|---|---|
close_code |
resource_websocket_close_code |
Close code from the browser's CloseEvent |
close_reason |
resource_websocket_close_reason |
Close reason |
was_clean |
resource_websocket_was_clean |
Whether the browser considers the connection closed cleanly |
These fields may not exist when settling due to session expiration or page unload.
Using beforeSend¶
You can inspect, supplement, or filter WebSocket Resources:
datafluxRum.init({
// other configurations
enableExperimentalFeatures: ["track_websockets"],
beforeSend(event, domainContext) {
if (
event.type === "resource" &&
event.resource?.type === "websocket"
) {
event.context = {
...event.context,
socket_channel: "notifications",
}
console.debug(
"WebSocket completed",
event.resource.websocket,
domainContext?.webSocket
)
}
return true
},
})
The domainContext for a WebSocket Resource contains:
domainContext is only used within the beforeSend callback and is not uploaded.
Returning false can discard a specific connection:
beforeSend(event) {
if (
event.type === "resource" &&
event.resource?.type === "websocket" &&
event.resource.url.includes("/health-stream")
) {
return false
}
return true
}
Privacy and Security¶
The SDK collects the full WebSocket URL and parses URL query parameters. Do not place passwords, long-term tokens, ID numbers, or other sensitive values in the URL.
Enabling collection does not bypass the page's CSP connect-src policy, server-side Origin validation, or other browser security policies, nor does it inject custom trace headers into the handshake request.
As long as a third-party library calls window.WebSocket after RUM initialization, the underlying connections will be collected:
- Each time auto-reconnect creates a new connection, a new
connection_idand Resource are generated; - When a single connection multiplexes multiple business topics, the SDK only provides connection-level aggregation;
- HTTP long polling phases are still collected as XHR or fetch Resources.
Verification¶
- Open the browser developer tools and confirm that the business WebSocket is connected and generating messages.
- Manually execute
socket.close(1000, "done"). - In the Network panel, filter for
/v1/write/rum. - Look for
resource_type=websocketin the request data. - Check
handshake_succeeded, message counts, byte sizes, and close fields.
If the connection is never closed, wait for the session to expire and check for tracking_end_reason=session_end. Refreshing the page should show tracking_end_reason=page_exit; this send is a best-effort report during the page exit phase.
Common Questions¶
No WebSocket Resource after configuration¶
Check the following in order:
- Is
enableExperimentalFeaturesan array containing"track_websockets"? - Is RUM initialized before the connection is created?
- Does the current session hit the
sessionSampleRate? - Is the connection closed, or has the session expired?
- Did
beforeSendreturnfalse? - Was the connection created by a Worker?
Received WebSocket error but no Resource yet¶
The SDK generates the final event on close or session settlement, not on the error event alone.
handshake_succeeded=false¶
The browser did not trigger open. Check the WebSocket URL, TLS certificate, CSP connect-src, reverse proxy Upgrade configuration, server-side Origin validation, and authentication.
Message size is 0¶
Confirm that the data type is string, ArrayBuffer, TypedArray, DataView, or Blob. The SDK does not serialize arbitrary objects to estimate size.
Cannot see HTTP status code¶
The browser WebSocket API does not expose the handshake HTTP status code to the page, so WebSocket Resources do not have resource_status.
```