UNPKG

autotel

Version:
175 lines (172 loc) 6.72 kB
import { h as isFunction, i as asNumber, l as asString, o as asPlainRecordOrMap, t as asBoolean } from "./values-xBtdXtjA.js"; import { SpanStatusCode } from "@opentelemetry/api"; //#region src/flatten-attributes.ts /** What a value that cannot be represented is recorded as. */ const INVALID_DATE = "<invalid-date>"; const INVALID_NUMBER = "<invalid-number>"; const SERIALIZATION_FAILED = "<serialization-failed>"; const CIRCULAR_REFERENCE = "<circular-reference>"; /** * Convert a value that arrived from outside to an OTel-compatible * AttributeValue. Returns undefined when the value cannot be represented - * which is how flattenToAttributes below learns it has an object to descend * into rather than a leaf to record. * * Total by construction: this runs on whatever an application hands an * attribute setter, so a value that cannot be read is a marker in the * telemetry, never an exception on the request path. */ function toAttributeValue(value) { const scalar = asString(value) ?? asNumber(value) ?? asBoolean(value); if (scalar !== void 0) return scalar; if (typeof value === "number") return INVALID_NUMBER; try { if (Array.isArray(value)) return toAttributeArray(value); if (value instanceof Set) return toAttributeArray([...value]); if (value instanceof Date) return Number.isNaN(value.getTime()) ? INVALID_DATE : value.toISOString(); if (value instanceof Error) return value.message; } catch { return SERIALIZATION_FAILED; } } /** A homogeneous array as itself; anything else as the JSON it serialises to. */ function toAttributeArray(values) { const strings = values.filter((v) => asString(v) !== void 0); if (strings.length === values.length) return strings.map(String); const numbers = values.filter((v) => asNumber(v) !== void 0); if (numbers.length === values.length) return numbers.map(Number); const booleans = values.filter((v) => asBoolean(v) !== void 0); if (booleans.length === values.length) return booleans.map(Boolean); try { return JSON.stringify(values); } catch { return SERIALIZATION_FAILED; } } /** * Recursively flatten a nested object into dot-notation OTel attributes. * Includes circular reference protection via WeakSet. * * Every key is converted inside its own guard, so one value that cannot be read * - a getter that throws, an exotic proxy, a `Map` whose key has no string form * - costs that key alone and never the call. Keys are listed before their * values are read, so a sibling of the bad value still lands. */ function flattenToAttributes(fields, prefix = "") { const out = {}; const seen = /* @__PURE__ */ new WeakSet(); function flatten(obj, currentPrefix) { for (const key of Object.keys(obj)) { const nextKey = currentPrefix ? `${currentPrefix}.${key}` : key; try { const value = obj[key]; if (value == null) continue; const attr = toAttributeValue(value); if (attr !== void 0) { out[nextKey] = attr; continue; } const nested = asPlainRecordOrMap(value); if (nested !== void 0) { if (seen.has(value)) { out[nextKey] = CIRCULAR_REFERENCE; continue; } seen.add(value); flatten(nested, nextKey); continue; } const json = JSON.stringify(value); if (json !== void 0) out[nextKey] = json; } catch { out[nextKey] = SERIALIZATION_FAILED; } } } try { flatten(fields, prefix); } catch {} return out; } //#endregion //#region src/structured-error.ts const internalKey = Symbol.for("autotel.error.internal"); function createStructuredError(input) { const error = new Error(input.message, { cause: input.cause }); error.name = input.name ?? "StructuredError"; if (input.why !== void 0) error.why = input.why; if (input.fix !== void 0) error.fix = input.fix; if (input.link !== void 0) error.link = input.link; if (input.code !== void 0) error.code = input.code; if (input.status !== void 0) error.status = input.status; if (input.details !== void 0) error.details = input.details; if (input.internal !== void 0) Object.defineProperty(error, internalKey, { value: input.internal, enumerable: false, writable: false, configurable: true }); Object.defineProperty(error, "internal", { get() { return this[internalKey]; }, enumerable: false, configurable: true }); error.toString = () => { const lines = [`${error.name}: ${error.message}`]; if (error.why) lines.push(` Why: ${error.why}`); if (error.fix) lines.push(` Fix: ${error.fix}`); if (error.link) lines.push(` Link: ${error.link}`); if (error.code !== void 0) lines.push(` Code: ${error.code}`); if (error.status !== void 0) lines.push(` Status: ${error.status}`); if (error.cause instanceof Error) lines.push(` Caused by: ${error.cause.name}: ${error.cause.message}`); else if (error.cause !== void 0) lines.push(` Caused by: ${String(error.cause)}`); return lines.join("\n"); }; return error; } function structuredErrorToJSON(error) { const result = { name: error.name, message: error.message }; if (error.status !== void 0) result.status = error.status; if (error.why || error.fix || error.link) result.data = { ...error.why && { why: error.why }, ...error.fix && { fix: error.fix }, ...error.link && { link: error.link } }; if (error.code !== void 0) result.code = error.code; if (error.details) result.details = error.details; if (error.cause instanceof Error) result.cause = { name: error.cause.name, message: error.cause.message }; return result; } function getStructuredErrorAttributes(error) { const structured = error; const attributes = {}; attributes["error.type"] = error.name || "Error"; attributes["error.message"] = error.message; if (error.stack) attributes["error.stack"] = error.stack; if (structured.why) attributes["error.why"] = structured.why; if (structured.fix) attributes["error.fix"] = structured.fix; if (structured.link) attributes["error.link"] = structured.link; if (structured.code !== void 0) attributes["error.code"] = String(structured.code); if (structured.status !== void 0) attributes["error.status"] = structured.status; if (structured.details) Object.assign(attributes, flattenToAttributes(structured.details, "error.details")); return attributes; } function recordStructuredError(ctx, error) { const maybeRecordException = ctx.recordException; if (isFunction(maybeRecordException)) maybeRecordException(error); ctx.setStatus({ code: SpanStatusCode.ERROR, message: error.message }); ctx.setAttributes(getStructuredErrorAttributes(error)); } //#endregion export { flattenToAttributes as a, structuredErrorToJSON as i, getStructuredErrorAttributes as n, toAttributeValue as o, recordStructuredError as r, createStructuredError as t };