From 31fa293cda017b3b6cfa3c3d7b62937937bafed4 Mon Sep 17 00:00:00 2001 From: Abinet Date: Tue, 28 Feb 2023 14:06:07 -0800 Subject: [PATCH 1/5] feat(instrumentation-documentLoad): custom attributes to document load spans --- .../auto-instrumentations-web/src/utils.ts | 4 +- .../README.md | 24 +++++ .../src/index.ts | 1 + .../src/instrumentation.ts | 46 +++++++++- .../src/types.ts | 34 +++++++ .../test/documentLoad.test.ts | 90 +++++++++++++++++++ 6 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 plugins/web/opentelemetry-instrumentation-document-load/src/types.ts diff --git a/metapackages/auto-instrumentations-web/src/utils.ts b/metapackages/auto-instrumentations-web/src/utils.ts index a139a06da8..50b431069b 100644 --- a/metapackages/auto-instrumentations-web/src/utils.ts +++ b/metapackages/auto-instrumentations-web/src/utils.ts @@ -15,7 +15,7 @@ */ import { diag } from '@opentelemetry/api'; -import { Instrumentation } from '@opentelemetry/instrumentation'; +import { Instrumentation, InstrumentationConfig } from '@opentelemetry/instrumentation'; import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'; @@ -55,7 +55,7 @@ export function getWebAutoInstrumentations( >) { const Instance = InstrumentationMap[name]; // Defaults are defined by the instrumentation itself - const userConfig = inputConfigs[name] ?? {}; + const userConfig : InstrumentationConfig = inputConfigs[name] ?? {}; if (userConfig.enabled === false) { diag.debug(`Disabling instrumentation for ${name}`); diff --git a/plugins/web/opentelemetry-instrumentation-document-load/README.md b/plugins/web/opentelemetry-instrumentation-document-load/README.md index c01b34bc3c..c81a6b52a4 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/README.md +++ b/plugins/web/opentelemetry-instrumentation-document-load/README.md @@ -82,6 +82,30 @@ Because the browser does not send a trace context header for the initial page na ``` + +## Optional : Add custom attributes to document load span if needed + +If it is needed to add custom attributes to the document load span,and/or document fetch span and/or resource fetch spans, respective functions to do so needs to be provided +as a config to the DocumentLoad Instrumentation as shown below. The attributes will be added to the respective spans +before the individual are spans are ended. If the function throws an error , no attributes will be added to the span and +the rest of the process continues. + +```js +const addCustomAttributesToSpan = (span: Span) => { + span.setAttribute('',''); +} +registerInstrumentations({ + instrumentations: [ + new DocumentLoadInstrumentation({ + applyAttributes: { + documentLoad: addCustomAttributesToSpan + } + }) + ] +}) + + +``` See [examples/tracer-web](https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/tracer-web) for a short example. ## Useful links diff --git a/plugins/web/opentelemetry-instrumentation-document-load/src/index.ts b/plugins/web/opentelemetry-instrumentation-document-load/src/index.ts index c464af622c..45cd8db559 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/src/index.ts +++ b/plugins/web/opentelemetry-instrumentation-document-load/src/index.ts @@ -16,3 +16,4 @@ export * from './instrumentation'; export * from './enums/AttributeNames'; +export * from './types'; diff --git a/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts b/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts index 49b3bb6b01..5dc182bb05 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts +++ b/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts @@ -31,8 +31,12 @@ import { } from '@opentelemetry/sdk-trace-web'; import { InstrumentationBase, - InstrumentationConfig, + safeExecuteInTheMiddle, } from '@opentelemetry/instrumentation'; +import { + DocumentLoadCustomAttributeFunction, + DocumentLoadInstrumentationConfig, +} from './types'; import { AttributeNames } from './enums/AttributeNames'; import { VERSION } from './version'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; @@ -53,7 +57,7 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { * * @param config */ - constructor(config: InstrumentationConfig = {}) { + constructor(config: DocumentLoadInstrumentationConfig = {}) { super('@opentelemetry/instrumentation-document-load', VERSION, config); } @@ -113,6 +117,10 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { fetchSpan.setAttribute(SemanticAttributes.HTTP_URL, location.href); context.with(trace.setSpan(context.active(), fetchSpan), () => { addSpanNetworkEvents(fetchSpan, entries); + this._addCustomAttributesOnSpan( + fetchSpan, + this._getConfig().applyAttributes?.documentFetch + ); this._endSpan(fetchSpan, PTN.RESPONSE_END, entries); }); } @@ -141,7 +149,10 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_END, entries); addSpanPerformancePaintEvents(rootSpan); - + this._addCustomAttributesOnSpan( + rootSpan, + this._getConfig().applyAttributes?.documentLoad + ); this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries); }); } @@ -186,6 +197,10 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { if (span) { span.setAttribute(SemanticAttributes.HTTP_URL, resource.name); addSpanNetworkEvents(span, resource); + this._addCustomAttributesOnSpan( + span, + this._getConfig().applyAttributes?.resourceFetch + ); this._endSpan(span, PTN.RESPONSE_END, resource); } } @@ -231,6 +246,31 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { } } + private _getConfig(): DocumentLoadInstrumentationConfig { + return this._config; + } + /** + * adds custom attributes to root span if configured + */ + private _addCustomAttributesOnSpan( + span: Span, + applyCustomAttributesOnSpan: DocumentLoadCustomAttributeFunction | undefined + ) { + if (applyCustomAttributesOnSpan) { + safeExecuteInTheMiddle( + () => applyCustomAttributesOnSpan(span), + error => { + if (!error) { + return; + } + + this._diag.error('addCustomAttributesOnSpan', error); + }, + true + ); + } + } + /** * implements enable function */ diff --git a/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts b/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts new file mode 100644 index 0000000000..faf4e42589 --- /dev/null +++ b/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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 + * + * https://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 { Span } from '@opentelemetry/api'; +import { InstrumentationConfig } from '@opentelemetry/instrumentation'; + +export interface DocumentLoadCustomAttributeFunction { + (span: Span): void; +} + +/** + * DocumentLoadInstrumentationPlugin Config + */ +export interface DocumentLoadInstrumentationConfig + extends InstrumentationConfig { + /** Function for adding custom attributes on the document load, document fetch and or resource fetch spans */ + applyAttributes?: { + documentLoad?: DocumentLoadCustomAttributeFunction; + documentFetch?: DocumentLoadCustomAttributeFunction; + resourceFetch?: DocumentLoadCustomAttributeFunction; + }; +} diff --git a/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts b/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts index 6b7422df98..263e499e08 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts +++ b/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts @@ -653,6 +653,96 @@ describe('DocumentLoad Instrumentation', () => { }); shouldExportCorrectSpan(); }); + + describe('add custom attributes to spans', () => { + let spyEntries: any; + beforeEach(() => { + spyEntries = sandbox.stub(window.performance, 'getEntriesByType'); + spyEntries.withArgs('navigation').returns([entries]); + spyEntries.withArgs('resource').returns(resources); + spyEntries.withArgs('paint').returns([]); + }); + afterEach(() => { + spyEntries.restore(); + }); + + it('should add attribute to document load span', done => { + plugin = new DocumentLoadInstrumentation({ + enabled: false, + applyAttributes: { + documentLoad: span => { + span.setAttribute('custom-key', 'custom-val'); + }, + }, + }); + plugin.enable(); + setTimeout(() => { + const rootSpan = exporter.getFinishedSpans()[3] as ReadableSpan; + assert.strictEqual(rootSpan.attributes['custom-key'], 'custom-val'); + assert.strictEqual(exporter.getFinishedSpans().length, 4); + done(); + }); + }); + + it('should add attribute to document fetch span', done => { + plugin = new DocumentLoadInstrumentation({ + enabled: false, + applyAttributes: { + documentFetch: span => { + span.setAttribute('custom-key', 'custom-val'); + }, + }, + }); + plugin.enable(); + setTimeout(() => { + const fetchSpan = exporter.getFinishedSpans()[0] as ReadableSpan; + assert.strictEqual(fetchSpan.attributes['custom-key'], 'custom-val'); + assert.strictEqual(exporter.getFinishedSpans().length, 4); + done(); + }); + }); + + it('should add attribute to resource fetch spans', done => { + plugin = new DocumentLoadInstrumentation({ + enabled: false, + applyAttributes: { + resourceFetch: span => { + span.setAttribute('custom-key', 'custom-val'); + }, + }, + }); + plugin.enable(); + setTimeout(() => { + const resourceSpan1 = exporter.getFinishedSpans()[1] as ReadableSpan; + const resourceSpan2 = exporter.getFinishedSpans()[2] as ReadableSpan; + assert.strictEqual( + resourceSpan1.attributes['custom-key'], + 'custom-val' + ); + assert.strictEqual( + resourceSpan2.attributes['custom-key'], + 'custom-val' + ); + assert.strictEqual(exporter.getFinishedSpans().length, 4); + done(); + }); + }); + it('should still create the spans if the function throws error', done => { + plugin = new DocumentLoadInstrumentation({ + enabled: false, + applyAttributes: { + documentLoad: span => { + throw new Error('test error'); + }, + }, + }); + plugin.enable(); + setTimeout(() => { + assert.strictEqual(exporter.getFinishedSpans().length, 4); + done(); + }); + }); + }); }); /** From 55be88e08c95e9199b1168ce337622a22f3f5d4b Mon Sep 17 00:00:00 2001 From: Abinet Date: Wed, 8 Mar 2023 09:48:50 -0800 Subject: [PATCH 2/5] feat: change config name for consistency --- .../src/instrumentation.ts | 6 +++--- .../src/types.ts | 2 +- .../test/documentLoad.test.ts | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts b/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts index 5dc182bb05..a13f3845b3 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts +++ b/plugins/web/opentelemetry-instrumentation-document-load/src/instrumentation.ts @@ -119,7 +119,7 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { addSpanNetworkEvents(fetchSpan, entries); this._addCustomAttributesOnSpan( fetchSpan, - this._getConfig().applyAttributes?.documentFetch + this._getConfig().applyCustomAttributesOnSpan?.documentFetch ); this._endSpan(fetchSpan, PTN.RESPONSE_END, entries); }); @@ -151,7 +151,7 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { addSpanPerformancePaintEvents(rootSpan); this._addCustomAttributesOnSpan( rootSpan, - this._getConfig().applyAttributes?.documentLoad + this._getConfig().applyCustomAttributesOnSpan?.documentLoad ); this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries); }); @@ -199,7 +199,7 @@ export class DocumentLoadInstrumentation extends InstrumentationBase { addSpanNetworkEvents(span, resource); this._addCustomAttributesOnSpan( span, - this._getConfig().applyAttributes?.resourceFetch + this._getConfig().applyCustomAttributesOnSpan?.resourceFetch ); this._endSpan(span, PTN.RESPONSE_END, resource); } diff --git a/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts b/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts index faf4e42589..0d96d53395 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts +++ b/plugins/web/opentelemetry-instrumentation-document-load/src/types.ts @@ -26,7 +26,7 @@ export interface DocumentLoadCustomAttributeFunction { export interface DocumentLoadInstrumentationConfig extends InstrumentationConfig { /** Function for adding custom attributes on the document load, document fetch and or resource fetch spans */ - applyAttributes?: { + applyCustomAttributesOnSpan?: { documentLoad?: DocumentLoadCustomAttributeFunction; documentFetch?: DocumentLoadCustomAttributeFunction; resourceFetch?: DocumentLoadCustomAttributeFunction; diff --git a/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts b/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts index 263e499e08..2545ce3b05 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts +++ b/plugins/web/opentelemetry-instrumentation-document-load/test/documentLoad.test.ts @@ -669,7 +669,7 @@ describe('DocumentLoad Instrumentation', () => { it('should add attribute to document load span', done => { plugin = new DocumentLoadInstrumentation({ enabled: false, - applyAttributes: { + applyCustomAttributesOnSpan: { documentLoad: span => { span.setAttribute('custom-key', 'custom-val'); }, @@ -687,7 +687,7 @@ describe('DocumentLoad Instrumentation', () => { it('should add attribute to document fetch span', done => { plugin = new DocumentLoadInstrumentation({ enabled: false, - applyAttributes: { + applyCustomAttributesOnSpan: { documentFetch: span => { span.setAttribute('custom-key', 'custom-val'); }, @@ -705,7 +705,7 @@ describe('DocumentLoad Instrumentation', () => { it('should add attribute to resource fetch spans', done => { plugin = new DocumentLoadInstrumentation({ enabled: false, - applyAttributes: { + applyCustomAttributesOnSpan: { resourceFetch: span => { span.setAttribute('custom-key', 'custom-val'); }, @@ -730,7 +730,7 @@ describe('DocumentLoad Instrumentation', () => { it('should still create the spans if the function throws error', done => { plugin = new DocumentLoadInstrumentation({ enabled: false, - applyAttributes: { + applyCustomAttributesOnSpan: { documentLoad: span => { throw new Error('test error'); }, From 281e26f867bcd09e915a56af8563d15a1a35a356 Mon Sep 17 00:00:00 2001 From: Abinet Date: Wed, 8 Mar 2023 09:53:53 -0800 Subject: [PATCH 3/5] feat: minor read me fix --- .../web/opentelemetry-instrumentation-document-load/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/web/opentelemetry-instrumentation-document-load/README.md b/plugins/web/opentelemetry-instrumentation-document-load/README.md index c81a6b52a4..e452b67b22 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/README.md +++ b/plugins/web/opentelemetry-instrumentation-document-load/README.md @@ -97,7 +97,7 @@ const addCustomAttributesToSpan = (span: Span) => { registerInstrumentations({ instrumentations: [ new DocumentLoadInstrumentation({ - applyAttributes: { + applyCustomAttributesOnSpan: { documentLoad: addCustomAttributesToSpan } }) From 0b84152f5a7739223f44a392e24e01d69a5b54db Mon Sep 17 00:00:00 2001 From: Martin Kuba Date: Thu, 9 Mar 2023 15:33:51 -0800 Subject: [PATCH 4/5] Fixing markdown lint --- .../web/opentelemetry-instrumentation-document-load/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/web/opentelemetry-instrumentation-document-load/README.md b/plugins/web/opentelemetry-instrumentation-document-load/README.md index e452b67b22..ec6595c57b 100644 --- a/plugins/web/opentelemetry-instrumentation-document-load/README.md +++ b/plugins/web/opentelemetry-instrumentation-document-load/README.md @@ -82,7 +82,6 @@ Because the browser does not send a trace context header for the initial page na ``` - ## Optional : Add custom attributes to document load span if needed If it is needed to add custom attributes to the document load span,and/or document fetch span and/or resource fetch spans, respective functions to do so needs to be provided @@ -103,9 +102,8 @@ registerInstrumentations({ }) ] }) - - ``` + See [examples/tracer-web](https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/tracer-web) for a short example. ## Useful links From 72b4971e1245a8fbcd8e1b519dfe490a3c84a4ed Mon Sep 17 00:00:00 2001 From: Martin Kuba Date: Thu, 9 Mar 2023 15:41:10 -0800 Subject: [PATCH 5/5] lint --- metapackages/auto-instrumentations-web/src/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/metapackages/auto-instrumentations-web/src/utils.ts b/metapackages/auto-instrumentations-web/src/utils.ts index 50b431069b..1127c0f1f2 100644 --- a/metapackages/auto-instrumentations-web/src/utils.ts +++ b/metapackages/auto-instrumentations-web/src/utils.ts @@ -15,7 +15,10 @@ */ import { diag } from '@opentelemetry/api'; -import { Instrumentation, InstrumentationConfig } from '@opentelemetry/instrumentation'; +import { + Instrumentation, + InstrumentationConfig, +} from '@opentelemetry/instrumentation'; import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'; @@ -55,7 +58,7 @@ export function getWebAutoInstrumentations( >) { const Instance = InstrumentationMap[name]; // Defaults are defined by the instrumentation itself - const userConfig : InstrumentationConfig = inputConfigs[name] ?? {}; + const userConfig: InstrumentationConfig = inputConfigs[name] ?? {}; if (userConfig.enabled === false) { diag.debug(`Disabling instrumentation for ${name}`);