UNPKG

@bugsnag/core-performance

Version:
166 lines (163 loc) 7.02 kB
import { ATTRIBUTE_KEY_LENGTH_LIMIT } from './custom-attribute-limits.js'; import { isNumber } from './validation.js'; function truncateString(value, limit) { const originalLength = value.length; const newString = value.slice(0, limit); const truncatedLength = newString.length; return `${newString} *** ${originalLength - truncatedLength} CHARS TRUNCATED`; } class Attributes { constructor(initialValues) { this.attributes = initialValues; } set(name, value) { if (typeof name === 'string' && (typeof value === 'string' || typeof value === 'boolean' || isNumber(value) || Array.isArray(value))) { this.attributes.set(name, value); } } remove(name) { this.attributes.delete(name); } toJson() { return Array.from(this.attributes).map(([key, value]) => attributeToJson(key, value)); } toObject() { return Object.fromEntries(this.attributes); } } class SpanAttributes extends Attributes { get droppedAttributesCount() { return this._droppedAttributesCount; } constructor(initialValues, spanAttributeLimits, spanName, logger) { super(initialValues); this._droppedAttributesCount = 0; this.spanAttributeLimits = spanAttributeLimits; this.spanName = spanName; this.logger = logger; } truncateAttribute(name, value) { if (typeof value === 'string' && value.length > this.spanAttributeLimits.attributeStringValueLimit) { this.attributes.set(name, truncateString(value, this.spanAttributeLimits.attributeStringValueLimit)); this.logger.warn(`Span attribute ${name} in span ${this.spanName} was truncated as the string exceeds the ${this.spanAttributeLimits.attributeStringValueLimit} character limit set by attributeStringValueLimit.`); } if (Array.isArray(value) && value.length > this.spanAttributeLimits.attributeArrayLengthLimit) { const truncatedValue = value.slice(0, this.spanAttributeLimits.attributeArrayLengthLimit); this.attributes.set(name, truncatedValue); this.logger.warn(`Span attribute ${name} in span ${this.spanName} was truncated as the array exceeds the ${this.spanAttributeLimits.attributeArrayLengthLimit} element limit set by attributeArrayLengthLimit.`); } } isValidAttributeValue(value) { return typeof value === 'string' || typeof value === 'boolean' || isNumber(value) || Array.isArray(value); } isValidAttributeName(name) { return typeof name === 'string' && name.length > 0; } // Used to set internal attributes set(name, value) { if (!this.isValidAttributeName(name)) return; if (value === null || value === undefined) { this.remove(name); return; } if (this.isValidAttributeValue(value)) { this.attributes.set(name, value); } } // Used by the public API to set custom attributes setCustom(name, value) { if (!this.isValidAttributeName(name)) return; if (value === null || value === undefined) { this.remove(name); return; } if (this.isValidAttributeValue(value)) { if (!this.attributes.has(name) && this.attributes.size >= this.spanAttributeLimits.attributeCountLimit) { this._droppedAttributesCount++; this.logger.warn(`Span attribute ${name} in span ${this.spanName} was dropped as the number of attributes exceeds the ${this.spanAttributeLimits.attributeCountLimit} attribute limit set by attributeCountLimit.`); return; } if (name.length > ATTRIBUTE_KEY_LENGTH_LIMIT) { this._droppedAttributesCount++; this.logger.warn(`Span attribute ${name} in span ${this.spanName} was dropped as the key length exceeds the ${ATTRIBUTE_KEY_LENGTH_LIMIT} character fixed limit.`); return; } this.attributes.set(name, value); } } toJson() { Array.from(this.attributes).forEach(([key, value]) => { this.truncateAttribute(key, value); }); return Array.from(this.attributes) .map(([key, value]) => attributeToJson(key, value)) .filter(attr => attr !== undefined); } } class ResourceAttributes extends Attributes { constructor(releaseStage, appVersion, serviceName, sdkName, sdkVersion) { const initialValues = new Map([ ['deployment.environment', releaseStage], ['telemetry.sdk.name', sdkName], ['telemetry.sdk.version', sdkVersion], ['service.name', serviceName] ]); if (appVersion.length > 0) { initialValues.set('service.version', appVersion); } super(initialValues); } } function getJsonAttributeValue(value) { switch (typeof value) { case 'number': if (Number.isNaN(value) || !Number.isFinite(value)) { return undefined; } if (Number.isInteger(value)) { return { intValue: `${value}` }; } return { doubleValue: value }; case 'boolean': return { boolValue: value }; case 'string': return { stringValue: value }; } } function getJsonArrayAttributeValue(attributeArray) { return attributeArray .map((value) => getJsonAttributeValue(value)) .filter(value => typeof value !== 'undefined'); } /** * Converts a span attribute into an OTEL compliant value i.e. { stringValue: 'value' } * @param key the name of the span attribute * @param attribute the value of the attribute. Can be of type string | number | boolean | string[] | number[] | boolean[]. Invalid types will be removed from array attributes. * @returns */ function attributeToJson(key, attribute) { switch (typeof attribute) { case 'number': if (Number.isNaN(attribute) || !Number.isFinite(attribute)) { return undefined; } // 'bugsnag.sampling.p' must always be sent as a doubleValue if (key !== 'bugsnag.sampling.p' && Number.isInteger(attribute)) { return { key, value: { intValue: `${attribute}` } }; } return { key, value: { doubleValue: attribute } }; case 'boolean': return { key, value: { boolValue: attribute } }; case 'string': return { key, value: { stringValue: attribute } }; case 'object': if (Array.isArray(attribute)) { const arrayValues = getJsonArrayAttributeValue(attribute); return { key, value: { arrayValue: arrayValues.length > 0 ? { values: arrayValues } : {} } }; } return undefined; default: return undefined; } } export { ResourceAttributes, SpanAttributes, attributeToJson };