From 31fa293cda017b3b6cfa3c3d7b62937937bafed4 Mon Sep 17 00:00:00 2001 From: Abinet Date: Tue, 28 Feb 2023 14:06:07 -0800 Subject: [PATCH] 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(); + }); + }); + }); }); /**