diff --git a/src/transform.ts b/src/transform.ts index 0134331..8b8e741 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -73,11 +73,12 @@ function createSpan( ): typeof Span { // convert to datadog span const [ddTraceId, ddSpanId, ddParentId] = getTraceContext(span); + const [ddResourceTags, ddResourceServiceName] = getResourceInfo(span); // generate datadog span base const ddSpanBase = new Span(NOOP_TRACER, null, SAMPLER, null, { startTime: hrTimeToMilliseconds(span.startTime), - tags: tags, + tags: Object.assign(tags, ddResourceTags), operationName: createSpanName(span), }); const ddSpanBaseContext = ddSpanBase.context(); @@ -95,7 +96,14 @@ function createSpan( addSpanType(ddSpanBase, span); // set datadog specific env and version tags - addDatadogTags(ddSpanBase, span, serviceName, env, version); + addDatadogTags( + ddSpanBase, + span, + serviceName, + env, + version, + ddResourceServiceName + ); // set sampling rate setSamplingRate(ddSpanBase, span); @@ -164,12 +172,13 @@ function addDatadogTags( span: ReadableSpan, serviceName?: string | undefined, env?: string | undefined, - version?: string | undefined + version?: string | undefined, + ddResourceServiceName?: string | undefined ): void { // set reserved service and resource tags ddSpanBase.addTags({ [DatadogDefaults.RESOURCE_TAG]: createResource(span), - [DatadogDefaults.SERVICE_TAG]: serviceName, + [DatadogDefaults.SERVICE_TAG]: ddResourceServiceName || serviceName, }); // set env tag @@ -334,3 +343,28 @@ function setSamplingRate(ddSpanBase: typeof Span, span: ReadableSpan): void { ddSpanBase.setTag(DatadogDefaults.SAMPLE_RATE_METRIC_KEY, samplingRate); } } + +function getResourceInfo(span: ReadableSpan): any[] { + // extract the resource labels/attributes and potential service name to use for spans + const ddResourceTags: { [key: string]: any } = {}; + let resourceServiceName: any; + const resource: any = span.resource; + + if (!resource) return [ddResourceTags, resourceServiceName]; + + const resourceTags: { [key: string]: string | number } | undefined = + resource['labels'] || resource['attributes']; + + if (!resourceTags) return [ddResourceTags, resourceServiceName]; + + // the spec around whether this is labels or attributes varies between versions + for (const [key, value] of Object.entries(resourceTags)) { + if (key === 'service.name') { + resourceServiceName = value.toString(); + } else { + ddResourceTags[key] = value.toString(); + } + } + + return [ddResourceTags, resourceServiceName]; +} diff --git a/test/mocks.ts b/test/mocks.ts index fa6ca9e..90f6cb4 100644 --- a/test/mocks.ts +++ b/test/mocks.ts @@ -10,6 +10,8 @@ import * as api from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; import { TraceState } from '@opentelemetry/core'; +export const mockResourceServiceName = 'otel-resource-service-name'; + export const mockSpanContextUnsampled = { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', @@ -89,8 +91,57 @@ export const mockExandedReadableSpan: any = { ], duration: [32, 800000000], resource: new Resource({ - service: 'ui', - version: 1, + zervice: 'ui', + otherInfo: 'one', + cost: 112.12, + }), + instrumentationLibrary: { + name: 'default', + version: '0.0.1', + }, +}; + +export const mockExandedReadableSpanWithResourceService: any = { + name: 'my-span', + kind: api.SpanKind.INTERNAL, + spanContext: mockSpanContextUnsampled, + startTime: [1566156729, 709], + endTime: [1566156731, 709], + ended: true, + status: { + code: api.CanonicalCode.OK, + }, + attributes: { + testBool: true, + testString: 'test', + testNum: '3.142', + }, + links: [ + { + context: { + traceId: 'a4cda95b652f4a1592b449d5929fda1b', + spanId: '3e0c63257de34c92', + }, + attributes: { + testBool: true, + testString: 'test', + testNum: 3.142, + }, + }, + ], + events: [ + { + name: 'something happened', + attributes: { + error: true, + }, + time: [1566156729, 809], + }, + ], + duration: [32, 800000000], + resource: new Resource({ + 'service.name': mockResourceServiceName, + otherInfo: 'one', cost: 112.12, }), instrumentationLibrary: { diff --git a/test/transform.test.ts b/test/transform.test.ts index a31c518..af48fa2 100644 --- a/test/transform.test.ts +++ b/test/transform.test.ts @@ -16,6 +16,8 @@ import { mockSpanContextSampled, mockSpanContextOrigin, mockExandedReadableSpan, + mockExandedReadableSpanWithResourceService, + mockResourceServiceName, } from './mocks'; describe('transform', () => { @@ -36,8 +38,18 @@ describe('transform', () => { return otelSpans; }; + const generateOtelSpansWithResourceService = function ( + options: any + ): ReadableSpan[] { + const otelSpans = []; + const span: ReadableSpan = mockExandedReadableSpanWithResourceService; + const updatedSpan = Object.assign(span, options); + otelSpans.push(updatedSpan); + return otelSpans; + }; + describe('translateToDatadog', () => { - it('should convert an OpenTelemetry span and its properties to a finished DatadogSpan', () => { + it('should convert an OpenTelemetry span and its properties, including resource info, to a finished DatadogSpan', () => { const spans = generateOtelSpans({ spanContext: spanContextUnsampled }); const datadogSpans = translateToDatadog(spans, serviceName); const datadogSpan = datadogSpans[0]; @@ -66,7 +78,8 @@ describe('transform', () => { datadogSpan.start, Math.round(hrTimeToMilliseconds(spans[0].startTime) * 1e6) ); - assert.strictEqual(Object.keys(datadogSpan.meta).length, 4); + + assert.strictEqual(Object.keys(datadogSpan.meta).length, 7); assert.strictEqual(Object.keys(datadogSpan.metrics).length, 1); assert.strictEqual( datadogSpan.metrics['_sample_rate'], @@ -157,5 +170,14 @@ describe('transform', () => { const datadogSpan = datadogSpans[0]; assert.strictEqual(datadogSpan.metrics['_sample_rate'], -1); }); + + it('should set the resource service.name as dd span service if it exists', () => { + const spans = generateOtelSpansWithResourceService({ + spanContext: spanContextSampled, + }); + const datadogSpans = translateToDatadog(spans, serviceName); + const datadogSpan = datadogSpans[0]; + assert.strictEqual(datadogSpan.service, mockResourceServiceName); + }); }); });