diff --git a/package-lock.json b/package-lock.json index 7fb23ee..ffcf5fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5222,6 +5222,8 @@ "integrity": "sha512-XavNYNBBfKIrT8Oi/ywJ0DoOOU+jHF5bQWTkqStFsAXvfCV9VzYN3J+TGAvZdrpXeoixqPRGUxQ2yZhD2iowdQ==", "dev": true, "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "@testim/chrome-version": "^1.1.3", "axios": "^1.2.1", @@ -15041,6 +15043,28 @@ "node": ">=14" } }, + "packages/web/node_modules/chromedriver": { + "version": "111.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-111.0.0.tgz", + "integrity": "sha512-XavNYNBBfKIrT8Oi/ywJ0DoOOU+jHF5bQWTkqStFsAXvfCV9VzYN3J+TGAvZdrpXeoixqPRGUxQ2yZhD2iowdQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@testim/chrome-version": "^1.1.3", + "axios": "^1.2.1", + "compare-versions": "^5.0.1", + "extract-zip": "^2.0.1", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^1.1.0", + "tcp-port-used": "^1.0.1" + }, + "bin": { + "chromedriver": "bin/chromedriver" + }, + "engines": { + "node": ">=14" + } + }, "packages/web/node_modules/node-fetch": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", @@ -18200,6 +18224,21 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==" }, + "chromedriver": { + "version": "111.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-111.0.0.tgz", + "integrity": "sha512-XavNYNBBfKIrT8Oi/ywJ0DoOOU+jHF5bQWTkqStFsAXvfCV9VzYN3J+TGAvZdrpXeoixqPRGUxQ2yZhD2iowdQ==", + "dev": true, + "requires": { + "@testim/chrome-version": "^1.1.3", + "axios": "^1.2.1", + "compare-versions": "^5.0.1", + "extract-zip": "^2.0.1", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^1.1.0", + "tcp-port-used": "^1.0.1" + } + }, "node-fetch": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", @@ -19325,6 +19364,8 @@ "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-111.0.0.tgz", "integrity": "sha512-XavNYNBBfKIrT8Oi/ywJ0DoOOU+jHF5bQWTkqStFsAXvfCV9VzYN3J+TGAvZdrpXeoixqPRGUxQ2yZhD2iowdQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@testim/chrome-version": "^1.1.3", "axios": "^1.2.1", diff --git a/packages/web/src/SplunkExporter.ts b/packages/web/src/SplunkExporter.ts index e12ddca..4445b24 100644 --- a/packages/web/src/SplunkExporter.ts +++ b/packages/web/src/SplunkExporter.ts @@ -115,7 +115,7 @@ export class SplunkExporter implements SpanExporter { spans = spans.filter(span => this._filter(span)); const zspans = spans.map(span => this._mapToZipkinSpan(span)); const zJson = JSON.stringify(zspans); - if (this._beaconSender) { + if (document.hidden && this._beaconSender && zJson.length <= 64000) { this._beaconSender(this.beaconUrl, zJson); } else { this._xhrSender(this.beaconUrl, zJson); diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index 1a22516..26e60ba 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -169,8 +169,8 @@ interface SplunkOtelWebConfigInternal extends SplunkOtelWebConfig { const OPTIONS_DEFAULTS: SplunkOtelWebConfigInternal = { app: 'unknown-browser-app', beaconUrl: undefined, - bufferTimeout: 5000, //millis, tradeoff between batching and loss of spans by not sending before page close - bufferSize: 20, // spans, tradeoff between batching and hitting sendBeacon invididual limits + bufferTimeout: 4000, //millis, tradeoff between batching and loss of spans by not sending before page close + bufferSize: 50, // spans, tradeoff between batching and hitting sendBeacon invididual limits instrumentations: {}, exporter: { factory: (options) => new SplunkExporter(options), diff --git a/packages/web/test/SplunkExporter.test.ts b/packages/web/test/SplunkExporter.test.ts index 36724eb..3c8863c 100644 --- a/packages/web/test/SplunkExporter.test.ts +++ b/packages/web/test/SplunkExporter.test.ts @@ -57,14 +57,25 @@ describe('SplunkExporter', () => { beaconSenderMock.restore(); }); - it('uses Beacon API if available', () => { + it('uses Beacon API if in background', () => { exporter = new SplunkExporter({ beaconUrl: 'https://domain1', xhrSender: xhrSenderMock, }); + const targetDocProto = Object.getPrototypeOf(Object.getPrototypeOf(document)); + const oldDef = Object.getOwnPropertyDescriptor(targetDocProto, 'hidden'); + Object.defineProperty(targetDocProto, 'hidden', { + get() { + return true; + }, + configurable: true, + enumerable: true, + }); const dummySpan = buildDummySpan(); exporter.export([dummySpan], () => {}); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Object.defineProperty(targetDocProto, 'hidden', oldDef!); expect(beaconSenderMock.args[0][0]).to.eq('https://domain1'); const sentSpan = JSON.parse(beaconSenderMock.args[0][1])[0]; @@ -94,6 +105,7 @@ describe('SplunkExporter', () => { it('limits spans sent', () => { exporter = new SplunkExporter({ beaconUrl: 'https://localhost', + xhrSender: xhrSenderMock, }); const dummySpan = buildDummySpan(); @@ -101,13 +113,14 @@ describe('SplunkExporter', () => { for (let i = 0; i < 110; i++) { spans.push(dummySpan); } exporter.export(spans, () => {}); - const sentSpans = JSON.parse(beaconSenderMock.getCall(0).args[1]); + const sentSpans = JSON.parse(xhrSenderMock.getCall(0).args[1]); expect(sentSpans).to.have.lengthOf(100); }); it('still exports parent spans', () => { exporter = new SplunkExporter({ beaconUrl: 'https://localhost', + xhrSender: xhrSenderMock, }); const dummySpan = buildDummySpan(); @@ -122,13 +135,14 @@ describe('SplunkExporter', () => { spans.push(parentSpan); exporter.export(spans, () => {}); - const sentSpans = JSON.parse(beaconSenderMock.getCall(0).args[1]); + const sentSpans = JSON.parse(xhrSenderMock.getCall(0).args[1]); expect(sentSpans).to.have.lengthOf(101); }); it('truncates long values', () => { exporter = new SplunkExporter({ beaconUrl: 'https://localhost', + xhrSender: xhrSenderMock, }); const dummySpan = buildDummySpan({ @@ -140,7 +154,7 @@ describe('SplunkExporter', () => { }); exporter.export([dummySpan], () => {}); - const sentSpan = JSON.parse(beaconSenderMock.getCall(0).args[1])[0]; + const sentSpan = JSON.parse(xhrSenderMock.getCall(0).args[1])[0]; expect(sentSpan.name).to.eq('a'.repeat(4096)); expect(sentSpan.tags['longValue']).to.eq('b'.repeat(4096)); expect(sentSpan.tags['shortValue']).to.eq('c'.repeat(4000)); @@ -149,6 +163,7 @@ describe('SplunkExporter', () => { it('filters out missing cors timings', () => { exporter = new SplunkExporter({ beaconUrl: 'https://localhost', + xhrSender: xhrSenderMock, }); const dummySpan = buildDummySpan({ @@ -175,13 +190,14 @@ describe('SplunkExporter', () => { }); exporter.export([dummySpan], () => {}); - const sentSpan = JSON.parse(beaconSenderMock.getCall(0).args[1])[0]; + const sentSpan = JSON.parse(xhrSenderMock.getCall(0).args[1])[0]; expect(sentSpan.annotations.length).to.eq(2); }); it('allows hooking into serialization', () => { exporter = new SplunkExporter({ beaconUrl: 'https://localhost', + xhrSender: xhrSenderMock, onAttributesSerializing: (attributes) => ({ ...attributes, key1: 'new value 1', @@ -198,7 +214,7 @@ describe('SplunkExporter', () => { }); exporter.export([dummySpan], () => {}); - const sentSpan = JSON.parse(beaconSenderMock.getCall(0).args[1])[0]; + const sentSpan = JSON.parse(xhrSenderMock.getCall(0).args[1])[0]; expect(sentSpan.name).to.eq(''); console.log(sentSpan.tags); expect(sentSpan.tags).to.deep.eq({