diff --git a/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts index 4df7f73f6c3..c7def85fc42 100644 --- a/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts @@ -236,6 +236,40 @@ describe('transform', () => { version: '1', }); }); + it('should map OpenTelemetry constructor attributes to a Zipkin tag', () => { + const span = new Span( + tracer, + api.ROOT_CONTEXT, + 'my-span', + spanContext, + api.SpanKind.SERVER, + parentId, + [], + undefined, + undefined, + { + key1: 'value1', + key2: 'value2', + } + ); + const tags: zipkinTypes.Tags = _toZipkinTags( + span, + defaultStatusCodeTagName, + defaultStatusErrorTagName + ); + + assert.deepStrictEqual(tags, { + key1: 'value1', + key2: 'value2', + [SemanticResourceAttributes.SERVICE_NAME]: 'zipkin-test', + 'telemetry.sdk.language': language, + 'telemetry.sdk.name': 'opentelemetry', + 'telemetry.sdk.version': VERSION, + cost: '112.12', + service: 'ui', + version: '1', + }); + }); it('should map OpenTelemetry SpanStatus.code to a Zipkin tag', () => { const span = new Span( tracer, diff --git a/packages/opentelemetry-sdk-trace-base/src/Span.ts b/packages/opentelemetry-sdk-trace-base/src/Span.ts index 31fb1555ac7..665fbf59ab0 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Span.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Span.ts @@ -100,7 +100,8 @@ export class Span implements APISpan, ReadableSpan { parentSpanId?: string, links: Link[] = [], startTime?: TimeInput, - _deprecatedClock?: unknown // keeping this argument even though it is unused to ensure backwards compatibility + _deprecatedClock?: unknown, // keeping this argument even though it is unused to ensure backwards compatibility + initAttributes?: SpanAttributes ) { this.name = spanName; this._spanContext = spanContext; @@ -119,6 +120,11 @@ export class Span implements APISpan, ReadableSpan { this.resource = parentTracer.resource; this.instrumentationLibrary = parentTracer.instrumentationLibrary; this._spanLimits = parentTracer.getSpanLimits(); + + if (initAttributes != null) { + this.setAttributes(initAttributes); + } + this._spanProcessor = parentTracer.getActiveSpanProcessor(); this._spanProcessor.onStart(this, context); this._attributeValueLengthLimit = diff --git a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts index b77a9427ec2..f943e6a11cc 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts @@ -132,6 +132,12 @@ export class Tracer implements api.Tracer { return nonRecordingSpan; } + // Set initial span attributes. The attributes object may have been mutated + // by the sampler, so we sanitize the merged attributes before setting them. + const initAttributes = sanitizeAttributes( + Object.assign(attributes, samplingResult.attributes) + ); + const span = new Span( this, context, @@ -140,14 +146,10 @@ export class Tracer implements api.Tracer { spanKind, parentSpanId, links, - options.startTime - ); - // Set initial span attributes. The attributes object may have been mutated - // by the sampler, so we sanitize the merged attributes before setting them. - const initAttributes = sanitizeAttributes( - Object.assign(attributes, samplingResult.attributes) + options.startTime, + undefined, + initAttributes ); - span.setAttributes(initAttributes); return span; } diff --git a/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts index 11a94ffc7c2..e4a8c6aee4f 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts @@ -1054,6 +1054,25 @@ describe('Span', () => { assert.ok(started); }); + it('should call onStart synchronously when span is started with the initial attributes', () => { + let attributes; + const processor: SpanProcessor = { + onStart: (span) => { + attributes = { ...span.attributes }; + }, + forceFlush: () => Promise.resolve(), + onEnd() {}, + shutdown: () => Promise.resolve(), + }; + + const provider = new BasicTracerProvider(); + + provider.addSpanProcessor(processor); + + provider.getTracer('default').startSpan('test', { attributes: { foo: 'bar' }}); + assert.deepStrictEqual(attributes, { foo: 'bar' }); + }); + it('should call onEnd synchronously when span is ended', () => { let ended = false; const processor: SpanProcessor = { @@ -1222,5 +1241,41 @@ describe('Span', () => { }); }); }); + + describe('when initial attributes are specified', () => { + it('should not store attributes if none are passed', () => { + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT + ); + assert.deepStrictEqual(span.attributes, {}); + }); + + it('should store the passed attributes', () => { + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT, + "", + [], + undefined, + undefined, + { + key1: 'value1', + key2: 'value2', + } + ); + + assert.deepStrictEqual(span.attributes, { + key1: 'value1', + key2: 'value2', + }); + }); + }); }); });