Skip to content

Commit

Permalink
feat: attach browser.instance.id and `browser.instance.visibility_s…
Browse files Browse the repository at this point in the history
…tate` to spans (#878)
  • Loading branch information
Joozty authored Nov 6, 2024
1 parent 02229af commit 2a58d98
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 2 deletions.
7 changes: 5 additions & 2 deletions packages/web/integration-tests/tests/cookies/cookies.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ module.exports = {
// This should create two streams of documentLoad sequences, all with the same sessionId but having
// two scriptInstances (one from parent, one from iframe)
const parent = await browser.globals.findSpan(span => span.name === 'documentFetch' && span.tags['location.href'].includes('cookies.ejs'));
await browser.assert.ok(parent.tags['splunk.rumSessionId']);
await browser.assert.ok(parent.tags['browser.instance.id']);

await browser.assert.notEqual(parent.tags['splunk.scriptInstance'], parent.tags['splunk.rumSessionId']);

const iframe = await browser.globals.findSpan(span => span.name === 'documentFetch' && span.tags['location.href'].includes('iframe.ejs'));
await browser.assert.ok(iframe.tags['splunk.rumSessionId']);
await browser.assert.notEqual(iframe.tags['splunk.scriptInstance'], iframe.tags['splunk.rumSessionId']);

// same session id
// same session id & instanceId
await browser.assert.equal(parent.tags['splunk.rumSessionId'], iframe.tags['splunk.rumSessionId']);
await browser.assert.equal(parent.tags['browser.instance.id'], iframe.tags['browser.instance.id']);

// but different scriptInstance
await browser.assert.notEqual(parent.tags['splunk.scriptInstance'], iframe.tags['splunk.scriptInstance']);

Expand Down
1 change: 1 addition & 0 deletions packages/web/src/SplunkSpanAttributesProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class SplunkSpanAttributesProcessor implements SpanProcessor {
span.setAttribute('location.href', location.href);
span.setAttributes(this._globalAttributes);
span.setAttribute('splunk.rumSessionId', getRumSessionId());
span.setAttribute('browser.instance.visibility_state', document.visibilityState);
}

onEnd(): void {
Expand Down
5 changes: 5 additions & 0 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { SessionBasedSampler } from './SessionBasedSampler';
import { SocketIoClientInstrumentationConfig, SplunkSocketIoClientInstrumentation } from './SplunkSocketIoClientInstrumentation';
import { SplunkOTLPTraceExporter } from './exporters/otlp';
import { registerGlobal, unregisterGlobal } from './global-utils';
import { BrowserInstanceService } from './services/BrowserInstanceService';

export { SplunkExporterConfig } from './exporters/common';
export { SplunkZipkinExporter } from './exporters/zipkin';
Expand Down Expand Up @@ -436,6 +437,10 @@ export const SplunkRum: SplunkOtelWebType = {
'app': applicationName,
};

if(BrowserInstanceService.id) {
resourceAttrs['browser.instance.id'] = BrowserInstanceService.id;
}

const syntheticsRunId = getSyntheticsRunId();
if (syntheticsRunId) {
resourceAttrs[SYNTHETICS_RUN_ID_ATTRIBUTE] = syntheticsRunId;
Expand Down
60 changes: 60 additions & 0 deletions packages/web/src/services/BrowserInstanceService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2024 Splunk Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { safelyGetSessionStorage, safelySetSessionStorage } from '../utils/storage';
import { generateId } from '../utils';

const BROWSER_INSTANCE_ID_KEY = 'browser_instance_id';

/**
* BrowserInstanceService is responsible for generating and storing a unique ID for the tab.
* THe ID is stored in the session storage, so stays the same even after page reload
* as long as the tab stays on the same domain.
* This ID will be the same for all frames/context in the tab as long as they are on the same domain.
* Currently, this is simplified version which has this limitation:
* - It does not cover the case when tab is duplicated -
* browsers copy storage when duplicating tabs so the ID will be the same
* To cover this case we need to implement a communication between tabs which requires asynchronous initialization.
* This is not implemented yet as requires bigger refactoring.
*/
export class BrowserInstanceService {
// `undefined` represents the case when the storage is inaccessible.
static _id: string | undefined | null = null;

static get id(): string | undefined {
if(this._id !== null) {
return this._id;
}


// Check if the ID is already stored in the session storage. It might be generated by another frame/context.
let browserInstanceId = safelyGetSessionStorage(BROWSER_INSTANCE_ID_KEY);
if(browserInstanceId) {
this._id = browserInstanceId;
} else if(browserInstanceId === null) {
// Storage is accessible but the ID is not stored yet.
browserInstanceId = generateId(64);
this._id = browserInstanceId;
safelySetSessionStorage(BROWSER_INSTANCE_ID_KEY, browserInstanceId);
} else {
// Storage is not accessible.
this._id = undefined;
}


return this._id;
}
}
36 changes: 36 additions & 0 deletions packages/web/src/utils/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright 2024 Splunk Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export const safelyGetSessionStorage = (key: string): string | null | undefined => {
try {
return window.sessionStorage.getItem(key);
} catch {
return undefined;
// sessionStorage not accessible probably user is in incognito-mode
// or set "Block third-party cookies" option in browser settings
}
};

export const safelySetSessionStorage = (key: string, value: string): boolean => {
try {
window.sessionStorage.setItem(key, value);
return true;
} catch {
// sessionStorage not accessible probably user is in incognito-mode
// or set "Block third-party cookies" option in browser settings
return false;
}
};

0 comments on commit 2a58d98

Please sign in to comment.