UNPKG

@yotamloe/instrumentation-document-load

Version:

Fork of OpenTelemetry document-load automatic instrumentation package.

239 lines 10.6 kB
"use strict"; /* * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DocumentLoadInstrumentation = void 0; const api_1 = require("@opentelemetry/api"); const core_1 = require("@opentelemetry/core"); const sdk_trace_web_1 = require("@opentelemetry/sdk-trace-web"); const instrumentation_1 = require("@opentelemetry/instrumentation"); const AttributeNames_1 = require("./enums/AttributeNames"); const version_1 = require("./version"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const utils_1 = require("./utils"); /** * This class represents a document load plugin */ class DocumentLoadInstrumentation extends instrumentation_1.InstrumentationBase { /** * * @param config */ constructor(config = {}) { super('@opentelemetry/instrumentation-document-load', version_1.VERSION, config); this.component = 'document-load'; this.version = '1'; this.moduleName = this.component; } 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(); }); } _detectOs() { let userAgent = window.navigator.userAgent, platform = window.navigator.platform, macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'], iosPlatforms = ['iPhone', 'iPad', 'iPod'], os = 'unknown'; if (macosPlatforms.indexOf(platform) !== -1) { os = 'macOS'; } else if (iosPlatforms.indexOf(platform) !== -1) { os = 'iOS'; } else if (windowsPlatforms.indexOf(platform) !== -1) { os = 'windows'; } else if (/Android/.test(userAgent)) { os = 'android'; } else if (!os && /Linux/.test(platform)) { os = 'linux'; } return os; } _detectBrowser() { if ((navigator.userAgent.indexOf("Opera") || navigator.userAgent.indexOf('OPR')) != -1) { return 'opera'; } else if (navigator.userAgent.indexOf("Edg") != -1) { return 'edge'; } else if (navigator.userAgent.indexOf("Chrome") != -1) { return 'chrome'; } else if (navigator.userAgent.indexOf("Safari") != -1) { return 'safari'; } else if (navigator.userAgent.indexOf("Firefox") != -1) { return 'firefox'; } else if (navigator.userAgent.indexOf("MSIE") != -1) { return 'IE'; } else { return 'unknown'; } } /** * Adds spans for all resources * @param rootSpan */ _addResourcesSpans(rootSpan) { var _a, _b; const resources = (_b = (_a = core_1.otperformance).getEntriesByType) === null || _b === void 0 ? void 0 : _b.call(_a, 'resource'); if (resources) { resources.forEach(resource => { this._initResourceSpan(resource, rootSpan); }); } } /** * Collects information about performance and creates appropriate spans */ _collectPerformance() { const parseCookie = (str) => str .split(';') .map((v) => v.split('=')) .reduce((acc, v) => { acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim()); return acc; }, {}); const meta = document.createElement('meta'); meta.name = "traceparent"; let parsedCookie = parseCookie(document.cookie); meta.content = parsedCookie.traceparent || ''; if (!parsedCookie.traceparent) { console.debug('No traceparent cookie detected'); } const metaElement = Array.from(document.getElementsByTagName('meta')).find(e => e.getAttribute('name') === core_1.TRACE_PARENT_HEADER); const entries = utils_1.getPerformanceNavigationEntries(); const traceparent = (metaElement && metaElement.content) || (meta && meta.content) || ''; api_1.context.with(api_1.propagation.extract(api_1.ROOT_CONTEXT, { traceparent }), () => { const rootSpan = this._startSpan(AttributeNames_1.AttributeNames.DOCUMENT_LOAD, sdk_trace_web_1.PerformanceTimingNames.FETCH_START, entries); if (!rootSpan) { return; } api_1.context.with(api_1.trace.setSpan(api_1.context.active(), rootSpan), () => { const fetchSpan = this._startSpan(AttributeNames_1.AttributeNames.DOCUMENT_FETCH, sdk_trace_web_1.PerformanceTimingNames.FETCH_START, entries); if (fetchSpan) { api_1.context.with(api_1.trace.setSpan(api_1.context.active(), fetchSpan), () => { sdk_trace_web_1.addSpanNetworkEvents(fetchSpan, entries); this._endSpan(fetchSpan, sdk_trace_web_1.PerformanceTimingNames.RESPONSE_END, entries); }); } }); rootSpan.setAttribute(semantic_conventions_1.SemanticAttributes.HTTP_URL, location.href); rootSpan.setAttribute(semantic_conventions_1.SemanticAttributes.HTTP_USER_AGENT, navigator.userAgent); this._addResourcesSpans(rootSpan); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.FETCH_START, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.UNLOAD_EVENT_START, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.UNLOAD_EVENT_END, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.DOM_INTERACTIVE, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.DOM_CONTENT_LOADED_EVENT_START, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.DOM_CONTENT_LOADED_EVENT_END, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.DOM_COMPLETE, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.LOAD_EVENT_START, entries); sdk_trace_web_1.addSpanNetworkEvent(rootSpan, sdk_trace_web_1.PerformanceTimingNames.LOAD_EVENT_END, entries); utils_1.addSpanPerformancePaintEvents(rootSpan); this._endSpan(rootSpan, sdk_trace_web_1.PerformanceTimingNames.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 (sdk_trace_web_1.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_1.AttributeNames.RESOURCE_FETCH, sdk_trace_web_1.PerformanceTimingNames.FETCH_START, resource, parentSpan); if (span) { span.setAttribute(semantic_conventions_1.SemanticAttributes.HTTP_URL, resource.name); sdk_trace_web_1.addSpanNetworkEvents(span, resource); this._endSpan(span, sdk_trace_web_1.PerformanceTimingNames.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 (sdk_trace_web_1.hasKey(entries, performanceName) && typeof entries[performanceName] === 'number') { const span = this.tracer.startSpan(spanName, { startTime: entries[performanceName], }, parentSpan ? api_1.trace.setSpan(api_1.context.active(), parentSpan) : undefined); span.setAttribute(AttributeNames_1.AttributeNames.COMPONENT, this.component); span.setAttribute('client.os', this._detectOs()); span.setAttribute('client.browser', this._detectBrowser()); span.setAttribute(semantic_conventions_1.SemanticAttributes.HTTP_URL, location.href); 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); } } /** * 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); } } exports.DocumentLoadInstrumentation = DocumentLoadInstrumentation; //# sourceMappingURL=instrumentation.js.map