autotel
Version:
Write Once, Observe Anywhere
210 lines (207 loc) • 7.46 kB
JavaScript
const require_values = require('./values-tm4bi0FF.cjs');
let _opentelemetry_api = require("@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 = require_values.asString(value) ?? require_values.asNumber(value) ?? require_values.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) => require_values.asString(v) !== void 0);
if (strings.length === values.length) return strings.map(String);
const numbers = values.filter((v) => require_values.asNumber(v) !== void 0);
if (numbers.length === values.length) return numbers.map(Number);
const booleans = values.filter((v) => require_values.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 = require_values.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 (require_values.isFunction(maybeRecordException)) maybeRecordException(error);
ctx.setStatus({
code: _opentelemetry_api.SpanStatusCode.ERROR,
message: error.message
});
ctx.setAttributes(getStructuredErrorAttributes(error));
}
//#endregion
Object.defineProperty(exports, 'createStructuredError', {
enumerable: true,
get: function () {
return createStructuredError;
}
});
Object.defineProperty(exports, 'flattenToAttributes', {
enumerable: true,
get: function () {
return flattenToAttributes;
}
});
Object.defineProperty(exports, 'getStructuredErrorAttributes', {
enumerable: true,
get: function () {
return getStructuredErrorAttributes;
}
});
Object.defineProperty(exports, 'recordStructuredError', {
enumerable: true,
get: function () {
return recordStructuredError;
}
});
Object.defineProperty(exports, 'structuredErrorToJSON', {
enumerable: true,
get: function () {
return structuredErrorToJSON;
}
});
Object.defineProperty(exports, 'toAttributeValue', {
enumerable: true,
get: function () {
return toAttributeValue;
}
});