@bugsnag/core-performance
Version:
Core performance client
166 lines (163 loc) • 7.02 kB
JavaScript
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 };