autotel
Version:
Write Once, Observe Anywhere
175 lines (172 loc) • 6.72 kB
JavaScript
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 };