@opentelemetry/core
Version:
OpenTelemetry Core provides constants and utilities shared by all OpenTelemetry SDK packages.
264 lines (263 loc) • 9.31 kB
JavaScript
let _opentelemetry_api = require("@opentelemetry/api");
//#region src/common/attributes.ts
let AddAttributeDecision = /* @__PURE__ */ function(AddAttributeDecision) {
AddAttributeDecision[AddAttributeDecision["DROP_UNDEFINED"] = 0] = "DROP_UNDEFINED";
AddAttributeDecision[AddAttributeDecision["DROP_INVALID"] = 1] = "DROP_INVALID";
AddAttributeDecision[AddAttributeDecision["DROP_LIMIT_REACHED"] = 2] = "DROP_LIMIT_REACHED";
AddAttributeDecision[AddAttributeDecision["ADD_NEW"] = 3] = "ADD_NEW";
AddAttributeDecision[AddAttributeDecision["ADD_OVERWRITE_EXISTING"] = 4] = "ADD_OVERWRITE_EXISTING";
return AddAttributeDecision;
}({});
const DEFAULT_ATTRIBUTE_LIMITS = {
attributeCountLimit: 128,
attributeValueLengthLimit: Infinity
};
/**
* Maybe add the `key`/`val` attribute to the given `attributes`.
* - "Maybe" because this ensures the `key` is valid (the string is not empty),
* the `val` is an accepted spec AnyValue type (see `isAnyValue` below),
* and the attribute does not need to be dropped because of a count limit.
* Note that the TypeScript `AnyValue` type is *looser* than the spec
* `AnyValue`.
* - This also applies attribute count and value length limits.
* - This returns an `AddAttributeDecision` enum value indicating whether,
* why, and how the attribute was added. This can be relevant for the
* caller updating metrics (e.g. `droppedAttributesCount`).
*/
function maybeAddAttribute(opts) {
return maybeAddAttributeInternal(opts, isAnyValue);
}
/**
* A version of `maybeAddAttribute` that limits allowed attribute value
* types to "simple" types (`SimpleAttributeValue`).
*/
function maybeAddSimpleAttribute(opts) {
return maybeAddAttributeInternal(opts, isSimpleAttributeValue);
}
function maybeAddAttributeInternal(opts, isValidValue) {
if (opts.value === void 0) return 0;
if (opts.key.length === 0) {
_opentelemetry_api.diag.warn(`Invalid attribute key: ${opts.key}`);
return 1;
}
const isNewKey = !Object.hasOwn(opts.attributes, opts.key);
if (isNewKey && opts.currentAttributesCount >= opts.limits.attributeCountLimit) return 2;
if (!isValidValue(opts.value)) {
_opentelemetry_api.diag.warn(`Invalid attribute value set for key: ${opts.key}`);
return 1;
}
let attributeValueLengthLimit = opts.limits.attributeValueLengthLimit;
if (attributeValueLengthLimit <= 0) {
_opentelemetry_api.diag.warn(`Invalid attributeValueLengthLimit: must be positive, got ${attributeValueLengthLimit} (ignoring)`);
attributeValueLengthLimit = Infinity;
}
opts.attributes[opts.key] = copyAndTruncAnyValue(opts.value, attributeValueLengthLimit);
if (isNewKey) return 3;
return 4;
}
/**
* Clean a given set of attributes.
* - `undefined` values, at the top-level and properties of nested objects,
* are silently dropped
* - invalid attribute value types are removed, with a warning
* - attribute count and value length *limits* are applied
*
* The call signature is meant to make it ergonomic for creating SDK things
* that take `attributes` and `droppedAttributesCount` arguments, e.g.:
* this.links({
* context: opts.context,
* ...cleanAttributes(opts.attributes, attributeLimits)
* });
*
* If the given `inAttributes` is undefined, then the properties in the
* return value will be undefined.
*/
function cleanAttributes(inAttributes, limits) {
return cleanAttributesInternal(inAttributes, limits, isAnyValue);
}
/**
* A version of `cleanAttributes` that limits allowed attribute value types to
* "simple" types (`SimpleAttributeValue`).
*/
function cleanSimpleAttributes(inAttributes, limits) {
return cleanAttributesInternal(inAttributes, limits, isSimpleAttributeValue);
}
function cleanAttributesInternal(inAttributes, limits, isValidValue) {
if (typeof inAttributes !== "object" || inAttributes == null) return {};
const attributes = {};
let droppedAttributesCount = 0;
let attributeValueLengthLimit = limits.attributeValueLengthLimit;
if (attributeValueLengthLimit <= 0) {
_opentelemetry_api.diag.warn(`Invalid attributeValueLengthLimit: must be positive, got ${attributeValueLengthLimit} (ignoring)`);
attributeValueLengthLimit = Infinity;
}
let count = 0;
for (const [key, val] of Object.entries(inAttributes)) {
if (val === void 0) continue;
if (count >= limits.attributeCountLimit) {
droppedAttributesCount++;
continue;
}
if (key.length === 0) {
_opentelemetry_api.diag.warn(`Invalid attribute key: ${key}`);
droppedAttributesCount++;
continue;
}
if (!isValidValue(val)) {
_opentelemetry_api.diag.warn(`Invalid attribute value set for key: ${key}`);
droppedAttributesCount++;
continue;
}
attributes[key] = copyAndTruncAnyValue(val, attributeValueLengthLimit);
count++;
}
return {
attributes,
droppedAttributesCount
};
}
/**
* Returns true iff the given value is a supported AnyValue per
* https://opentelemetry.io/docs/specs/otel/common/#anyvalue
*
* - Primitives/scalars: string, boolean, number
* (Note that this *excludes* BigInt and Symbol)
* - Byte arrays (Uint8Array, Buffer)
* - Arrays of AnyValue (heterogeneous arrays allowed)
* - Maps from string to AnyValue (nested objects)
* - Empty values (null)
*
* Notes:
* - `undefined` is *not* considered an AnyValue.
*
* @param val
* @returns true if the value is a valid AnyValue, false otherwise
*/
function isAnyValue(val) {
return isAnyValueInternal(val, /* @__PURE__ */ new WeakSet());
}
function isAnyValueInternal(val, visited) {
switch (typeof val) {
case "undefined":
case "bigint":
case "symbol":
case "function": return false;
case "string":
case "number":
case "boolean": return true;
case "object": break;
default: return false;
}
if (val === null) return true;
if (val instanceof Uint8Array) return true;
if (visited.has(val)) return false;
visited.add(val);
if (Array.isArray(val)) {
for (const item of val) if (item !== void 0 && !isAnyValueInternal(item, visited)) return false;
return true;
}
const obj = val;
if (obj.constructor !== Object && obj.constructor !== void 0) return false;
for (const k of Object.keys(obj)) {
const v = obj[k];
if (v !== void 0 && !isAnyValueInternal(v, visited)) return false;
}
return true;
}
/**
* Attribute value length limiting/truncation per:
* https://opentelemetry.io/docs/specs/otel/common/#attribute-limits
*
* For arrays and objects, this also returns a deep copy to avoid mutating data
* passed in by calling code.
*
* @param {AnyValue} val - The attribute value. We are assuming this has
* already been guarded by `isAnyValue`. This filters out non-plain objects,
* like RegExp & Date, and circular references.
*/
function copyAndTruncAnyValue(val, limit) {
switch (typeof val) {
case "number":
case "boolean":
case "undefined":
case "bigint":
case "symbol":
case "function": return val;
case "string": if (val.length <= limit) return val;
else return val.substring(0, limit);
case "object": break;
default: return val;
}
if (val === null) return val;
if (val instanceof Uint8Array) {
if (val.length <= limit) return val;
else return val.slice(0, limit);
}
if (Array.isArray(val)) return val.map((elem) => copyAndTruncAnyValue(elem, limit));
const truncObj = {};
const obj = val;
for (const k of Object.keys(obj)) if (obj[k] !== void 0) truncObj[k] = copyAndTruncAnyValue(obj[k], limit);
return truncObj;
}
/**
* Clean the given attributes to just those with "simple" attribute types.
* This remains for backwards compatibility with 2.x.
*
* Changes to previous behavior:
* - a default attribute count limit of 128 is applied
* - nullish values (except in arrays) are removed
*
* @deprecated Use `cleanAttributes` or `cleanSimpleAttributes`.
*/
function sanitizeAttributes(inAttributes) {
const { attributes } = cleanSimpleAttributes(inAttributes, DEFAULT_ATTRIBUTE_LIMITS);
return attributes ?? {};
}
/**
* Return true if the given attribute value is a "simple" attribute value type.
*
* Changes to previous behavior: nullish values now return *false*.
*
* @deprecated use `isAnyValue`, or `isSimpleAttributeValue`.
*/
const isAttributeValue = isSimpleAttributeValue;
function isSimpleAttributeValue(val) {
if (Array.isArray(val)) return isHomogeneousAttributeValueArray(val);
return isValidPrimitiveAttributeValueType(typeof val);
}
function isHomogeneousAttributeValueArray(arr) {
let type;
for (const element of arr) {
if (element == null) continue;
const elementType = typeof element;
if (elementType === type) continue;
if (!type) {
if (isValidPrimitiveAttributeValueType(elementType)) {
type = elementType;
continue;
}
return false;
}
return false;
}
return true;
}
function isValidPrimitiveAttributeValueType(valType) {
switch (valType) {
case "number":
case "boolean":
case "string": return true;
}
return false;
}
//#endregion
exports.AddAttributeDecision = AddAttributeDecision;
exports.cleanAttributes = cleanAttributes;
exports.cleanSimpleAttributes = cleanSimpleAttributes;
exports.isAnyValue = isAnyValue;
exports.isAttributeValue = isAttributeValue;
exports.isSimpleAttributeValue = isSimpleAttributeValue;
exports.maybeAddAttribute = maybeAddAttribute;
exports.maybeAddSimpleAttribute = maybeAddSimpleAttribute;
exports.sanitizeAttributes = sanitizeAttributes;
//# sourceMappingURL=attributes.cjs.map