diff --git a/src/telemetry-tracing.ts b/src/telemetry-tracing.ts index 74dc7945f..455174a9d 100644 --- a/src/telemetry-tracing.ts +++ b/src/telemetry-tracing.ts @@ -299,7 +299,6 @@ export class PubsubSpans { ['messaging.system']: 'gcp_pubsub', ['messaging.destination.name']: destinationId ?? destinationName, ['gcp.project_id']: projectId, - ['code.function']: '', } as SpanAttributes; if (message) { @@ -332,14 +331,11 @@ export class PubsubSpans { const topicInfo = getTopicInfo(topicName); const span: Span = getTracer().startSpan(`${topicName} create`, { kind: SpanKind.PRODUCER, - attributes: PubsubSpans.createAttributes( - getTopicInfo(topicName), - message - ), + attributes: PubsubSpans.createAttributes(topicInfo, message), }); if (topicInfo.topicId) { span.updateName(`${topicInfo.topicId} create`); - span.setAttribute('messaging.destination_name', topicInfo.topicId); + span.setAttribute('messaging.destination.name', topicInfo.topicId); } return span; @@ -359,8 +355,8 @@ export class PubsubSpans { } static createReceiveSpan( - subName: string, message: PubsubMessage, + subName: string, parent: Context | undefined ): Span { const subInfo = getSubscriptionInfo(subName); @@ -528,13 +524,12 @@ export class PubsubSpans { isInitial?: boolean ): Span | undefined { const subInfo = getSubscriptionInfo(subName); - const attributes = this.createAttributes(subInfo, message); - const span = PubsubSpans.createChildSpan( - `${subInfo.subId} ${type}`, + const span = PubsubSpans.createReceiveSpan( message, - undefined, - attributes + `${subInfo.subId} ${type}`, + undefined ); + if (deadline) { span?.setAttribute( 'messaging.gcp_pubsub.message.ack_deadline_seconds', @@ -749,7 +744,7 @@ export function extractSpan( } } - const span = PubsubSpans.createReceiveSpan(subName, message, context); + const span = PubsubSpans.createReceiveSpan(message, subName, context); message.parentSpan = span; return span; } diff --git a/test/telemetry-tracing.ts b/test/telemetry-tracing.ts index 8a2f76f76..0549ecb73 100644 --- a/test/telemetry-tracing.ts +++ b/test/telemetry-tracing.ts @@ -28,6 +28,9 @@ describe('OpenTelemetryTracer', () => { beforeEach(() => { exporter.reset(); }); + afterEach(() => { + exporter.reset(); + }); describe('project parser', () => { it('parses subscription info', () => { @@ -49,124 +52,289 @@ describe('OpenTelemetryTracer', () => { assert.strictEqual(info.subId, undefined); assert.strictEqual(info.subName, undefined); }); + + it('parses broken subscription info', () => { + const name = 'projec/foo_foo/subs/sub_sub'; + const info = otel.getSubscriptionInfo(name); + assert.strictEqual(info.subName, name); + assert.strictEqual(info.projectId, undefined); + assert.strictEqual(info.subId, undefined); + assert.strictEqual(info.topicId, undefined); + assert.strictEqual(info.topicName, undefined); + }); + + it('parses broken topic info', () => { + const name = 'projec/foo_foo/tops/top_top'; + const info = otel.getTopicInfo(name); + assert.strictEqual(info.subName, undefined); + assert.strictEqual(info.projectId, undefined); + assert.strictEqual(info.subId, undefined); + assert.strictEqual(info.topicId, undefined); + assert.strictEqual(info.topicName, name); + }); }); - it('creates a span', () => { - const message: PubsubMessage = {}; - const span = otel.PubsubSpans.createPublisherSpan( - message, - 'projects/test/topics/topicfoo' - ) as trace.Span; - span.end(); + describe('basic span creation', () => { + it('creates a span', () => { + const message: PubsubMessage = {}; + const span = otel.PubsubSpans.createPublisherSpan( + message, + 'projects/test/topics/topicfoo' + ) as trace.Span; + span.end(); - const spans = exporter.getFinishedSpans(); - assert.notStrictEqual(spans.length, 0); - const exportedSpan = spans.concat().pop()!; + const spans = exporter.getFinishedSpans(); + assert.notStrictEqual(spans.length, 0); + const exportedSpan = spans.concat().pop()!; - assert.strictEqual(exportedSpan.name, 'topicfoo create'); - assert.strictEqual(exportedSpan.kind, SpanKind.PRODUCER); - }); + assert.strictEqual(exportedSpan.name, 'topicfoo create'); + assert.strictEqual(exportedSpan.kind, SpanKind.PRODUCER); + }); - it('injects a trace context', () => { - const message: PubsubMessage = { - attributes: {}, - }; - const span = otel.PubsubSpans.createPublisherSpan( - message, - 'projects/test/topics/topicfoo' - ) as trace.Span; - - otel.injectSpan(span, message, otel.OpenTelemetryLevel.Modern); - - assert.strictEqual( - Object.getOwnPropertyNames(message.attributes).includes( - otel.modernAttributeName - ), - true - ); - }); + it('injects a trace context', () => { + const message: PubsubMessage = { + attributes: {}, + }; + const span = otel.PubsubSpans.createPublisherSpan( + message, + 'projects/test/topics/topicfoo' + ) as trace.Span; - it('injects a trace context and legacy baggage', () => { - const message: PubsubMessage = { - attributes: {}, - }; - const span = otel.PubsubSpans.createPublisherSpan( - message, - 'projects/test/topics/topicfoo' - ); - - otel.injectSpan(span, message, otel.OpenTelemetryLevel.Legacy); - - assert.strictEqual( - Object.getOwnPropertyNames(message.attributes).includes( - otel.modernAttributeName - ), - true - ); - assert.strictEqual( - Object.getOwnPropertyNames(message.attributes).includes( - otel.legacyAttributeName - ), - true - ); + otel.injectSpan(span, message, otel.OpenTelemetryLevel.Modern); + + assert.strictEqual( + Object.getOwnPropertyNames(message.attributes).includes( + otel.modernAttributeName + ), + true + ); + }); }); - it('should issue a warning if OpenTelemetry span context key is set', () => { - const message: PubsubMessage = { - attributes: { - [otel.legacyAttributeName]: 'foobar', - [otel.modernAttributeName]: 'bazbar', - }, - }; - const span = otel.PubsubSpans.createPublisherSpan( - message, - 'projects/test/topics/topicfoo' - ); + describe('context propagation', () => { + it('injects a trace context and legacy baggage', () => { + const message: PubsubMessage = { + attributes: {}, + }; + const span = otel.PubsubSpans.createPublisherSpan( + message, + 'projects/test/topics/topicfoo' + ); - const warnSpy = sinon.spy(console, 'warn'); - try { otel.injectSpan(span, message, otel.OpenTelemetryLevel.Legacy); - assert.strictEqual(warnSpy.callCount, 2); - } finally { - warnSpy.restore(); - } + + assert.strictEqual( + Object.getOwnPropertyNames(message.attributes).includes( + otel.modernAttributeName + ), + true + ); + assert.strictEqual( + Object.getOwnPropertyNames(message.attributes).includes( + otel.legacyAttributeName + ), + true + ); + }); + + it('should issue a warning if OpenTelemetry span context key is set', () => { + const message: PubsubMessage = { + attributes: { + [otel.legacyAttributeName]: 'foobar', + [otel.modernAttributeName]: 'bazbar', + }, + }; + const span = otel.PubsubSpans.createPublisherSpan( + message, + 'projects/test/topics/topicfoo' + ); + + const warnSpy = sinon.spy(console, 'warn'); + try { + otel.injectSpan(span, message, otel.OpenTelemetryLevel.Legacy); + assert.strictEqual(warnSpy.callCount, 2); + } finally { + warnSpy.restore(); + } + }); + + it('should be able to determine if attributes are present', () => { + let message: otel.MessageWithAttributes = { + attributes: { + [otel.legacyAttributeName]: 'foobar', + }, + }; + assert.strictEqual(otel.containsSpanContext(message), true); + + message = { + attributes: { + [otel.modernAttributeName]: 'foobar', + }, + }; + assert.strictEqual(otel.containsSpanContext(message), true); + + message = {}; + assert.strictEqual(otel.containsSpanContext(message), false); + }); + + it('extracts a trace context', () => { + const message = { + attributes: { + [otel.modernAttributeName]: + '00-d4cda95b652f4a1592b449d5929fda1b-553964cd9101a314-01', + }, + }; + + const childSpan = otel.extractSpan( + message, + 'projects/test/subscriptions/subfoo', + otel.OpenTelemetryLevel.Modern + ); + assert.strictEqual( + childSpan!.spanContext().traceId, + 'd4cda95b652f4a1592b449d5929fda1b' + ); + }); }); - it('should be able to determine if attributes are present', () => { - let message: otel.MessageWithAttributes = { - attributes: { - [otel.legacyAttributeName]: 'foobar', - }, - }; - assert.strictEqual(otel.containsSpanContext(message), true); + describe('attribute creation', () => { + it('creates attributes for publish', () => { + const topicInfo: otel.AttributeParams = { + topicName: 'projects/foo/topics/top', + topicId: 'top', + projectId: 'foo', + }; + const message: PubsubMessage = { + data: Buffer.from('test'), + attributes: {}, + calculatedSize: 1234, + orderingKey: 'key', + isExactlyOnceDelivery: true, + ackId: 'ackack', + }; - message = { - attributes: { - [otel.modernAttributeName]: 'foobar', - }, - }; - assert.strictEqual(otel.containsSpanContext(message), true); + const topicAttrs = otel.PubsubSpans.createAttributes(topicInfo, message); + assert.deepStrictEqual(topicAttrs, { + 'messaging.system': 'gcp_pubsub', + 'messaging.destination.name': topicInfo.topicId, + 'gcp.project_id': topicInfo.projectId, + 'messaging.message.envelope.size': message.calculatedSize, + 'messaging.gcp_pubsub.ordering_key': message.orderingKey, + 'messaging.gcp_pubsub.message.exactly_once_delivery': + message.isExactlyOnceDelivery, + 'messaging.gcp_pubsub.message.ack_id': message.ackId, + }); + + // Check again with no calculated size and other parameters missing. + delete message.calculatedSize; + delete message.orderingKey; + delete message.isExactlyOnceDelivery; + delete message.ackId; - message = {}; - assert.strictEqual(otel.containsSpanContext(message), false); + const topicAttrs2 = otel.PubsubSpans.createAttributes(topicInfo, message); + assert.deepStrictEqual(topicAttrs2, { + 'messaging.system': 'gcp_pubsub', + 'messaging.destination.name': topicInfo.topicId, + 'gcp.project_id': topicInfo.projectId, + 'messaging.message.envelope.size': message.data?.length, + }); + }); }); - it('extracts a trace context', () => { - const message = { - attributes: { - [otel.modernAttributeName]: - '00-d4cda95b652f4a1592b449d5929fda1b-553964cd9101a314-01', - }, + describe('specialized span creation', () => { + const tests = { + topicInfo: { + topicName: 'projects/foo/topics/top', + topicId: 'top', + projectId: 'foo', + } as otel.AttributeParams, + subInfo: { + subName: 'projects/foo/subscriptions/sub', + subId: 'sub', + projectId: 'foo', + } as otel.AttributeParams, + message: { + data: Buffer.from('test'), + attributes: {}, + calculatedSize: 1234, + orderingKey: 'key', + isExactlyOnceDelivery: true, + ackId: 'ackack', + } as PubsubMessage, }; - const childSpan = otel.extractSpan( - message, - 'projects/test/subscriptions/subfoo', - otel.OpenTelemetryLevel.Modern - ); - assert.strictEqual( - childSpan!.spanContext().traceId, - 'd4cda95b652f4a1592b449d5929fda1b' - ); + it('creates publisher spans', () => { + const span = otel.PubsubSpans.createPublisherSpan( + tests.message, + tests.topicInfo.topicName! + ); + span.end(); + + const spans = exporter.getFinishedSpans(); + assert.strictEqual(spans.length, 1); + + const firstSpan = spans.pop(); + assert.ok(firstSpan); + assert.strictEqual(firstSpan.name, `${tests.topicInfo.topicId} create`); + assert.strictEqual( + firstSpan.attributes['messaging.destination.name'], + tests.topicInfo.topicId + ); + assert.strictEqual( + firstSpan.attributes['messaging.system'], + 'gcp_pubsub' + ); + }); + + it('updates publisher topic names', () => { + const span = otel.PubsubSpans.createPublisherSpan( + tests.message, + tests.topicInfo.topicName! + ); + otel.PubsubSpans.updatePublisherTopicName( + span, + 'projects/foo/topics/other' + ); + span.end(); + + const spans = exporter.getFinishedSpans(); + assert.strictEqual(spans.length, 1); + + const firstSpan = spans.pop(); + assert.ok(firstSpan); + assert.strictEqual(firstSpan.name, 'other create'); + + assert.strictEqual( + firstSpan.attributes['messaging.destination.name'], + 'other' + ); + }); + + it('creates receive spans', () => { + const parentSpan = otel.PubsubSpans.createPublisherSpan( + tests.message, + tests.topicInfo.topicName! + ); + const span = otel.PubsubSpans.createReceiveSpan( + tests.message, + tests.subInfo.subName!, + otel.spanContextToContext(parentSpan.spanContext()) + ); + span.end(); + parentSpan.end(); + + const spans = exporter.getFinishedSpans(); + const parentReadSpan = spans.pop(); + const childReadSpan = spans.pop(); + assert.ok(parentReadSpan && childReadSpan); + + assert.strictEqual(childReadSpan.name, 'sub subscribe'); + assert.strictEqual( + childReadSpan.attributes['messaging.destination.name'], + 'sub' + ); + assert.strictEqual(childReadSpan.kind, SpanKind.CONSUMER); + assert.ok(childReadSpan.parentSpanId); + }); }); });