Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(opentelemetry-sdk-trace-base): implemented option to limit length of values of attributes #2418

Merged
2 changes: 2 additions & 0 deletions packages/opentelemetry-core/src/utils/environment.ts
Expand Up @@ -28,6 +28,7 @@ const ENVIRONMENT_NUMBERS_KEYS = [
'OTEL_BSP_MAX_EXPORT_BATCH_SIZE',
'OTEL_BSP_MAX_QUEUE_SIZE',
'OTEL_BSP_SCHEDULE_DELAY',
'OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT',
'OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT',
'OTEL_SPAN_EVENT_COUNT_LIMIT',
'OTEL_SPAN_LINK_COUNT_LIMIT',
Expand Down Expand Up @@ -117,6 +118,7 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_PROPAGATORS: ['tracecontext', 'baggage'],
OTEL_RESOURCE_ATTRIBUTES: '',
OTEL_SERVICE_NAME: '',
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: Infinity,
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 128,
OTEL_SPAN_EVENT_COUNT_LIMIT: 128,
OTEL_SPAN_LINK_COUNT_LIMIT: 128,
Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry-core/test/utils/environment.test.ts
Expand Up @@ -84,6 +84,7 @@ describe('environment', () => {
OTEL_LOG_LEVEL: 'ERROR',
OTEL_NO_PATCH_MODULES: 'a,b,c',
OTEL_RESOURCE_ATTRIBUTES: '<attrs>',
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: 100,
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 10,
OTEL_SPAN_EVENT_COUNT_LIMIT: 20,
OTEL_SPAN_LINK_COUNT_LIMIT: 30,
Expand All @@ -93,6 +94,7 @@ describe('environment', () => {
const env = getEnv();
assert.deepStrictEqual(env.OTEL_NO_PATCH_MODULES, ['a', 'b', 'c']);
assert.strictEqual(env.OTEL_LOG_LEVEL, DiagLogLevel.ERROR);
assert.strictEqual(env.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, 100);
assert.strictEqual(env.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, 10);
assert.strictEqual(env.OTEL_SPAN_EVENT_COUNT_LIMIT, 20);
assert.strictEqual(env.OTEL_SPAN_LINK_COUNT_LIMIT, 30);
Expand Down
48 changes: 47 additions & 1 deletion packages/opentelemetry-sdk-trace-base/src/Span.ts
Expand Up @@ -105,7 +105,7 @@ export class Span implements api.Span, ReadableSpan {
) {
return this;
}
this.attributes[key] = value;
this.attributes[key] = this._truncateToSize(value);
return this;
}

Expand Down Expand Up @@ -235,4 +235,50 @@ export class Span implements api.Span, ReadableSpan {
}
return this._ended;
}

// Utility function to truncate given value within size
// for value type of string, will truncate to given limit
// for type of non-string, will return same value
private _truncateToLimitUtil(value: string, limit: number): string {
if (value.length <= limit) {
return value;
}
return value.substr(0, limit);
}

/**
* If the given attribute value is of type string and has more characters than given {@code attributeValueLengthLimit} then
* return string with trucated to {@code attributeValueLengthLimit} characters
*
* If the given attribute value is array of strings then
* return new array of strings with each element truncated to {@code attributeValueLengthLimit} characters
*
* Otherwise return same Attribute {@code value}
*
* @param value Attribute value
* @returns truncated attribute value if required, otherwise same value
*/
private _truncateToSize(value?: SpanAttributeValue): SpanAttributeValue | undefined {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private _truncateToSize(value?: SpanAttributeValue): SpanAttributeValue | undefined {
private _truncateToSize(value: SpanAttributeValue): SpanAttributeValue {

const limit = this._spanLimits.attributeValueLengthLimit;
// Check limit
// undefined limit means do not truncate
if (typeof limit !== 'number' || limit <= 0) {
Flarna marked this conversation as resolved.
Show resolved Hide resolved
// Non-integer and negative values are invalid, so do not truncate
api.diag.warn(`Attribute value limit must be positive, got ${limit}`);
dyladan marked this conversation as resolved.
Show resolved Hide resolved
return value;
}

// String
if (typeof value === 'string') {
return this._truncateToLimitUtil(value, limit);
}

// Array of strings
if (Array.isArray(value)) {
return (value as []).map(val => typeof val === 'string' ? this._truncateToLimitUtil(val, limit) : val);
Flarna marked this conversation as resolved.
Show resolved Hide resolved
}

// Other types, no need to apply value length limit
return value;
}
}
1 change: 1 addition & 0 deletions packages/opentelemetry-sdk-trace-base/src/config.ts
Expand Up @@ -38,6 +38,7 @@ export const DEFAULT_CONFIG = {
sampler: buildSamplerFromEnv(env),
forceFlushTimeoutMillis: 30000,
spanLimits: {
attributeValueLengthLimit: getEnv().OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
attributeCountLimit: getEnv().OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
linkCountLimit: getEnv().OTEL_SPAN_LINK_COUNT_LIMIT,
eventCountLimit: getEnv().OTEL_SPAN_EVENT_COUNT_LIMIT,
Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry-sdk-trace-base/src/types.ts
Expand Up @@ -63,6 +63,8 @@ export interface SDKRegistrationConfig {

/** Global configuration of trace service */
export interface SpanLimits {
/** attributeValueLengthLimit is maximum allowed attribute value size */
attributeValueLengthLimit?: number;
/** attributeCountLimit is number of attributes per span */
attributeCountLimit?: number;
/** linkCountLimit is number of links per span */
Expand Down
Expand Up @@ -64,74 +64,97 @@ describe('BasicTracerProvider', () => {
});

describe('constructor', () => {
it('should construct an instance without any options', () => {
const provider = new BasicTracerProvider();
assert.ok(provider instanceof BasicTracerProvider);
});
describe('when options not defined', () => {
it('should construct an instance', () => {
const tracer = new BasicTracerProvider();
assert.ok(tracer instanceof BasicTracerProvider);
});

it('should construct an instance with sampler', () => {
const provider = new BasicTracerProvider({
sampler: new AlwaysOnSampler(),
it('should use noop span processor by default', () => {
const tracer = new BasicTracerProvider();
assert.ok(tracer.activeSpanProcessor instanceof NoopSpanProcessor);
});
assert.ok(provider instanceof BasicTracerProvider);
});

it('should construct an instance with default span limits', () => {
const tracer = new BasicTracerProvider({}).getTracer('default');
assert.deepStrictEqual(tracer.getSpanLimits(), {
attributeCountLimit: 128,
eventCountLimit: 128,
linkCountLimit: 128,
describe('when "sampler" option defined', () => {
it('should have an instance with sampler', () => {
const tracer = new BasicTracerProvider({
sampler: new AlwaysOnSampler(),
});
assert.ok(tracer instanceof BasicTracerProvider);
});
});

it('should construct an instance with customized attributeCountLimit span limits', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
attributeCountLimit: 100,
},
}).getTracer('default');
assert.deepStrictEqual(tracer.getSpanLimits(), {
attributeCountLimit: 100,
eventCountLimit: 128,
linkCountLimit: 128,
describe('spanLimits', () => {
describe('when not defined default values', () => {
it('should have tracer with default values', () => {
const tracer = new BasicTracerProvider({}).getTracer('default');
assert.deepStrictEqual(tracer.getSpanLimits(), {
attributeValueLengthLimit: Infinity,
attributeCountLimit: 128,
eventCountLimit: 128,
linkCountLimit: 128,
});
});
});
});

it('should construct an instance with customized eventCountLimit span limits', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
eventCountLimit: 300,
},
}).getTracer('default');
assert.deepStrictEqual(tracer.getSpanLimits(), {
attributeCountLimit: 128,
eventCountLimit: 300,
linkCountLimit: 128,
describe('when "attributeCountLimit" is defined', () => {
it('should have tracer with defined value', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
attributeCountLimit: 100,
},
}).getTracer('default');
const spanLimits = tracer.getSpanLimits();
assert.strictEqual(spanLimits.attributeCountLimit, 100);
});
});
});

it('should construct an instance with customized linkCountLimit span limits', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
linkCountLimit: 10,
},
}).getTracer('default');
assert.deepStrictEqual(tracer.getSpanLimits(), {
attributeCountLimit: 128,
eventCountLimit: 128,
linkCountLimit: 10,
describe('when "attributeValueLengthLimit" is defined', () => {
it('should have tracer with defined value', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
attributeValueLengthLimit: 10,
},
}).getTracer('default');
const spanLimits = tracer.getSpanLimits();
assert.strictEqual(spanLimits.attributeValueLengthLimit, 10);
});

it('should have tracer with negative "attributeValueLengthLimit" value', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
attributeValueLengthLimit: -10,
},
}).getTracer('default');
const spanLimits = tracer.getSpanLimits();
assert.strictEqual(spanLimits.attributeValueLengthLimit, -10);
});
});
});

it('should construct an instance of BasicTracerProvider', () => {
const tracer = new BasicTracerProvider();
assert.ok(tracer instanceof BasicTracerProvider);
});
describe('when "eventCountLimit" is defined', () => {
it('should have tracer with defined value', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
eventCountLimit: 300,
},
}).getTracer('default');
const spanLimits = tracer.getSpanLimits();
assert.strictEqual(spanLimits.eventCountLimit, 300);
});
});

it('should use noop span processor by default', () => {
const tracer = new BasicTracerProvider();
assert.ok(tracer.activeSpanProcessor instanceof NoopSpanProcessor);
describe('when "linkCountLimit" is defined', () => {
it('should have tracer with defined value', () => {
const tracer = new BasicTracerProvider({
spanLimits: {
linkCountLimit: 10,
},
}).getTracer('default');
const spanLimits = tracer.getSpanLimits();
assert.strictEqual(spanLimits.linkCountLimit, 10);
});
});
});
});

Expand Down