Skip to content

Commit 5dbee1d

Browse files
jcstorms1juan-fernandez
authored andcommitted
[APMSVLS-218] Expand support for Azure Service Bus (#6669)
* initial * get batching set up * start tests * finalize batching and tests * update serverless.yml * refactor and fix tests * try restarting sqledge too * update steps for serverless.yml * Fix path in workflow and address comments * rename proto variables * update codeowners for serverless.yml * update core tools in serverless.yml
1 parent b12f8fe commit 5dbee1d

File tree

13 files changed

+820
-85
lines changed

13 files changed

+820
-85
lines changed

.github/workflows/serverless.yml

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ jobs:
160160
BLOB_SERVER: azurite
161161
METADATA_SERVER: azurite
162162
ACCEPT_EULA: "Y"
163+
azuresqledge:
164+
image: mcr.microsoft.com/azure-sql-edge:1.0.7
165+
ports:
166+
- "127.0.0.1:1433:1433"
167+
env:
168+
ACCEPT_EULA: "Y"
169+
MSSQL_SA_PASSWORD: "Localtestpass1!"
163170
azureservicebusemulator:
164171
image: mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2
165172
ports:
@@ -169,27 +176,28 @@ jobs:
169176
ACCEPT_EULA: "Y"
170177
MSSQL_SA_PASSWORD: "Localtestpass1!"
171178
SQL_SERVER: azuresqledge
172-
azuresqledge:
173-
image: mcr.microsoft.com/azure-sql-edge:1.0.7
174-
ports:
175-
- "127.0.0.1:1433:1433"
176-
env:
177-
ACCEPT_EULA: "Y"
178-
MSSQL_SA_PASSWORD: "Localtestpass1!"
179179
env:
180180
PLUGINS: azure-functions
181-
SERVICES: azureservicebusemulator,azuresqledge,azurite,azureeventhubsemulator
181+
SERVICES: azuresqledge,azureservicebusemulator,azurite,azureeventhubsemulator
182182
steps:
183183
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
184-
- run: |
184+
185+
- name: Copy emulator config files
186+
run: |
185187
docker cp \
186188
${{ github.workspace }}/packages/datadog-plugin-azure-functions/test/fixtures/eventhub-emulator-config.json \
187189
${{ job.services.azureeventhubsemulator.id }}:/Eventhubs_Emulator/ConfigFiles/Config.json
188-
- run : |
190+
191+
docker cp \
192+
${{ github.workspace }}/packages/datadog-plugin-azure-functions/test/fixtures/servicebus-emulator-config.json \
193+
${{ job.services.azureservicebusemulator.id }}:/ServiceBus_Emulator/ConfigFiles/Config.json
194+
- name: Restart emulators to pick up config changes
195+
run : |
189196
docker restart ${{ job.services.azureeventhubsemulator.id }}
197+
docker restart ${{ job.services.azureservicebusemulator.id }}
190198
- uses: ./.github/actions/node/newest-maintenance-lts
191199
- uses: ./.github/actions/install
192-
- run: npm install -g azure-functions-core-tools@4.1.0
200+
- run: npm install -g azure-functions-core-tools@4.3.0
193201
- run: echo "$(dirname $(which func))" >> $GITHUB_PATH
194202
- run: yarn test:plugins:ci
195203
- uses: DataDog/junit-upload-github-action@762867566348d59ac9bcf479ebb4ec040db8940a # v2.0.0

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
/.github/workflows/appsec.yml @DataDog/asm-js
105105
/.github/workflows/codeql-analysis.yml @DataDog/dd-trace-js
106106
/.github/workflows/debugger.yml @DataDog/debugger
107-
/.github/workflows/serverless.yml @DataDog/serverless-aws
107+
/.github/workflows/serverless.yml @DataDog/serverless-aws @DataDog/apm-serverless
108108
/.github/workflows/llmobs.yml @DataDog/ml-observability
109109
/.github/workflows/platform.yml @DataDog/dd-trace-js
110110
/.github/workflows/pr-labels.yml @DataDog/dd-trace-js

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ services:
2222
ACCEPT_EULA: "Y"
2323
azureservicebusemulator:
2424
image: mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2
25+
volumes:
26+
- "./packages/datadog-plugin-azure-functions/test/fixtures/servicebus-emulator-config.json:/ServiceBus_Emulator/ConfigFiles/Config.json"
2527
ports:
2628
- "127.0.0.1:5672:5672"
2729
- "127.0.0.1:5300:5300"
Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,65 @@
11
'use strict'
22

33
const {
4-
channel,
54
addHook
65
} = require('./helpers/instrument')
76

87
const shimmer = require('../../datadog-shimmer')
8+
const dc = require('dc-polyfill')
99

10-
const producerStartCh = channel('apm:azure-service-bus:send:start')
11-
const producerErrorCh = channel('apm:azure-service-bus:send:error')
12-
const producerFinishCh = channel('apm:azure-service-bus:send:finish')
10+
const producerCh = dc.tracingChannel('apm:azure-service-bus:send')
1311

1412
addHook({ name: '@azure/service-bus', versions: ['>=7.9.2'], patchDefault: false }, (obj) => {
1513
const ServiceBusClient = obj.ServiceBusClient
16-
shimmer.wrap(ServiceBusClient.prototype, 'createSender', createSender => function (queueOrTopicName) {
17-
const sender = createSender.apply(this, arguments)
18-
shimmer.wrap(sender._sender, 'send', send => function (msg) {
19-
const ctx = { sender, msg }
20-
return producerStartCh.runStores(ctx, () => {
21-
return send.apply(this, arguments)
22-
.then(
23-
response => {
24-
producerFinishCh.publish(ctx)
25-
},
26-
error => {
27-
ctx.error = error
28-
producerErrorCh.publish(ctx)
29-
producerFinishCh.publish(ctx)
30-
throw error
31-
}
14+
let didItShim = false
15+
shimmer.wrap(ServiceBusClient.prototype, 'createSender',
16+
createSender => function (queueOrTopicName) {
17+
const sender = createSender.apply(this, arguments)
18+
if (didItShim) return sender
19+
const senderPrototype = sender.constructor.prototype
20+
const senderSenderPrototype = sender._sender.constructor.prototype
21+
shimmer.wrap(senderPrototype, 'scheduleMessages', scheduleMessages =>
22+
function (msg, scheduledEnqueueTimeUtc) {
23+
const functionName = scheduleMessages.name
24+
const config = this._context.config
25+
const entityPath = this._entityPath
26+
return producerCh.tracePromise(
27+
scheduleMessages,
28+
{ config, entityPath, functionName, msg, scheduledEnqueueTimeUtc },
29+
this, ...arguments
3230
)
31+
})
32+
33+
shimmer.wrap(senderPrototype, 'createMessageBatch', createMessageBatch => async function () {
34+
const batch = await createMessageBatch.apply(this, arguments)
35+
shimmer.wrap(batch.constructor.prototype, 'tryAddMessage', tryAddMessage => function (msg) {
36+
const functionName = tryAddMessage.name
37+
const config = this._context.config
38+
return producerCh.tracePromise(
39+
tryAddMessage, { config, functionName, batch, msg }, this, ...arguments)
40+
})
41+
return batch
42+
})
43+
44+
shimmer.wrap(senderSenderPrototype, 'send', send => function (msg) {
45+
const functionName = send.name
46+
const config = this._context.config
47+
const entityPath = this.entityPath
48+
return producerCh.tracePromise(
49+
send, { config, entityPath, functionName, msg }, this, ...arguments
50+
)
51+
})
52+
53+
shimmer.wrap(senderSenderPrototype, 'sendBatch', sendBatch => function (msg) {
54+
const functionName = sendBatch.name
55+
const config = this._context.config
56+
const entityPath = this.entityPath
57+
return producerCh.tracePromise(
58+
sendBatch, { config, entityPath, functionName, msg }, this, ...arguments
59+
)
3360
})
61+
didItShim = true
62+
return sender
3463
})
35-
return sender
36-
})
3764
return obj
3865
})

packages/datadog-plugin-azure-functions/src/index.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ class AzureFunctionsPlugin extends TracingPlugin {
2424
static prefix = 'tracing:datadog:azure:functions:invoke'
2525

2626
bindStart (ctx) {
27-
const childOf = extractTraceContext(this._tracer, ctx)
2827
const meta = getMetaForTrigger(ctx)
2928
const triggerType = triggerMap[ctx.methodName]
3029
const isMessagingService = (triggerType === 'ServiceBus' || triggerType === 'EventHubs')
30+
const childOf = isMessagingService ? null : extractTraceContext(this._tracer, ctx)
3131
const span = this.startSpan(this.operationName(), {
3232
childOf,
3333
service: this.serviceName(),
@@ -36,7 +36,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
3636
}, ctx)
3737

3838
if (isMessagingService) {
39-
setSpanLinks(this.tracer, span, ctx)
39+
setSpanLinks(triggerType, this.tracer, span, ctx)
4040
}
4141

4242
ctx.span = span
@@ -116,29 +116,39 @@ function extractTraceContext (tracer, ctx) {
116116
switch (String(triggerMap[ctx.methodName])) {
117117
case 'Http':
118118
return tracer.extract('http_headers', Object.fromEntries(ctx.httpRequest.headers))
119-
case 'ServiceBus':
120-
return tracer.extract('text_map', ctx.invocationContext.triggerMetadata.applicationProperties)
121119
default:
122120
null
123121
}
124122
}
125123

126-
function setSpanLinks (tracer, span, ctx) {
124+
// message & messages & batch with cardinality of 1 == applicationProperties
125+
// messages with cardinality of many == applicationPropertiesArray
126+
function setSpanLinks (triggerType, tracer, span, ctx) {
127127
const cardinality = ctx.invocationContext.options.trigger.cardinality
128128
const triggerMetadata = ctx.invocationContext.triggerMetadata
129-
if (cardinality === 'many' && triggerMetadata.propertiesArray.length > 0) {
130-
triggerMetadata.propertiesArray.forEach(event => {
131-
// Check for possible empty event when span links are disabled
132-
if (Object.keys(event).length > 0) {
133-
span.addLink(tracer.extract('text_map', event))
134-
}
135-
})
136-
} else if (cardinality === 'one') {
137-
const spanContext = tracer.extract('text_map', triggerMetadata.properties)
129+
const isServiceBus = triggerType === 'ServiceBus'
130+
131+
const properties = isServiceBus
132+
? triggerMetadata.applicationProperties
133+
: triggerMetadata.properties
134+
135+
const propertiesArray = isServiceBus
136+
? triggerMetadata.applicationPropertiesArray
137+
: triggerMetadata.propertiesArray
138+
139+
const addLinkFromProperties = (props) => {
140+
if (!props || Object.keys(props).length === 0) return
141+
const spanContext = tracer.extract('text_map', props)
138142
if (spanContext) {
139143
span.addLink(spanContext)
140144
}
141145
}
146+
147+
if (cardinality === 'many' && propertiesArray?.length > 0) {
148+
propertiesArray.forEach(addLinkFromProperties)
149+
} else if (cardinality === 'one') {
150+
addLinkFromProperties(properties)
151+
}
142152
}
143153

144154
module.exports = AzureFunctionsPlugin
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"UserConfig": {
3+
"Namespaces": [
4+
{
5+
"Name": "sbemulatorns",
6+
"Queues": [
7+
{
8+
"Name": "queue.1",
9+
"Properties": {
10+
"DeadLetteringOnMessageExpiration": false,
11+
"DefaultMessageTimeToLive": "PT1H",
12+
"DuplicateDetectionHistoryTimeWindow": "PT20S",
13+
"ForwardDeadLetteredMessagesTo": "",
14+
"ForwardTo": "",
15+
"LockDuration": "PT1M",
16+
"MaxDeliveryCount": 3,
17+
"RequiresDuplicateDetection": false,
18+
"RequiresSession": false
19+
}
20+
},
21+
{
22+
"Name": "queue.2",
23+
"Properties": {
24+
"DeadLetteringOnMessageExpiration": false,
25+
"DefaultMessageTimeToLive": "PT1H",
26+
"DuplicateDetectionHistoryTimeWindow": "PT20S",
27+
"ForwardDeadLetteredMessagesTo": "",
28+
"ForwardTo": "",
29+
"LockDuration": "PT1M",
30+
"MaxDeliveryCount": 3,
31+
"RequiresDuplicateDetection": false,
32+
"RequiresSession": false
33+
}
34+
}
35+
],
36+
37+
"Topics": [
38+
{
39+
"Name": "topic.1",
40+
"Properties": {
41+
"DefaultMessageTimeToLive": "PT1H",
42+
"DuplicateDetectionHistoryTimeWindow": "PT20S",
43+
"RequiresDuplicateDetection": false
44+
},
45+
"Subscriptions": [
46+
{
47+
"Name": "subscription.1",
48+
"Properties": {
49+
"DeadLetteringOnMessageExpiration": false,
50+
"DefaultMessageTimeToLive": "PT1H",
51+
"LockDuration": "PT1M",
52+
"MaxDeliveryCount": 3,
53+
"ForwardDeadLetteredMessagesTo": "",
54+
"ForwardTo": "",
55+
"RequiresSession": false
56+
}
57+
}
58+
]
59+
},
60+
{
61+
"Name": "topic.2",
62+
"Properties": {
63+
"DefaultMessageTimeToLive": "PT1H",
64+
"DuplicateDetectionHistoryTimeWindow": "PT20S",
65+
"RequiresDuplicateDetection": false
66+
},
67+
"Subscriptions": [
68+
{
69+
"Name": "subscription.2",
70+
"Properties": {
71+
"DeadLetteringOnMessageExpiration": false,
72+
"DefaultMessageTimeToLive": "PT1H",
73+
"LockDuration": "PT1M",
74+
"MaxDeliveryCount": 3,
75+
"ForwardDeadLetteredMessagesTo": "",
76+
"ForwardTo": "",
77+
"RequiresSession": false
78+
}
79+
}
80+
]
81+
}
82+
]
83+
}
84+
],
85+
"Logging": {
86+
"Type": "File"
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)