UNPKG

@opentelemetry/instrumentation-document-load

Version:
205 lines 8.77 kB
/* * 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 { context, propagation, trace, ROOT_CONTEXT, } from '@opentelemetry/api'; import { otperformance, TRACE_PARENT_HEADER } from '@opentelemetry/core'; import { addSpanNetworkEvent, addSpanNetworkEvents, hasKey, PerformanceTimingNames as PTN, } from '@opentelemetry/sdk-trace-web'; import { InstrumentationBase, safeExecuteInTheMiddle, } from '@opentelemetry/instrumentation'; import { AttributeNames } from './enums/AttributeNames'; /** @knipignore */ import { PACKAGE_NAME, PACKAGE_VERSION } from './version'; import { SEMATTRS_HTTP_URL, SEMATTRS_HTTP_USER_AGENT, } from '@opentelemetry/semantic-conventions'; import { addSpanPerformancePaintEvents, getPerformanceNavigationEntries, } from './utils'; /** * This class represents a document load plugin */ export class DocumentLoadInstrumentation extends InstrumentationBase { component = 'document-load'; version = '1'; moduleName = this.component; constructor(config = {}) { super(PACKAGE_NAME, PACKAGE_VERSION, config); } init() { } /** * callback to be executed when page is loaded */ _onDocumentLoaded() { // Timeout is needed as load event doesn't have yet the performance metrics for loadEnd. // Support for event "loadend" is very limited and cannot be used window.setTimeout(() => { this._collectPerformance(); }); } /** * Adds spans for all resources * @param rootSpan */ _addResourcesSpans(rootSpan) { const resources = otperformance.getEntriesByType?.('resource'); if (resources) { resources.forEach(resource => { this._initResourceSpan(resource, rootSpan); }); } } /** * Collects information about performance and creates appropriate spans */ _collectPerformance() { const metaElement = Array.from(document.getElementsByTagName('meta')).find(e => e.getAttribute('name') === TRACE_PARENT_HEADER); const entries = getPerformanceNavigationEntries(); const traceparent = (metaElement && metaElement.content) || ''; context.with(propagation.extract(ROOT_CONTEXT, { traceparent }), () => { const rootSpan = this._startSpan(AttributeNames.DOCUMENT_LOAD, PTN.FETCH_START, entries); if (!rootSpan) { return; } context.with(trace.setSpan(context.active(), rootSpan), () => { const fetchSpan = this._startSpan(AttributeNames.DOCUMENT_FETCH, PTN.FETCH_START, entries); if (fetchSpan) { fetchSpan.setAttribute(SEMATTRS_HTTP_URL, location.href); context.with(trace.setSpan(context.active(), fetchSpan), () => { addSpanNetworkEvents(fetchSpan, entries, this.getConfig().ignoreNetworkEvents); this._addCustomAttributesOnSpan(fetchSpan, this.getConfig().applyCustomAttributesOnSpan?.documentFetch); this._endSpan(fetchSpan, PTN.RESPONSE_END, entries); }); } }); rootSpan.setAttribute(SEMATTRS_HTTP_URL, location.href); rootSpan.setAttribute(SEMATTRS_HTTP_USER_AGENT, navigator.userAgent); this._addResourcesSpans(rootSpan); if (!this.getConfig().ignoreNetworkEvents) { addSpanNetworkEvent(rootSpan, PTN.FETCH_START, entries); addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_START, entries); addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_END, entries); addSpanNetworkEvent(rootSpan, PTN.DOM_INTERACTIVE, entries); addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_START, entries); addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_END, entries); addSpanNetworkEvent(rootSpan, PTN.DOM_COMPLETE, entries); addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_START, entries); addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_END, entries); } if (!this.getConfig().ignorePerformancePaintEvents) { addSpanPerformancePaintEvents(rootSpan); } this._addCustomAttributesOnSpan(rootSpan, this.getConfig().applyCustomAttributesOnSpan?.documentLoad); this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries); }); } /** * Helper function for ending span * @param span * @param performanceName name of performance entry for time end * @param entries */ _endSpan(span, performanceName, entries) { // span can be undefined when entries are missing the certain performance - the span will not be created if (span) { if (hasKey(entries, performanceName)) { span.end(entries[performanceName]); } else { // just end span span.end(); } } } /** * Creates and ends a span with network information about resource added as timed events * @param resource * @param parentSpan */ _initResourceSpan(resource, parentSpan) { const span = this._startSpan(AttributeNames.RESOURCE_FETCH, PTN.FETCH_START, resource, parentSpan); if (span) { span.setAttribute(SEMATTRS_HTTP_URL, resource.name); addSpanNetworkEvents(span, resource, this.getConfig().ignoreNetworkEvents); this._addCustomAttributesOnResourceSpan(span, resource, this.getConfig().applyCustomAttributesOnSpan?.resourceFetch); this._endSpan(span, PTN.RESPONSE_END, resource); } } /** * Helper function for starting a span * @param spanName name of span * @param performanceName name of performance entry for time start * @param entries * @param parentSpan */ _startSpan(spanName, performanceName, entries, parentSpan) { if (hasKey(entries, performanceName) && typeof entries[performanceName] === 'number') { const span = this.tracer.startSpan(spanName, { startTime: entries[performanceName], }, parentSpan ? trace.setSpan(context.active(), parentSpan) : undefined); return span; } return undefined; } /** * executes callback {_onDocumentLoaded} when the page is loaded */ _waitForPageLoad() { if (window.document.readyState === 'complete') { this._onDocumentLoaded(); } else { this._onDocumentLoaded = this._onDocumentLoaded.bind(this); window.addEventListener('load', this._onDocumentLoaded); } } /** * adds custom attributes to root span if configured */ _addCustomAttributesOnSpan(span, applyCustomAttributesOnSpan) { if (applyCustomAttributesOnSpan) { safeExecuteInTheMiddle(() => applyCustomAttributesOnSpan(span), error => { if (!error) { return; } this._diag.error('addCustomAttributesOnSpan', error); }, true); } } /** * adds custom attributes to span if configured */ _addCustomAttributesOnResourceSpan(span, resource, applyCustomAttributesOnSpan) { if (applyCustomAttributesOnSpan) { safeExecuteInTheMiddle(() => applyCustomAttributesOnSpan(span, resource), error => { if (!error) { return; } this._diag.error('addCustomAttributesOnResourceSpan', error); }, true); } } /** * implements enable function */ enable() { // remove previously attached load to avoid adding the same event twice // in case of multiple enable calling. window.removeEventListener('load', this._onDocumentLoaded); this._waitForPageLoad(); } /** * implements disable function */ disable() { window.removeEventListener('load', this._onDocumentLoaded); } } //# sourceMappingURL=instrumentation.js.map