UNPKG

@opentelemetry/core

Version:

OpenTelemetry Core provides constants and utilities shared by all OpenTelemetry SDK packages.

256 lines (255 loc) • 8.93 kB
import { diag } from "@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) { 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)) { diag.warn(`Invalid attribute value set for key: ${opts.key}`); return 1; } let attributeValueLengthLimit = opts.limits.attributeValueLengthLimit; if (attributeValueLengthLimit <= 0) { 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) { 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) { diag.warn(`Invalid attribute key: ${key}`); droppedAttributesCount++; continue; } if (!isValidValue(val)) { 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 export { AddAttributeDecision, cleanAttributes, cleanSimpleAttributes, isAnyValue, isAttributeValue, isSimpleAttributeValue, maybeAddAttribute, maybeAddSimpleAttribute, sanitizeAttributes }; //# sourceMappingURL=attributes.mjs.map