-
Notifications
You must be signed in to change notification settings - Fork 357
[SVLS-7168] Create inferred Span and Span links for GCP PubSub #6415
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
base: nina.rei/SVLS-7168/gcp-pubsub-batch-plugin
Are you sure you want to change the base?
Changes from all commits
306d6cd
083e5aa
7c8cda6
10eb97c
7172f88
1394f6c
9a40f0b
a67bc15
9322651
44ea80c
50cd907
5cdaa94
c2edec8
0371fac
065ad3e
7505ad0
6fdb1c8
cc98bec
6697655
3dd7fec
03d725d
5002a29
b1c71d4
9fc3bd8
81e643a
615d2c8
db9e235
4d44ec0
488bfc2
a440473
4609bcd
0ba390e
8b41a6b
de1d319
6f6820a
1f3b25d
47cb8f6
fb73e20
5912702
a8338c3
f2fa333
6e10d50
c9fc172
82085be
19962dd
a1fd1eb
c9ad616
25afa6b
83796c6
2513640
ed3cc6d
725d47d
00a08ce
87829c4
3021780
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,30 +2,161 @@ | |
|
|
||
| const { getMessageSize } = require('../../dd-trace/src/datastreams') | ||
| const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer') | ||
| const SpanContext = require('../../dd-trace/src/opentracing/span_context') | ||
| const id = require('../../dd-trace/src/id') | ||
|
|
||
| class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin { | ||
| static id = 'google-cloud-pubsub' | ||
| static operation = 'receive' | ||
|
|
||
| _reconstructPubSubRequestContext (attrs) { | ||
| const traceIdLower = attrs['_dd.pubsub_request.trace_id'] | ||
| const spanId = attrs['_dd.pubsub_request.span_id'] | ||
| const traceIdUpper = attrs['_dd.p.tid'] | ||
|
|
||
| if (!traceIdLower || !spanId) return null | ||
|
|
||
| try { | ||
| const traceId128 = traceIdUpper ? traceIdUpper + traceIdLower : traceIdLower.padStart(32, '0') | ||
| const traceId = id(traceId128, 16) | ||
| const parentId = id(spanId, 16) | ||
|
|
||
| const tags = {} | ||
| if (traceIdUpper) tags['_dd.p.tid'] = traceIdUpper | ||
|
|
||
| return new SpanContext({ | ||
| traceId, | ||
| spanId: parentId, | ||
| tags | ||
| }) | ||
| } catch { | ||
| return null | ||
| } | ||
| } | ||
|
|
||
| bindStart (ctx) { | ||
| const { message } = ctx | ||
| const subscription = message._subscriber._subscription | ||
| const topic = subscription.metadata && subscription.metadata.topic | ||
| const childOf = this.tracer.extract('text_map', message.attributes) || null | ||
| const topic = (subscription.metadata && subscription.metadata.topic) || | ||
| (message.attributes && message.attributes['pubsub.topic']) || | ||
| (message.attributes && message.attributes['gcloud.project_id'] | ||
| ? `projects/${message.attributes['gcloud.project_id']}/topics/unknown` | ||
| : null) | ||
|
|
||
| const batchRequestTraceId = message.attributes?.['_dd.pubsub_request.trace_id'] | ||
| const batchRequestSpanId = message.attributes?.['_dd.pubsub_request.span_id'] | ||
| const batchSize = message.attributes?.['_dd.batch.size'] | ||
| const batchIndex = message.attributes?.['_dd.batch.index'] | ||
|
|
||
| let childOf = this.tracer.extract('text_map', message.attributes) || null | ||
|
|
||
| const isFirstMessage = batchIndex === '0' || batchIndex === 0 | ||
| if (isFirstMessage && batchRequestSpanId) { | ||
| const pubsubRequestContext = this._reconstructPubSubRequestContext(message.attributes) | ||
| if (pubsubRequestContext) { | ||
| childOf = pubsubRequestContext | ||
| } | ||
| } | ||
|
|
||
| const topicName = topic ? topic.split('/').pop() : subscription.name.split('/').pop() | ||
| const baseService = this.tracer._service || 'unknown' | ||
| const serviceName = this.config.service || `${baseService}-pubsub` | ||
| const meta = { | ||
| 'gcloud.project_id': subscription.pubsub.projectId, | ||
| 'pubsub.topic': topic, | ||
| 'span.kind': 'consumer', | ||
| 'pubsub.delivery_method': 'pull', | ||
| 'pubsub.span_type': 'message_processing', | ||
| 'messaging.operation': 'receive', | ||
| '_dd.base_service': this.tracer._service, | ||
| '_dd.serviceoverride.type': 'custom' | ||
| } | ||
|
|
||
| if (batchRequestTraceId) { | ||
| meta['pubsub.batch.request_trace_id'] = batchRequestTraceId | ||
| } | ||
| if (batchRequestSpanId) { | ||
| meta['pubsub.batch.request_span_id'] = batchRequestSpanId | ||
| // Also add span link metadata | ||
| meta['_dd.pubsub_request.trace_id'] = batchRequestTraceId | ||
| meta['_dd.pubsub_request.span_id'] = batchRequestSpanId | ||
| if (batchRequestTraceId && batchRequestSpanId) { | ||
| // Use JSON format like producer for proper span link parsing | ||
| meta['_dd.span_links'] = JSON.stringify([{ | ||
| trace_id: batchRequestTraceId, | ||
| span_id: batchRequestSpanId, | ||
| flags: 0 | ||
| }]) | ||
| } | ||
| } | ||
|
|
||
| const metrics = { | ||
| 'pubsub.ack': 0 | ||
| } | ||
|
|
||
| if (batchSize) { | ||
| metrics['pubsub.batch.message_count'] = Number.parseInt(batchSize, 10) | ||
| metrics['pubsub.batch.size'] = Number.parseInt(batchSize, 10) | ||
| } | ||
| if (batchIndex !== undefined) { | ||
| metrics['pubsub.batch.message_index'] = Number.parseInt(batchIndex, 10) | ||
| metrics['pubsub.batch.index'] = Number.parseInt(batchIndex, 10) | ||
| } | ||
|
|
||
| // Add batch description | ||
| if (batchSize && batchIndex !== undefined) { | ||
| const index = Number.parseInt(batchIndex, 10) | ||
| const size = Number.parseInt(batchSize, 10) | ||
| meta['pubsub.batch.description'] = `Message ${index + 1} of ${size}` | ||
| } | ||
|
|
||
| const span = this.startSpan({ | ||
| childOf, | ||
| resource: topic, | ||
| resource: `Message from ${topicName}`, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like in the previous PR this one also looks like a breaking change.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There shouldn't be any users for this currently, but I can always revert to what is in prod. I wanted to make the spans more descriptive for the blog post and product release. |
||
| type: 'worker', | ||
| meta: { | ||
| 'gcloud.project_id': subscription.pubsub.projectId, | ||
| 'pubsub.topic': topic | ||
| }, | ||
| metrics: { | ||
| 'pubsub.ack': 0 | ||
| } | ||
| service: serviceName, | ||
| meta, | ||
| metrics | ||
| }, ctx) | ||
|
|
||
| if (message.id) { | ||
| span.setTag('pubsub.message_id', message.id) | ||
| } | ||
| if (message.publishTime) { | ||
| span.setTag('pubsub.publish_time', message.publishTime.toISOString()) | ||
| } | ||
|
|
||
| if (message.attributes) { | ||
| const publishStartTime = message.attributes['x-dd-publish-start-time'] | ||
| if (publishStartTime) { | ||
| const deliveryDuration = Date.now() - Number.parseInt(publishStartTime, 10) | ||
| span.setTag('pubsub.delivery_duration_ms', deliveryDuration) | ||
| } | ||
|
|
||
| const pubsubRequestTraceId = message.attributes['_dd.pubsub_request.trace_id'] | ||
| const pubsubRequestSpanId = message.attributes['_dd.pubsub_request.span_id'] | ||
| const batchSize = message.attributes['_dd.batch.size'] | ||
| const batchIndex = message.attributes['_dd.batch.index'] | ||
|
|
||
| if (pubsubRequestTraceId && pubsubRequestSpanId) { | ||
| span.setTag('_dd.pubsub_request.trace_id', pubsubRequestTraceId) | ||
| span.setTag('_dd.pubsub_request.span_id', pubsubRequestSpanId) | ||
| // Use JSON format like producer for proper span link parsing | ||
| span.setTag('_dd.span_links', JSON.stringify([{ | ||
| trace_id: pubsubRequestTraceId, | ||
| span_id: pubsubRequestSpanId, | ||
| flags: 0 | ||
| }])) | ||
| } | ||
|
|
||
| if (batchSize) { | ||
| span.setTag('pubsub.batch.size', Number.parseInt(batchSize, 10)) | ||
| } | ||
| if (batchIndex) { | ||
| span.setTag('pubsub.batch.index', Number.parseInt(batchIndex, 10)) | ||
| } | ||
| } | ||
|
|
||
| if (this.config.dsmEnabled && message?.attributes) { | ||
| const payloadSize = getMessageSize(message) | ||
| this.tracer.decodeDataStreamsContext(message.attributes) | ||
|
|
@@ -38,14 +169,15 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin { | |
|
|
||
| bindFinish (ctx) { | ||
| const { message } = ctx | ||
| const span = ctx.currentStore.span | ||
| const span = ctx.currentStore?.span | ||
|
|
||
| if (!span) return ctx.parentStore | ||
|
|
||
| if (message?._handled) { | ||
| span.setTag('pubsub.ack', 1) | ||
| } | ||
|
|
||
| super.finish() | ||
|
|
||
| return ctx.parentStore | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you find that sometimes
obj.Topic.prototype.publishwas a truthy value but wasn't a function?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I have not found evidence that
obj.Topic.prototype.publishis ever a truthy non-function value in practice.The
typeof obj.Topic.prototype.publish === 'function'check is following the pattern that's used throughout the dd-trace-js codebase for instrumentation you can see other examples in the same file that i did not add myself