|
2 | 2 |
|
3 | 3 | const { getMessageSize } = require('../../dd-trace/src/datastreams') |
4 | 4 | const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer') |
| 5 | +const SpanContext = require('../../dd-trace/src/opentracing/span_context') |
| 6 | +const id = require('../../dd-trace/src/id') |
5 | 7 |
|
6 | 8 | class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin { |
7 | 9 | static id = 'google-cloud-pubsub' |
8 | 10 | static operation = 'receive' |
9 | 11 |
|
| 12 | + // Reconstruct a SpanContext for the pubsub.request span |
| 13 | + // This creates proper Identifier objects that the encoder can serialize |
| 14 | + _reconstructPubSubRequestContext (attrs) { |
| 15 | + const traceIdLower = attrs['_dd.pubsub_request.trace_id'] |
| 16 | + const spanId = attrs['_dd.pubsub_request.span_id'] |
| 17 | + const traceIdUpper = attrs['_dd.p.tid'] |
| 18 | + |
| 19 | + if (!traceIdLower || !spanId) return null |
| 20 | + |
| 21 | + try { |
| 22 | + const traceId128 = traceIdUpper ? traceIdUpper + traceIdLower : traceIdLower.padStart(32, '0') |
| 23 | + const traceId = id(traceId128, 16) |
| 24 | + const parentId = id(spanId, 16) |
| 25 | + |
| 26 | + const tags = {} |
| 27 | + if (traceIdUpper) tags['_dd.p.tid'] = traceIdUpper |
| 28 | + |
| 29 | + return new SpanContext({ |
| 30 | + traceId, |
| 31 | + spanId: parentId, |
| 32 | + tags |
| 33 | + }) |
| 34 | + } catch { |
| 35 | + return null |
| 36 | + } |
| 37 | + } |
| 38 | + |
10 | 39 | bindStart (ctx) { |
11 | 40 | const { message } = ctx |
12 | 41 | const subscription = message._subscriber._subscription |
13 | | - const topic = subscription.metadata && subscription.metadata.topic |
14 | | - const childOf = this.tracer.extract('text_map', message.attributes) || null |
| 42 | + // Get topic from metadata or message attributes (attributes more reliable for pull subscriptions) |
| 43 | + const topic = (subscription.metadata && subscription.metadata.topic) || |
| 44 | + (message.attributes && message.attributes['pubsub.topic']) || |
| 45 | + (message.attributes && message.attributes['gcloud.project_id'] ? |
| 46 | + `projects/${message.attributes['gcloud.project_id']}/topics/unknown` : null) |
| 47 | + |
| 48 | + // Extract batch metadata from message attributes |
| 49 | + const batchRequestTraceId = message.attributes?.['_dd.pubsub_request.trace_id'] |
| 50 | + const batchRequestSpanId = message.attributes?.['_dd.pubsub_request.span_id'] |
| 51 | + const batchSize = message.attributes?.['_dd.batch.size'] |
| 52 | + const batchIndex = message.attributes?.['_dd.batch.index'] |
| 53 | + |
| 54 | + // Extract the standard context (this gets us the full 128-bit trace ID, sampling priority, etc.) |
| 55 | + let childOf = this.tracer.extract('text_map', message.attributes) || null |
| 56 | + |
| 57 | + // Only reparent to pubsub.request for the FIRST message in the batch (index 0) |
| 58 | + // Messages 2-N are in separate traces and should stay as children of their original parent |
| 59 | + const isFirstMessage = batchIndex === '0' || batchIndex === 0 |
| 60 | + if (isFirstMessage && batchRequestSpanId) { |
| 61 | + // Reconstruct a proper SpanContext for the pubsub.request span |
| 62 | + // This ensures pubsub.receive becomes a child of pubsub.request (not triggerPubsub) |
| 63 | + const pubsubRequestContext = this._reconstructPubSubRequestContext(message.attributes) |
| 64 | + if (pubsubRequestContext) { |
| 65 | + childOf = pubsubRequestContext |
| 66 | + } |
| 67 | + } |
15 | 68 |
|
16 | | - // Create pubsub.delivery span |
| 69 | + // Extract topic name for better resource naming |
| 70 | + const topicName = topic ? topic.split('/').pop() : subscription.name.split('/').pop() |
| 71 | + // Create pubsub.receive span (note: operation name will be 'google-cloud-pubsub.receive') |
| 72 | + // Use a separate service name (like push subscriptions do) for better service map visibility |
| 73 | + const baseService = this.tracer._service || 'unknown' |
| 74 | + const serviceName = this.config.service || `${baseService}-pubsub` |
| 75 | + |
| 76 | + // Build meta object with batch metadata if available |
| 77 | + const meta = { |
| 78 | + 'gcloud.project_id': subscription.pubsub.projectId, |
| 79 | + 'pubsub.topic': topic, |
| 80 | + 'span.kind': 'consumer', |
| 81 | + 'pubsub.delivery_method': 'pull', |
| 82 | + 'pubsub.span_type': 'message_processing', // Easy filtering in Datadog |
| 83 | + 'messaging.operation': 'receive' // Standard tag |
| 84 | + } |
| 85 | + |
| 86 | + // Add batch metadata tags for correlation |
| 87 | + if (batchRequestTraceId) { |
| 88 | + meta['pubsub.batch.request_trace_id'] = batchRequestTraceId |
| 89 | + } |
| 90 | + if (batchRequestSpanId) { |
| 91 | + meta['pubsub.batch.request_span_id'] = batchRequestSpanId |
| 92 | + // Also add span link metadata |
| 93 | + meta['_dd.pubsub_request.trace_id'] = batchRequestTraceId |
| 94 | + meta['_dd.pubsub_request.span_id'] = batchRequestSpanId |
| 95 | + if (batchRequestTraceId && batchRequestSpanId) { |
| 96 | + meta['_dd.span_links'] = `${batchRequestTraceId}:${batchRequestSpanId}` |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + const metrics = { |
| 101 | + 'pubsub.ack': 0 |
| 102 | + } |
| 103 | + |
| 104 | + // Add batch size and index if available |
| 105 | + if (batchSize) { |
| 106 | + metrics['pubsub.batch.message_count'] = Number.parseInt(batchSize, 10) |
| 107 | + metrics['pubsub.batch.size'] = Number.parseInt(batchSize, 10) |
| 108 | + } |
| 109 | + if (batchIndex !== undefined) { |
| 110 | + metrics['pubsub.batch.message_index'] = Number.parseInt(batchIndex, 10) |
| 111 | + metrics['pubsub.batch.index'] = Number.parseInt(batchIndex, 10) |
| 112 | + } |
| 113 | + |
| 114 | + // Add batch description |
| 115 | + if (batchSize && batchIndex !== undefined) { |
| 116 | + const index = Number.parseInt(batchIndex, 10) |
| 117 | + const size = Number.parseInt(batchSize, 10) |
| 118 | + meta['pubsub.batch.description'] = `Message ${index + 1} of ${size}` |
| 119 | + } |
| 120 | + |
17 | 121 | const span = this.startSpan({ |
18 | 122 | childOf, |
19 | | - resource: topic, |
| 123 | + resource: `Message from ${topicName}`, // More descriptive resource name |
20 | 124 | type: 'worker', |
21 | | - meta: { |
22 | | - 'gcloud.project_id': subscription.pubsub.projectId, |
23 | | - 'pubsub.topic': topic, |
24 | | - 'span.kind': 'consumer', |
25 | | - operation: 'pubsub.delivery' |
26 | | - }, |
27 | | - metrics: { |
28 | | - 'pubsub.ack': 0 |
29 | | - } |
30 | | - }, ctx) |
| 125 | + service: serviceName, // Use integration-specific service name |
| 126 | + meta, |
| 127 | + metrics |
| 128 | + }, ctx) |
31 | 129 |
|
32 | 130 | // Add message metadata |
33 | 131 | if (message.id) { |
@@ -85,7 +183,6 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin { |
85 | 183 | } |
86 | 184 |
|
87 | 185 | super.finish() |
88 | | - |
89 | 186 | return ctx.parentStore |
90 | 187 | } |
91 | 188 | } |
|
0 commit comments