iudex-web
Version:
Iudex web client
1,435 lines (1,426 loc) • 80.3 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Dispatch: () => Dispatch,
EVENT_NAMES: () => EVENT_NAMES,
FETCH_IGNORE_URLS: () => FETCH_IGNORE_URLS,
XMLHttpRequest: () => XMLHttpRequest2,
buildHeaders: () => buildHeaders,
buildResource: () => buildResource,
config: () => config,
convertSeverityTextToNumber: () => convertSeverityTextToNumber,
convertSeverityValuesToLevel: () => convertSeverityValuesToLevel,
defaultInstrumentConfig: () => defaultInstrumentConfig,
emitOtelLog: () => emitOtelLog,
flattenObject: () => flattenObject,
getCallerInfo: () => getCallerInfo,
instrument: () => instrument,
iudexCloudflare: () => cloudflare_worker_exports,
iudexConsole: () => console_instrumentation_exports,
nativeConsole: () => nativeConsole,
registerOTelOptions: () => registerOTelOptions,
setAttribute: () => setAttribute,
setError: () => setError,
setName: () => setName,
setStatus: () => setStatus,
trackAttribute: () => trackAttribute,
trackGlobalAttribute: () => trackGlobalAttribute,
useTracing: () => useTracing,
withTracing: () => withTracing
});
module.exports = __toCommonJS(src_exports);
var import_api3 = require("@opentelemetry/api");
// src/utils.ts
var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
var import_api_logs = require("@opentelemetry/api-logs");
var import_lodash = __toESM(require("lodash"));
var config = {
isInstrumented: false,
nativeConsole: { ...console },
nativeFetch: fetch.bind(globalThis)
};
var nativeConsole = config.nativeConsole;
function convertSeverityTextToNumber(severityText) {
if (severityText == void 0) {
return;
}
switch (severityText) {
case "TRACE":
return 1;
case "DEBUG":
return 5;
case "INFO":
return 9;
case "WARN":
return 13;
case "ERROR":
return 17;
case "FATAL":
return 21;
default:
return;
}
}
__name(convertSeverityTextToNumber, "convertSeverityTextToNumber");
function convertSeverityValuesToLevel(severityNumber, severityText) {
severityNumber ||= convertSeverityTextToNumber(severityText) || 0;
if (severityNumber >= 1 && severityNumber <= 4) {
return "TRACE";
} else if (severityNumber >= 5 && severityNumber <= 8) {
return "DEBUG";
} else if (severityNumber >= 9 && severityNumber <= 12) {
return "INFO";
} else if (severityNumber >= 13 && severityNumber <= 16) {
return "WARN";
} else if (severityNumber >= 17 && severityNumber <= 20) {
return "ERROR";
} else if (severityNumber >= 21 && severityNumber <= 24) {
return "FATAL";
} else {
return "INFO";
}
}
__name(convertSeverityValuesToLevel, "convertSeverityValuesToLevel");
function getCallerInfo(frameDepth) {
const stack = new Error().stack;
if (!stack) return {};
const stackLines = stack.split("\n");
const callerStackLine = stackLines[frameDepth + 1];
const callerAndPathRegex = /at (?<caller>.+?) \((?<filePath>[^:()]+(?::[^:()]+)*):(?<lineNum>\d+):\d+\)/;
const capMatch = callerStackLine.match(callerAndPathRegex);
if (capMatch) {
const { filePath, lineNum, caller } = capMatch.groups;
return { filePath, lineNum: Number(lineNum), caller };
}
const pathOnlyRegex = /at (?<filePath>[^:()]+(?::[^:()]+)*):(?<lineNum>\d+):\d+/;
const poMatch = callerStackLine.match(pathOnlyRegex);
if (poMatch) {
const { filePath, lineNum } = poMatch.groups;
return { filePath, lineNum: Number(lineNum) };
}
return {};
}
__name(getCallerInfo, "getCallerInfo");
function flattenObject(obj, parentKey = "", result = {}, seen = /* @__PURE__ */ new Set(), maxObjectKeys = 30, maxDepth = 4) {
if (!obj) return;
if (maxDepth < 0) {
result[parentKey] = "<max depth exceeded>";
return;
}
Object.entries(obj).forEach(([key, value], i) => {
if (i === maxObjectKeys) {
result[`${parentKey}.${key}._max_keys_exceeded`] = "<max keys exceeded>";
return;
}
if (i > maxObjectKeys) {
return;
}
const newKey = parentKey ? `${parentKey}.${key}` : key;
if (seen.has(value)) {
result[newKey] = "<circular>";
return;
}
if (typeof value === "object" && value !== null && !Array.isArray(obj[key])) {
seen.add(value);
flattenObject(value, newKey, result, seen, maxObjectKeys, maxDepth - 1);
} else {
result[newKey] = value;
}
});
return result;
}
__name(flattenObject, "flattenObject");
function emitOtelLog({
level,
body,
severityNumber,
attributes,
stackDepth
}) {
if (!config.isInstrumented) return;
const attrs = { ...attributes };
if (stackDepth != null) {
const { filePath, lineNum, caller } = getCallerInfo(stackDepth + 1);
Object.assign(attrs, {
[import_semantic_conventions.SEMATTRS_CODE_FILEPATH]: filePath,
[import_semantic_conventions.SEMATTRS_CODE_LINENO]: lineNum,
[import_semantic_conventions.SEMATTRS_CODE_FUNCTION]: caller
});
}
if (config.sessionProvider) {
attrs.session = config.sessionProvider.getActiveSession();
}
const otelLogger = config.loggerProvider?.getLogger("default") || import_api_logs.logs.getLogger("default");
otelLogger.emit({
severityNumber: severityNumber || convertSeverityTextToNumber(level.toUpperCase()),
severityText: level.toUpperCase(),
body,
attributes: import_lodash.default.omitBy(attrs, import_lodash.default.isNil)
});
}
__name(emitOtelLog, "emitOtelLog");
var Dispatch = class extends EventTarget {
static {
__name(this, "Dispatch");
}
dispatch(eventName) {
const ev = new Event(eventName);
if ("on" + eventName in this) {
this["on" + eventName](ev);
}
this.dispatchEvent(ev);
}
};
var XMLHttpRequest2 = class extends Dispatch {
static {
__name(this, "XMLHttpRequest");
}
// readyState enumeration
UNSENT = 0;
OPENED = 1;
HEADERS_RECEIVED = 2;
LOADING = 3;
DONE = 4;
upload = new Dispatch();
// Request
url;
method;
headers;
readyState;
_controller;
withCredentials = false;
// Response
errored = false;
responseHeaders = void 0;
responseURL;
responseValue;
responseType;
status;
statusText;
constructor() {
super();
}
open(method, url) {
this.url = url;
this.method = method;
this.headers = new Headers();
this.readyState = this.UNSENT;
this.responseType = "";
this._controller = new AbortController();
}
setRequestHeader(key, value) {
this.headers?.set(key, value);
}
abort() {
this.upload.dispatch("abort");
this._controller?.abort();
}
send(payload) {
this.readyState = this.OPENED;
this.status = 0;
this.dispatch("readystatechange");
this.upload.dispatch("loadstart");
if (!this.url) {
throw new DOMException(
`Failed to execute 'send' on 'XMLHttpRequest': The object's state must be OPENED`
);
}
const fetchPromise = config.nativeFetch(this.url, {
method: this.method,
credentials: "credentials" in Request.prototype ? this.withCredentials ? "omit" : "include" : void 0,
headers: this.headers,
signal: this._controller?.signal,
body: payload
}).then((response) => {
this.responseHeaders = response.headers;
this.readyState = this.HEADERS_RECEIVED;
this.responseURL = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.responseType = "text";
const contentType = response.headers.get("content-type");
if (!contentType) {
} else if (contentType.includes("application/json")) {
this.responseType = "json";
} else if (contentType.includes("text/html")) {
this.responseType = "document";
} else if (contentType.includes("application/octet-stream")) {
this.responseType = "blob";
}
switch (this.responseType) {
case void 0:
return response.text();
case "blob":
return response.blob();
case "document":
return response.text();
case "json":
return response.json();
}
return response.text();
}).then((value) => {
this.responseValue = value;
this.readyState = this.DONE;
this.dispatch("readystatechange");
this.upload.dispatch("progress");
this.dispatch("progress");
this.upload.dispatch("load");
this.dispatch("load");
}).catch((err) => {
this.errored = true;
this.dispatch("error");
this.upload.dispatch("error");
this.readyState = this.DONE;
this.dispatch("readystatechange");
}).finally(() => {
this.upload.dispatch("loadend");
this.dispatch("loadend");
});
config.workerEvent?.waitUntil(fetchPromise);
}
get responseText() {
if (this.responseValue) {
if (this.responseType === "arraybuffer") {
return new TextDecoder().decode(this.responseValue);
}
if (this.responseType === "json") {
return JSON.stringify(this.responseValue);
}
if (this.responseValue === "blob") {
return new TextDecoder().decode(this.responseValue);
}
return this.responseValue;
}
return "";
}
getAllResponseHeaders() {
if (this.errored || this.readyState && this.readyState < this.HEADERS_RECEIVED || !this.responseHeaders) return "";
return Object.entries(this.responseHeaders).map(([header, value]) => `${header}: ${value}`).join("\r\n");
}
getResponseHeader(headerName) {
return this.responseHeaders?.get(headerName.toLowerCase()) || null;
}
};
// src/trace.ts
var import_api = require("@opentelemetry/api");
function withTracing(fn, ctx = {}) {
return function(...args) {
const { name, trackArgs = true, maxArgKeys, maxArgDepth, attributes, setSpan, setArgs } = ctx;
const tracer = import_api.trace.getTracer("default");
if (!config.isInstrumented) {
return fn(...args);
}
if (ctx.beforeRun) {
ctx.beforeRun(...args);
}
return tracer.startActiveSpan(name || fn.name || "<anonymous>", (span) => {
try {
if (attributes) {
span.setAttributes(attributes);
}
if (trackArgs && !setArgs) {
if (args.length === 1) {
if (args[0] != null && typeof args[0] === "object") {
const flatObj = flattenObject(args[0], "", {}, /* @__PURE__ */ new Set(), maxArgKeys, maxArgDepth);
flatObj && Object.entries(flatObj).forEach(([key, value]) => {
span.setAttribute(key, value);
});
} else if (args[0] == null) {
span.setAttribute("args.0", `<${args[0]}>`);
} else {
span.setAttribute("args.0", args[0]);
}
} else if (args.length > 1) {
const flatObjs = flattenObject(args, "", {}, /* @__PURE__ */ new Set(), maxArgKeys, maxArgDepth);
flatObjs && Object.entries(flatObjs).forEach(([key, value]) => {
span.setAttribute(`args.${key}`, value);
});
}
}
if (setArgs) {
setArgs(span, args);
}
const ret = fn(...args);
if (ret.then) {
if (setSpan) {
setSpan(span, ret);
return ret;
}
return ret.then((res) => {
return res;
}).catch((err) => {
span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: String(err) });
span.recordException(err);
span.setAttribute("exception.message", err.message);
span.setAttribute("exception.stacktrace", err.stack);
emitOtelLog({ level: "ERROR", body: err });
throw err;
}).finally(() => {
span.end();
});
}
if (setSpan) {
setSpan(span, ret);
return ret;
}
span.end();
return ret;
} catch (err) {
span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: err.message });
span.recordException(err);
span.setAttribute("exception.message", err.message);
err.stack && span.setAttribute("exception.stacktrace", err.stack || "");
emitOtelLog({ level: "ERROR", body: err });
span.end();
throw err;
}
});
};
}
__name(withTracing, "withTracing");
function useTracing(fn, ctx = {}) {
return withTracing(fn, ctx)();
}
__name(useTracing, "useTracing");
// src/instrument.ts
var import_exporter_logs_otlp_proto = require("@opentelemetry/exporter-logs-otlp-proto");
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
var import_instrumentation3 = require("@opentelemetry/instrumentation");
var import_instrumentation_document_load = require("@opentelemetry/instrumentation-document-load");
// src/instrumentations/fetch-instrumentation.ts
var api = __toESM(require("@opentelemetry/api"));
var import_instrumentation = require("@opentelemetry/instrumentation");
var core = __toESM(require("@opentelemetry/core"));
var web = __toESM(require("@opentelemetry/sdk-trace-web"));
var import_AttributeNames = require("@opentelemetry/instrumentation-fetch/build/src/enums/AttributeNames");
var import_semantic_conventions2 = require("@opentelemetry/semantic-conventions");
var import_version = require("@opentelemetry/instrumentation-fetch/build/src/version");
var import_core = require("@opentelemetry/core");
var OBSERVER_WAIT_TIME_MS = 300;
var isNode = typeof process === "object" && process.release?.name === "node";
var FetchInstrumentation = class extends import_instrumentation.InstrumentationBase {
static {
__name(this, "FetchInstrumentation");
}
component = "fetch";
version = import_version.VERSION;
moduleName = this.component;
_usedResources = /* @__PURE__ */ new WeakSet();
_tasksCount = 0;
constructor(config2 = {}) {
super("@opentelemetry/instrumentation-fetch", import_version.VERSION, config2);
}
init() {
}
/**
* Add cors pre flight child span
* @param span
* @param corsPreFlightRequest
*/
_addChildSpan(span, corsPreFlightRequest) {
const childSpan = this.tracer.startSpan(
"CORS Preflight",
{
startTime: corsPreFlightRequest[web.PerformanceTimingNames.FETCH_START]
},
api.trace.setSpan(api.context.active(), span)
);
if (!this.getConfig().ignoreNetworkEvents) {
web.addSpanNetworkEvents(childSpan, corsPreFlightRequest);
}
childSpan.end(
corsPreFlightRequest[web.PerformanceTimingNames.RESPONSE_END]
);
}
/**
* Adds more attributes to span just before ending it
* @param span
* @param response
*/
_addFinalSpanAttributes(span, response) {
const parsedUrl = web.parseUrl(response.url);
span.setAttribute(import_semantic_conventions2.SEMATTRS_HTTP_STATUS_CODE, response.status);
if (response.statusText != null) {
span.setAttribute(import_AttributeNames.AttributeNames.HTTP_STATUS_TEXT, response.statusText);
}
span.setAttribute(import_semantic_conventions2.SEMATTRS_HTTP_HOST, parsedUrl.host);
span.setAttribute(
import_semantic_conventions2.SEMATTRS_HTTP_SCHEME,
parsedUrl.protocol.replace(":", "")
);
if (typeof navigator !== "undefined") {
span.setAttribute(import_semantic_conventions2.SEMATTRS_HTTP_USER_AGENT, navigator.userAgent);
}
}
/**
* Add headers
* @param options
* @param spanUrl
*/
_addHeaders(options, spanUrl) {
if (!web.shouldPropagateTraceHeaders(
spanUrl,
this.getConfig().propagateTraceHeaderCorsUrls
)) {
const headers = {};
api.propagation.inject(api.context.active(), headers);
if (Object.keys(headers).length > 0) {
this._diag.debug("headers inject skipped due to CORS policy");
}
return;
}
const sessionId = config.sessionProvider?.getActiveSession()?.id;
let baggage = api.propagation.getActiveBaggage() || api.propagation.createBaggage({});
if (sessionId) {
baggage = baggage.setEntry("session.id", { value: sessionId });
}
const ctx = api.propagation.setBaggage(api.context.active(), baggage);
api.context.with(ctx, () => {
if (options instanceof Request) {
api.propagation.inject(api.context.active(), options.headers, {
set: /* @__PURE__ */ __name((h, k, v) => h.set(k, typeof v === "string" ? v : String(v)), "set")
});
} else if (options.headers instanceof Headers) {
api.propagation.inject(api.context.active(), options.headers, {
set: /* @__PURE__ */ __name((h, k, v) => h.set(k, typeof v === "string" ? v : String(v)), "set")
});
} else if (options.headers instanceof Map) {
api.propagation.inject(api.context.active(), options.headers, {
set: /* @__PURE__ */ __name((h, k, v) => h.set(k, typeof v === "string" ? v : String(v)), "set")
});
} else {
const headers = {};
api.propagation.inject(api.context.active(), headers);
options.headers = Object.assign({}, headers, options.headers || {});
}
});
}
/**
* Clears the resource timings and all resources assigned with spans
* when {@link FetchPluginConfig.clearTimingResources} is
* set to true (default false)
* @private
*/
_clearResources() {
if (this._tasksCount === 0 && this.getConfig().clearTimingResources) {
performance.clearResourceTimings();
this._usedResources = /* @__PURE__ */ new WeakSet();
}
}
/**
* Creates a new span
* @param url
* @param options
*/
_createSpan(url, options = {}) {
if (core.isUrlIgnored(url, this.getConfig().ignoreUrls)) {
this._diag.debug("ignoring span as url matches ignored url");
return;
}
const method = (options.method || "GET").toUpperCase();
const spanName = `HTTP ${method}`;
return this.tracer.startSpan(spanName, {
kind: api.SpanKind.CLIENT,
attributes: {
[import_AttributeNames.AttributeNames.COMPONENT]: this.moduleName,
[import_semantic_conventions2.SEMATTRS_HTTP_METHOD]: method,
[import_semantic_conventions2.SEMATTRS_HTTP_URL]: url
}
});
}
/**
* Finds appropriate resource and add network events to the span
* @param span
* @param resourcesObserver
* @param endTime
*/
_findResourceAndAddNetworkEvents(span, resourcesObserver, endTime) {
let resources = resourcesObserver.entries;
if (!resources.length) {
if (!performance.getEntriesByType) {
return;
}
resources = performance.getEntriesByType(
"resource"
);
}
const resource = web.getResource(
resourcesObserver.spanUrl,
resourcesObserver.startTime,
endTime,
resources,
this._usedResources,
"fetch"
);
if (resource.mainRequest) {
const mainRequest = resource.mainRequest;
this._markResourceAsUsed(mainRequest);
const corsPreFlightRequest = resource.corsPreFlightRequest;
if (corsPreFlightRequest) {
this._addChildSpan(span, corsPreFlightRequest);
this._markResourceAsUsed(corsPreFlightRequest);
}
if (!this.getConfig().ignoreNetworkEvents) {
web.addSpanNetworkEvents(span, mainRequest);
}
}
}
/**
* Marks certain [resource]{@link PerformanceResourceTiming} when information
* from this is used to add events to span.
* This is done to avoid reusing the same resource again for next span
* @param resource
*/
_markResourceAsUsed(resource) {
this._usedResources.add(resource);
}
/**
* Finish span, add attributes, network events etc.
* @param span
* @param spanData
* @param response
*/
_endSpan(span, spanData, response) {
const endTime = core.millisToHrTime(Date.now());
const performanceEndTime = core.hrTime();
this._addFinalSpanAttributes(span, response);
setTimeout(() => {
spanData.observer?.disconnect();
this._findResourceAndAddNetworkEvents(span, spanData, performanceEndTime);
this._tasksCount--;
this._clearResources();
span.end(endTime);
}, OBSERVER_WAIT_TIME_MS);
}
/**
* Patches the constructor of fetch
*/
_patchConstructor() {
return (original) => {
const plugin = this;
return /* @__PURE__ */ __name(function patchConstructor(...args) {
const self = this;
const url = web.parseUrl(
args[0] instanceof Request ? args[0].url : String(args[0])
).href;
const options = args[0] instanceof Request ? args[0] : args[1] || {};
const createdSpan = plugin._createSpan(url, options);
if (!createdSpan) {
return original.apply(this, args);
}
const spanData = plugin._prepareSpanData(url);
function endSpanOnError(span, error) {
plugin._applyAttributesAfterFetch(span, options, error);
plugin._endSpan(span, spanData, {
status: error.status || 0,
statusText: error.message,
url
});
}
__name(endSpanOnError, "endSpanOnError");
function endSpanOnSuccess(span, response) {
plugin._applyAttributesAfterFetch(span, options, response);
if (response.status >= 200 && response.status < 400) {
plugin._endSpan(span, spanData, response);
} else {
plugin._endSpan(span, spanData, {
status: response.status,
statusText: response.statusText,
url
});
}
}
__name(endSpanOnSuccess, "endSpanOnSuccess");
function onSuccess(span, resolve, response) {
try {
const resClone = response.clone();
const resClone4Hook = response.clone();
const body = resClone.body;
if (body) {
const reader = body.getReader();
const read = /* @__PURE__ */ __name(() => {
reader.read().then(
({ done }) => {
if (done) {
endSpanOnSuccess(span, resClone4Hook);
} else {
read();
}
},
(error) => {
endSpanOnError(span, error);
}
);
}, "read");
read();
} else {
endSpanOnSuccess(span, response);
}
} finally {
resolve(response);
}
}
__name(onSuccess, "onSuccess");
function onError(span, reject, error) {
try {
endSpanOnError(span, error);
} finally {
reject(error);
}
}
__name(onError, "onError");
return new Promise((resolve, reject) => {
return api.context.with(
api.trace.setSpan(api.context.active(), createdSpan),
() => {
plugin._addHeaders(options, url);
plugin._tasksCount++;
return original.apply(
self,
options instanceof Request ? [options] : [url, options]
).then(
onSuccess.bind(self, createdSpan, resolve),
onError.bind(self, createdSpan, reject)
);
}
);
});
}, "patchConstructor");
};
}
_applyAttributesAfterFetch(span, request, result) {
const applyCustomAttributesOnSpan = this.getConfig().applyCustomAttributesOnSpan;
if (applyCustomAttributesOnSpan) {
(0, import_instrumentation.safeExecuteInTheMiddle)(
() => applyCustomAttributesOnSpan(span, request, result),
(error) => {
if (!error) {
return;
}
this._diag.error("applyCustomAttributesOnSpan", error);
},
true
);
}
}
/**
* Prepares a span data - needed later for matching appropriate network
* resources
* @param spanUrl
*/
_prepareSpanData(spanUrl) {
const startTime = core.hrTime();
const entries = [];
if (typeof PerformanceObserver !== "function") {
return { entries, startTime, spanUrl };
}
const observer = new PerformanceObserver((list) => {
const perfObsEntries = list.getEntries();
perfObsEntries.forEach((entry) => {
if (entry.initiatorType === "fetch" && entry.name === spanUrl) {
entries.push(entry);
}
});
});
observer.observe({
entryTypes: ["resource"]
});
return { entries, observer, startTime, spanUrl };
}
/**
* implements enable function
*/
enable() {
if (isNode) {
this._diag.warn(
"this instrumentation is intended for web usage only, it does not instrument Node.js's fetch()"
);
return;
}
if ((0, import_instrumentation.isWrapped)(fetch)) {
this._unwrap(import_core._globalThis, "fetch");
this._diag.debug("removing previous patch for constructor");
}
this._wrap(import_core._globalThis, "fetch", this._patchConstructor());
}
/**
* implements unpatch function
*/
disable() {
if (isNode) {
return;
}
this._unwrap(import_core._globalThis, "fetch");
this._usedResources = /* @__PURE__ */ new WeakSet();
}
};
// src/instrument.ts
var import_instrumentation_xml_http_request = require("@opentelemetry/instrumentation-xml-http-request");
var import_resources2 = require("@opentelemetry/resources");
var import_sdk_logs2 = require("@opentelemetry/sdk-logs");
var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
var import_sdk_trace_web2 = require("@opentelemetry/sdk-trace-web");
var import_core4 = require("@opentelemetry/core");
var import_semantic_conventions3 = require("@opentelemetry/semantic-conventions");
var import_lodash2 = __toESM(require("lodash"));
// src/instrumentations/console-instrumentation.ts
var console_instrumentation_exports = {};
__export(console_instrumentation_exports, {
instrumentConsole: () => instrumentConsole
});
var R = __toESM(require("ramda"));
var import_util = __toESM(require("util"));
function instrumentConsole() {
if (console._instrumented) return;
const { log, error, warn, info, debug, timeLog, timeEnd } = console;
[
{ name: "log", logger: log, level: "INFO" },
{ name: "error", logger: error, level: "ERROR" },
{ name: "warn", logger: warn, level: "WARN" },
{ name: "info", logger: info, level: "INFO" },
{ name: "debug", logger: debug, level: "DEBUG" },
{ name: "timeLog", logger: timeLog, level: "INFO" },
{ name: "timeEnd", logger: timeEnd, level: "INFO" }
].forEach(({ name, logger, level }) => {
console[name] = function(...content) {
logger(...content);
const contentWoCtx = content.filter((c) => !isObject(c) || !("ctx" in c || "authCtx" in c));
const contentCtx = R.mergeAll(
content.filter((c) => isObject(c) && ("ctx" in c || "authCtx" in c)).map((c) => {
if (c.ctx) return c.ctx;
if (c.authCtx) return c.authCtx;
return {};
})
);
const prettyContentWoCtx = contentWoCtx.map((c) => {
if (typeof c === "object") {
try {
return import_util.default.inspect(c);
} catch {
}
}
return c;
});
emitOtelLog({ level, body: prettyContentWoCtx.join(" "), attributes: contentCtx });
};
});
console._instrumented = true;
}
__name(instrumentConsole, "instrumentConsole");
function isObject(obj) {
return typeof obj === "object" && !Array.isArray(obj) && obj !== null;
}
__name(isObject, "isObject");
// src/instrumentations/user-interaction-instrumentation.ts
var import_instrumentation2 = require("@opentelemetry/instrumentation");
var api2 = __toESM(require("@opentelemetry/api"));
var import_core2 = require("@opentelemetry/core");
var import_sdk_trace_web = require("@opentelemetry/sdk-trace-web");
var PACKAGE_NAME = "iudex-web/instrumentation-user-interaction";
var PACKAGE_VERSION = "0.39.0";
var ZONE_CONTEXT_KEY = "OT_ZONE_CONTEXT";
var EVENT_NAVIGATION_NAME = "Navigation:";
var DEFAULT_EVENT_NAMES = ["click"];
function defaultShouldPreventSpanCreation() {
return false;
}
__name(defaultShouldPreventSpanCreation, "defaultShouldPreventSpanCreation");
var UserInteractionInstrumentation = class extends import_instrumentation2.InstrumentationBase {
static {
__name(this, "UserInteractionInstrumentation");
}
version = PACKAGE_VERSION;
moduleName = "user-interaction";
_spansData = /* @__PURE__ */ new WeakMap();
_zonePatched;
// for addEventListener/removeEventListener state
_wrappedListeners = /* @__PURE__ */ new WeakMap();
// for event bubbling
_eventsSpanMap = /* @__PURE__ */ new WeakMap();
_eventNames;
_shouldPreventSpanCreation;
// How to connect event with when react handles it?
spansByPendingEvent = /* @__PURE__ */ new Map();
constructor(config2 = {}) {
super(PACKAGE_NAME, PACKAGE_VERSION, config2);
this._eventNames = new Set(config2?.eventNames ?? DEFAULT_EVENT_NAMES);
this._shouldPreventSpanCreation = typeof config2?.shouldPreventSpanCreation === "function" ? config2.shouldPreventSpanCreation : defaultShouldPreventSpanCreation;
}
init() {
}
/**
* This will check if last task was timeout and will save the time to
* fix the user interaction when nothing happens
* This timeout comes from xhr plugin which is needed to collect information
* about last xhr main request from observer
* @param task
* @param span
*/
_checkForTimeout(task, span) {
const spanData = this._spansData.get(span);
if (spanData) {
if (task.source === "setTimeout") {
spanData.hrTimeLastTimeout = (0, import_core2.hrTime)();
} else if (task.source !== "Promise.then" && task.source !== "setTimeout") {
spanData.hrTimeLastTimeout = void 0;
}
}
}
/**
* Controls whether or not to create a span, based on the event type.
*/
_allowEventName(eventName) {
return this._eventNames.has(eventName);
}
/**
* Creates a new span
* @param element
* @param eventName
* @param parentSpan
*/
_createSpan(element, eventName, parentSpan) {
if (!(element instanceof HTMLElement)) {
return void 0;
}
if (!element.getAttribute) {
return void 0;
}
if (element.hasAttribute("disabled")) {
return void 0;
}
const isReactEvent = eventName.startsWith("react-");
if (isReactEvent) {
eventName = eventName.split("react-", 2)[1];
}
if (!this._allowEventName(eventName)) {
return void 0;
}
const xpath = (0, import_sdk_trace_web.getElementXPath)(element, true);
try {
const span = this.tracer.startSpan(
eventName,
{
attributes: {
["event_type" /* EVENT_TYPE */]: eventName,
["target_element" /* TARGET_ELEMENT */]: element.tagName,
["target_xpath" /* TARGET_XPATH */]: xpath,
["http.url" /* HTTP_URL */]: window.location.href
}
},
parentSpan ? api2.trace.setSpan(api2.context.active(), parentSpan) : void 0
);
if (this._shouldPreventSpanCreation(eventName, element, span) === true) {
return void 0;
}
this._spansData.set(span, {
taskCount: 0
});
return span;
} catch (e) {
this._diag.error("failed to start create new user interaction span", e);
}
return void 0;
}
/**
* Decrement number of tasks that left in zone,
* This is needed to be able to end span when no more tasks left
* @param span
*/
_decrementTask(span) {
const spanData = this._spansData.get(span);
if (spanData) {
spanData.taskCount--;
if (spanData.taskCount === 0) {
this._tryToEndSpan(span, spanData.hrTimeLastTimeout);
}
}
}
/**
* Return the current span
* @param zone
* @private
*/
_getCurrentSpan(zone) {
const context4 = zone.get(ZONE_CONTEXT_KEY);
if (context4) {
return api2.trace.getSpan(context4);
}
return context4;
}
/**
* Increment number of tasks that are run within the same zone.
* This is needed to be able to end span when no more tasks left
* @param span
*/
_incrementTask(span) {
const spanData = this._spansData.get(span);
if (spanData) {
spanData.taskCount++;
}
}
/**
* Returns true iff we should use the patched callback; false if it's already been patched
*/
addPatchedListener(on, type, listener, wrappedListener) {
let listener2Type = this._wrappedListeners.get(listener);
if (!listener2Type) {
listener2Type = /* @__PURE__ */ new Map();
this._wrappedListeners.set(listener, listener2Type);
}
let element2patched = listener2Type.get(type);
if (!element2patched) {
element2patched = /* @__PURE__ */ new Map();
listener2Type.set(type, element2patched);
}
if (element2patched.has(on)) {
return false;
}
element2patched.set(on, wrappedListener);
return true;
}
/**
* Returns the patched version of the callback (or undefined)
*/
removePatchedListener(on, type, listener) {
const listener2Type = this._wrappedListeners.get(listener);
if (!listener2Type) {
return void 0;
}
const element2patched = listener2Type.get(type);
if (!element2patched) {
return void 0;
}
const patched = element2patched.get(on);
if (patched) {
element2patched.delete(on);
if (element2patched.size === 0) {
listener2Type.delete(type);
if (listener2Type.size === 0) {
this._wrappedListeners.delete(listener);
}
}
}
return patched;
}
// utility method to deal with the Function|EventListener nature of addEventListener
_invokeListener(listener, target, args) {
if (typeof listener === "function") {
return listener.apply(target, args);
} else {
return listener.handleEvent(args[0]);
}
}
/**
* This patches the addEventListener of HTMLElement to be able to
* auto instrument the click events
* This is done when zone is not available
*/
_patchAddEventListener() {
const plugin = this;
return (original) => {
return /* @__PURE__ */ __name(function addEventListenerPatched(type, listener, useCapture) {
if (!listener) {
return original.call(this, type, listener, useCapture);
}
const once = useCapture && typeof useCapture === "object" && useCapture.once;
const patchedListener = /* @__PURE__ */ __name(function(...args) {
let parentSpan;
const event = args[0];
let target = event?.target;
if (event) {
parentSpan = plugin._eventsSpanMap.get(event);
}
if (once) {
plugin.removePatchedListener(this, type, listener);
}
const isReactEvent = event?.currentTarget?.tagName === "REACT";
if (isReactEvent && event?.currentTarget?.ownerDocument?.activeElement) {
target = event.currentTarget.ownerDocument.activeElement;
}
const span = plugin._createSpan(target, type, parentSpan);
if (span) {
if (event) {
plugin._eventsSpanMap.set(event, span);
}
if (isReactEvent && target) {
const fiberKey = Object.keys(target).find((k) => k.startsWith("__reactFiber$"));
if (fiberKey) {
const fiber = target[fiberKey];
if (fiber._debugSource) {
const { fileName, lineNumber, columnNumber } = fiber._debugSource;
columnNumber && span.setAttribute("code.column", columnNumber);
fileName && span.setAttribute("code.filepath", fileName);
lineNumber && span.setAttribute("code.lineno", lineNumber);
}
const fibs = getFiberTree(fiber);
const lineage = getReactRenderLineage(fibs);
const stackTrace = getReactRenderStackTrace(fibs);
span.setAttribute("code.stacktrace", stackTrace.slice(0, 10).join("\n"));
span.setAttribute("code.reacttrace", lineage.join("\n"));
}
}
try {
return api2.context.with(
api2.trace.setSpan(api2.context.active(), span),
() => {
const result = plugin._invokeListener(listener, this, args);
return result;
}
);
} catch (e) {
const error = e;
span.setStatus({ code: api2.SpanStatusCode.ERROR, message: error.message });
span.recordException(error);
emitOtelLog({ level: "ERROR", body: error.stack });
throw e;
} finally {
span.end();
}
} else {
return plugin._invokeListener(listener, this, args);
}
}, "patchedListener");
if (plugin.addPatchedListener(this, type, listener, patchedListener)) {
return original.call(this, type, patchedListener, useCapture);
}
}, "addEventListenerPatched");
};
}
/**
* This patches the removeEventListener of HTMLElement to handle the fact that
* we patched the original callbacks
* This is done when zone is not available
*/
_patchRemoveEventListener() {
const plugin = this;
return (original) => {
return /* @__PURE__ */ __name(function removeEventListenerPatched(type, listener, useCapture) {
const wrappedListener = plugin.removePatchedListener(
this,
type,
listener
);
if (wrappedListener) {
return original.call(this, type, wrappedListener, useCapture);
} else {
return original.call(this, type, listener, useCapture);
}
}, "removeEventListenerPatched");
};
}
/**
* Most browser provide event listener api via EventTarget in prototype chain.
* Exception to this is IE 11 which has it on the prototypes closest to EventTarget:
*
* * - has addEventListener in IE
* ** - has addEventListener in all other browsers
* ! - missing in IE
*
* HTMLElement -> Element -> Node * -> EventTarget **! -> Object
* Document -> Node * -> EventTarget **! -> Object
* Window * -> WindowProperties ! -> EventTarget **! -> Object
*/
_getPatchableEventTargets() {
return window.EventTarget ? [EventTarget.prototype] : [Node.prototype, Window.prototype];
}
/**
* Patches the history api
*/
_patchHistoryApi() {
this._unpatchHistoryApi();
this._wrap(history, "replaceState", this._patchHistoryMethod());
this._wrap(history, "pushState", this._patchHistoryMethod());
this._wrap(history, "back", this._patchHistoryMethod());
this._wrap(history, "forward", this._patchHistoryMethod());
this._wrap(history, "go", this._patchHistoryMethod());
}
/**
* Patches the certain history api method
*/
_patchHistoryMethod() {
const plugin = this;
return (original) => {
return /* @__PURE__ */ __name(function patchHistoryMethod(...args) {
const url = `${location.pathname}${location.hash}${location.search}`;
const result = original.apply(this, args);
const urlAfter = `${location.pathname}${location.hash}${location.search}`;
if (url !== urlAfter) {
plugin._updateInteractionName(urlAfter);
}
return result;
}, "patchHistoryMethod");
};
}
/**
* unpatch the history api methods
*/
_unpatchHistoryApi() {
if ((0, import_instrumentation2.isWrapped)(history.replaceState)) this._unwrap(history, "replaceState");
if ((0, import_instrumentation2.isWrapped)(history.pushState)) this._unwrap(history, "pushState");
if ((0, import_instrumentation2.isWrapped)(history.back)) this._unwrap(history, "back");
if ((0, import_instrumentation2.isWrapped)(history.forward)) this._unwrap(history, "forward");
if ((0, import_instrumentation2.isWrapped)(history.go)) this._unwrap(history, "go");
}
/**
* Updates interaction span name
* @param url
*/
_updateInteractionName(url) {
const span = api2.trace.getSpan(api2.context.active());
if (span && typeof span.updateName === "function") {
span.updateName(`${EVENT_NAVIGATION_NAME} ${url}`);
}
}
/**
* Patches zone cancel task - this is done to be able to correctly
* decrement the number of remaining tasks
*/
_patchZoneCancelTask() {
const plugin = this;
return (original) => {
return /* @__PURE__ */ __name(function patchCancelTask(task) {
const currentZone = Zone.current;
const currentSpan = plugin._getCurrentSpan(currentZone);
if (currentSpan && plugin._shouldCountTask(task, currentZone)) {
plugin._decrementTask(currentSpan);
}
return original.call(this, task);
}, "patchCancelTask");
};
}
/**
* Patches zone schedule task - this is done to be able to correctly
* increment the number of tasks running within current zone but also to
* save time in case of timeout running from xhr plugin when waiting for
* main request from PerformanceResourceTiming
*/
_patchZoneScheduleTask() {
const plugin = this;
return (original) => {
return /* @__PURE__ */ __name(function patchScheduleTask(task) {
const currentZone = Zone.current;
const currentSpan = plugin._getCurrentSpan(currentZone);
if (currentSpan && plugin._shouldCountTask(task, currentZone)) {
plugin._incrementTask(currentSpan);
plugin._checkForTimeout(task, currentSpan);
}
return original.call(this, task);
}, "patchScheduleTask");
};
}
/**
* Patches zone run task - this is done to be able to create a span when
* user interaction starts
* @private
*/
_patchZoneRunTask() {
const plugin = this;
return (original) => {
return /* @__PURE__ */ __name(function patchRunTask(task, applyThis, applyArgs) {
const event = Array.isArray(applyArgs) && applyArgs[0] instanceof Event ? applyArgs[0] : void 0;
const target = event?.target;
const activeZone = this;
const span = target ? plugin._createSpan(target, task.eventName) : plugin._getCurrentSpan(this);
if (target && span) {
plugin._incrementTask(span);
const isReactEvent = Object.keys(target).some((k) => k.startsWith("__reactFiber$"));
if (isReactEvent) {
plugin._incrementTask(span);
const spans = plugin.spansByPendingEvent.get(task.eventName) || [];
spans.push(span);
plugin.spansByPendingEvent.set(task.eventName, spans);
}
return activeZone.run(() => {
try {
return api2.context.with(
api2.trace.setSpan(api2.context.active(), span),
() => {
const currentZone = Zone.current;
task._zone = currentZone;
return original.call(
currentZone,
task,
applyThis,
applyArgs
);
}
);
} catch (e) {
const error = e;
span.setStatus({ code: api2.SpanStatusCode.ERROR, message: error.message });
span.recordException(error);
emitOtelLog({ level: "ERROR", body: error.zoneAwareStack || error.stack });
throw e;
} finally {
plugin._decrementTask(span);
}
});
}
if (event?.type?.startsWith("react-")) {
const eventType = event.type.split("react-", 2)[1];
if (eventType) {
const spans = plugin.spansByPendingEvent.get(eventType);
if (spans?.length) {
const span2 = spans.shift();
if (span2) {
return activeZone.run(() => {
try {
return api2.context.with(
api2.trace.setSpan(api2.context.active(), span2),
() => {
const currentZone = Zone.current;
task._zone = currentZone;
return original.call(
currentZone,
task,
applyThis,
applyArgs
);
}
);
} catch (e) {
const error = e;
span2.setStatus({ code: api2.SpanStatusCode.ERROR, message: error.message });
span2.recordException(error);
emitOtelLog({ level: "ERROR", body: error.zoneAwareStack || error.stack });
throw e;
} finally {
plugin._decrementTask(span2);
}
});
}
}
}
}
try {
return original.call(activeZone, task, applyThis, applyArgs);
} catch (e) {
const error = e;
span?.setStatus({ code: api2.SpanStatusCode.ERROR, message: error.message });
span?.recordException(error);
emitOtelLog({ level: "ERROR", body: error.zoneAwareStack || error.stack });
throw e;
} finally {
if (span && plugin._shouldCountTask(task, activeZone)) {
plugin._decrementTask(span);
}
}
}, "patchRunTask");
};
}
/**
* Decides if task should be counted.
* @param task
* @param currentZone
* @private
*/
_shouldCountTask(task, currentZone) {
if (task._zone) {
currentZone = task._zone;
}
if (!currentZone || !task.data || task.data.isPeriodic) {
return false;
}
const currentSpan = this._getCurrentSpan(currentZone);
if (!currentSpan) {
return false;
}
if (!this._spansData.get(currentSpan)) {
return false;
}
return task.type === "macroTask" || task.type === "microTask";
}
/**
* Will try to end span when such span still exists.
* @param span
* @param endTime
* @private
*/
_tryToEndSpan(span, endTime) {
if (span) {
const spanData = this._spansData.get(span);
if (spanData) {
span.end(endTime);
this._spansData.delete(span);
}
}
}
/**
* implements enable function
*/
enable() {
const ZoneWithPrototype = this.getZoneWithPrototype();
this._diag.debug(
"applying patch to",
this.moduleName,
this.version,
"zone:",
!!ZoneWithPrototype
);
if (ZoneWithPrototype) {
if ((0, import_instrumentation2.isWrapped)(ZoneWithPrototype.prototype.runTask)) {
this._unwrap(ZoneWithPrototype.prototype, "runTask");
this._diag.debug("removing previous patch from method runTask");
}
if ((0, import_instrumentation2.isWrapped)(ZoneWithPrototype.prototype.scheduleTask)) {
this._unwrap(ZoneWithPrototype.prototype, "scheduleTask");
this._diag.debug("removing previous patch from method scheduleTask");
}
if ((0, import_instrumentation2.isWrapped)(ZoneWithPrototype.prototype.cancelTask)) {
this._unwrap(ZoneWithPrototype.prototype, "cancelTask");
this._diag.debug("removing previous patch from method cancelTask");
}
this._zonePatched = true;
this._wrap(
ZoneWithPrototype.prototype,
"runTask",
this._patchZoneRunTask()
);
this._wrap(
ZoneWithPrototype.prototype,
"scheduleTask",
this._patchZoneScheduleTask()
);
this._wrap(
ZoneWithPrototype.prototype,
"cancelTask",
this._patchZoneCancelTask()
);
} else {
this._zonePatched = false;
const targets = this._getPatchableEventTargets();
targets.forEach((target) => {
if ((0, import_instrumentation2.isWrapped)(target.addEventListener)) {
this._unwrap(target, "addEventListener");
this._diag.debug(
"removing previous patch from method addEventListener"
);