UNPKG

@embrace-io/web-sdk

Version:
233 lines (232 loc) 13.6 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_constants_attributes = require("../../../constants/attributes.cjs"); const require_instrumentations_EmbraceInstrumentationBase_EmbraceInstrumentationBase = require("../../EmbraceInstrumentationBase/EmbraceInstrumentationBase.cjs"); require("./enums/AttributeNames.cjs"); const require_instrumentations_document_load_DocumentLoadInstrumentation_utils = require("./utils.cjs"); let _opentelemetry_api = require("@opentelemetry/api"); let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions"); let _opentelemetry_instrumentation = require("@opentelemetry/instrumentation"); let _opentelemetry_sdk_trace_web = require("@opentelemetry/sdk-trace-web"); let _opentelemetry_semantic_conventions_incubating = require("@opentelemetry/semantic-conventions/incubating"); let _opentelemetry_core = require("@opentelemetry/core"); //#region src/instrumentations/document-load/DocumentLoadInstrumentation/DocumentLoadInstrumentation.ts const ATTR_HTTP_RESPONSE_DELIVERY_TYPE = "http.response.delivery_type"; const ATTR_HTTP_RESPONSE_DECODED_BODY_SIZE = "http.response.decoded_body_size"; const ATTR_HTTP_REQUEST_INITIATOR_TYPE = "http.request.initiator_type"; const ATTR_HTTP_REQUEST_RENDER_BLOCKING_STATUS = "http.request.render_blocking_status"; const ATTR_HTTP_RESPONSE_CORS_OPAQUE = "http.response.cors_opaque"; const ATTR_HTTP_RESPONSE_CACHE_REVALIDATED = "http.response.cache_revalidated"; const ATTR_HTTP_REQUEST_INCOMPLETE = "http.request.incomplete"; const ATTR_HTTP_REQUEST_PREVENTED = "http.request.prevented"; var DocumentLoadInstrumentation = class extends require_instrumentations_EmbraceInstrumentationBase_EmbraceInstrumentationBase.EmbraceInstrumentationBase { _onDocumentLoaded; _performanceCollected = false; constructor({ diag, perf, enabled, applyCustomAttributesOnSpan, ignorePerformancePaintEvents = false, ignoreNetworkEvents = false } = {}) { super({ instrumentationName: "DocumentLoadInstrumentation", instrumentationVersion: "1.0.0", diag, perf, config: { enabled, applyCustomAttributesOnSpan, ignorePerformancePaintEvents, ignoreNetworkEvents } }); this._onDocumentLoaded = () => { window.setTimeout(() => { this._collectPerformance(); }, 0); }; if (this._config.enabled) this.enable(); } init() { this._diag.debug("Initializing document load instrumentation"); } /** * Adds spans for all resources * @param rootSpan */ _addResourcesSpans(rootSpan) { performance.getEntriesByType("resource").forEach((resource) => { this._initResourceSpan(resource, rootSpan); }); } /** * Collects information about performance and creates appropriate spans */ _collectPerformance() { if (this._performanceCollected) return; this._performanceCollected = true; const metaElement = Array.from(document.getElementsByTagName("meta")).find((e) => e.getAttribute("name") === _opentelemetry_core.TRACE_PARENT_HEADER); const entries = require_instrumentations_document_load_DocumentLoadInstrumentation_utils.getPerformanceNavigationEntries(); const traceparent = metaElement?.content || ""; _opentelemetry_api.context.with(_opentelemetry_api.propagation.extract(_opentelemetry_api.ROOT_CONTEXT, { traceparent }), () => { const rootSpan = this._startSpan("documentLoad", _opentelemetry_sdk_trace_web.PerformanceTimingNames.FETCH_START, entries); if (!rootSpan) return; _opentelemetry_api.context.with(_opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(), rootSpan), () => { const fetchSpan = this._startSpan("documentFetch", _opentelemetry_sdk_trace_web.PerformanceTimingNames.FETCH_START, entries); if (fetchSpan) { fetchSpan.setAttribute(_opentelemetry_semantic_conventions_incubating.ATTR_URL_FULL, location.href); _opentelemetry_api.context.with(_opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(), fetchSpan), () => { (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvents)(fetchSpan, entries, this.getConfig().ignoreNetworkEvents); this._addCustomAttributesOnSpan(fetchSpan, this.getConfig().applyCustomAttributesOnSpan?.documentFetch); this._endSpan(fetchSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.RESPONSE_END, entries); }); } }); rootSpan.setAttribute(require_constants_attributes.KEY_EMB_TYPE, "ux.document_load"); rootSpan.setAttribute(_opentelemetry_semantic_conventions_incubating.ATTR_URL_FULL, location.href); rootSpan.setAttribute(_opentelemetry_semantic_conventions_incubating.ATTR_USER_AGENT_ORIGINAL, navigator.userAgent); this._addResourcesSpans(rootSpan); if (!this.getConfig().ignoreNetworkEvents) { (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.FETCH_START, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.UNLOAD_EVENT_START, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.UNLOAD_EVENT_END, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.DOM_INTERACTIVE, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.DOM_CONTENT_LOADED_EVENT_START, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.DOM_CONTENT_LOADED_EVENT_END, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.DOM_COMPLETE, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.LOAD_EVENT_START, entries); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvent)(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.LOAD_EVENT_END, entries); } if (!this.getConfig().ignorePerformancePaintEvents) require_instrumentations_document_load_DocumentLoadInstrumentation_utils.addSpanPerformancePaintEvents(rootSpan, this.perf); this._addCustomAttributesOnSpan(rootSpan, this.getConfig().applyCustomAttributesOnSpan?.documentLoad); this._endSpan(rootSpan, _opentelemetry_sdk_trace_web.PerformanceTimingNames.LOAD_EVENT_END, entries); }); } /** * Helper function for ending a span * @param span * @param performanceName name of performance entry for end time * @param entries */ _endSpan(span, performanceName, entries) { if (span) if ((0, _opentelemetry_sdk_trace_web.hasKey)(entries, performanceName) && typeof entries[performanceName] === "number") span.end(this.perf.epochMillisFromOrigin(entries[performanceName])); else span.end(); } /** * Creates and ends a span with network information about a resource added as timed events * @param resource * @param parentSpan */ _initResourceSpan(resource, parentSpan) { const span = this._startSpan("resourceFetch", _opentelemetry_sdk_trace_web.PerformanceTimingNames.FETCH_START, resource, parentSpan); if (!span) return; span.setAttribute(require_constants_attributes.KEY_EMB_TYPE, "ux.resource_fetch"); span.setAttribute(_opentelemetry_semantic_conventions_incubating.ATTR_URL_FULL, resource.name); (0, _opentelemetry_sdk_trace_web.addSpanNetworkEvents)(span, resource, this.getConfig().ignoreNetworkEvents); if (resource.deliveryType) span.setAttribute(ATTR_HTTP_RESPONSE_DELIVERY_TYPE, resource.deliveryType); if (resource.initiatorType) span.setAttribute(ATTR_HTTP_REQUEST_INITIATOR_TYPE, resource.initiatorType); if (resource.renderBlockingStatus) span.setAttribute(ATTR_HTTP_REQUEST_RENDER_BLOCKING_STATUS, resource.renderBlockingStatus); if (resource.responseStatus) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_RESPONSE_STATUS_CODE, resource.responseStatus); if (typeof resource.encodedBodySize === "number" && resource.encodedBodySize >= 0) span.setAttribute(_opentelemetry_semantic_conventions_incubating.ATTR_HTTP_RESPONSE_BODY_SIZE, resource.encodedBodySize); if (typeof resource.transferSize === "number" && resource.transferSize >= 0) span.setAttribute(_opentelemetry_semantic_conventions_incubating.ATTR_HTTP_RESPONSE_SIZE, resource.transferSize); if (typeof resource.decodedBodySize === "number" && resource.decodedBodySize >= 0) span.setAttribute(ATTR_HTTP_RESPONSE_DECODED_BODY_SIZE, resource.decodedBodySize); this._addResourceDiagnosticAttributes(span, resource); this._addCustomAttributesOnResourceSpan(span, resource, this.getConfig().applyCustomAttributesOnSpan?.resourceFetch); this._endSpan(span, _opentelemetry_sdk_trace_web.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 ((0, _opentelemetry_sdk_trace_web.hasKey)(entries, performanceName) && typeof entries[performanceName] === "number") return this.tracer.startSpan(spanName, { startTime: this.perf.epochMillisFromOrigin(entries[performanceName]) }, parentSpan ? _opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(), parentSpan) : void 0); } /** * Executes callback {_onDocumentLoaded} when the page is loaded */ _waitForPageLoad() { if (window.document.readyState === "complete") this._onDocumentLoaded(); else window.addEventListener("load", this._onDocumentLoaded); } /** * Adds custom attributes to span if configured * Used for both documentFetch and documentLoad spans */ _addCustomAttributesOnSpan(span, applyCustomAttributesOnSpan) { if (applyCustomAttributesOnSpan) (0, _opentelemetry_instrumentation.safeExecuteInTheMiddle)(() => { applyCustomAttributesOnSpan(span); }, (error) => { if (!error) return; this._diag.error("addCustomAttributesOnSpan", error); }, true); } /** * Adds custom attributes to resource span if configured */ _addCustomAttributesOnResourceSpan(span, resource, applyCustomAttributesOnSpan) { if (applyCustomAttributesOnSpan) (0, _opentelemetry_instrumentation.safeExecuteInTheMiddle)(() => { applyCustomAttributesOnSpan(span, resource); }, (error) => { if (!error) return; this._diag.error("addCustomAttributesOnResourceSpan", error); }, true); } _hasNoSizeData(resource) { const transferSize = typeof resource.transferSize === "number" ? resource.transferSize : 0; const decodedBodySize = typeof resource.decodedBodySize === "number" ? resource.decodedBodySize : 0; const encodedBodySize = typeof resource.encodedBodySize === "number" ? resource.encodedBodySize : 0; return transferSize === 0 && decodedBodySize === 0 && encodedBodySize === 0; } _hasTimingData(resource) { const fetchStart = typeof resource.fetchStart === "number" ? resource.fetchStart : 0; const responseEnd = typeof resource.responseEnd === "number" ? resource.responseEnd : 0; return fetchStart > 0 && responseEnd > 0; } _isCorsRestricted(resource) { return this._hasNoSizeData(resource) && this._hasTimingData(resource); } _isFetchIncomplete(resource) { const fetchStart = typeof resource.fetchStart === "number" ? resource.fetchStart : 0; const responseEnd = typeof resource.responseEnd === "number" ? resource.responseEnd : 0; return this._hasNoSizeData(resource) && fetchStart > 0 && responseEnd === 0; } _isFetchPrevented(resource) { const fetchStart = typeof resource.fetchStart === "number" ? resource.fetchStart : 0; return this._hasNoSizeData(resource) && fetchStart === 0; } /** * Detect cache validation (304 Not Modified responses) * * 304 responses show transferSize of ~300 bytes (headers only, no body). * Use deliveryType to distinguish from cache hits: 'cache' = no network, otherwise = 304. * * Spec: https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize */ _isCacheValidated(resource) { const transferSize = typeof resource.transferSize === "number" ? resource.transferSize : 0; const deliveryType = typeof resource.deliveryType === "string" ? resource.deliveryType : ""; return transferSize === 300 && deliveryType !== "cache"; } /** * Add diagnostic attributes to identify resource loading issues * * Diagnostic attributes help identify why resources may have incomplete timing data: * - CORS restrictions (opaque responses without Timing-Allow-Origin header) * - Cache revalidation (304 Not Modified responses) * - Request incomplete (started but didn't complete - network error, aborted) * - Request prevented (never started - blocked by CSP, browser, extension) */ _addResourceDiagnosticAttributes(span, resource) { if (this._isCorsRestricted(resource)) span.setAttribute(ATTR_HTTP_RESPONSE_CORS_OPAQUE, true); else if (this._isFetchIncomplete(resource)) span.setAttribute(ATTR_HTTP_REQUEST_INCOMPLETE, true); else if (this._isFetchPrevented(resource)) span.setAttribute(ATTR_HTTP_REQUEST_PREVENTED, true); if (this._isCacheValidated(resource)) span.setAttribute(ATTR_HTTP_RESPONSE_CACHE_REVALIDATED, true); } onEnable() { window.removeEventListener("load", this._onDocumentLoaded); this._waitForPageLoad(); } onDisable() { window.removeEventListener("load", this._onDocumentLoaded); } }; //#endregion exports.DocumentLoadInstrumentation = DocumentLoadInstrumentation; //# sourceMappingURL=DocumentLoadInstrumentation.cjs.map