77
88const defaultLogger = require ( '../../logger' ) . child ( { component : 'azure-functions' } )
99const urltils = require ( '../../util/urltils' )
10- const recordWeb = require ( '../../metrics/recorders/http' )
1110const headerProcessing = require ( '../../header-processing' )
1211const synthetics = require ( '../../synthetics' )
1312
14- const DESTS = require ( '../../config/attribute-filter' ) . DESTINATIONS
13+ const backgroundRecorder = require ( '../../metrics/recorders/other.js' )
14+ const recordWeb = require ( '../../metrics/recorders/http' )
15+
16+ const {
17+ DESTINATIONS : DESTS ,
18+ TYPES
19+ } = require ( '../../transaction/index.js' )
1520
1621const {
1722 WEBSITE_OWNER_NAME ,
@@ -23,8 +28,12 @@ const RESOURCE_GROUP_NAME = WEBSITE_RESOURCE_GROUP ?? WEBSITE_OWNER_NAME?.split(
2328const AZURE_FUNCTION_APP_NAME = WEBSITE_SITE_NAME
2429
2530let coldStart = true
31+ let _agent
32+ let _logger
2633
2734module . exports = function initialize ( agent , azureFunctions , _moduleName , shim , { logger = defaultLogger } = { } ) {
35+ _agent = agent
36+ _logger = logger
2837 if ( ! SUBSCRIPTION_ID || ! RESOURCE_GROUP_NAME || ! AZURE_FUNCTION_APP_NAME ) {
2938 logger . warn (
3039 {
@@ -38,98 +47,163 @@ module.exports = function initialize(agent, azureFunctions, _moduleName, shim, {
3847 return
3948 }
4049
41- const methods = [ 'http' , 'get' , 'put' , 'post' , 'patch' , 'deleteRequest' ]
42- shim . wrap ( azureFunctions . app , methods , function wrapAzureHttpMethods ( shim , appMethod ) {
43- return async function wrappedAzureHttpMethod ( ...args ) {
44- // If the app doesn't need an options object, the user can pass the
45- // handler function as the second argument
46- // (e.g. `app.get('name', handler)`).
47- // See https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#registering-a-function
48- let handler
49- if ( typeof args [ 1 ] === 'function' ) {
50- handler = args [ 1 ]
51- args [ 1 ] = { handler }
50+ const httpMethods = [ 'http' , 'get' , 'put' , 'post' , 'patch' , 'deleteRequest' ]
51+ shim . wrap ( azureFunctions . app , httpMethods , wrapAzureHttpMethods )
52+
53+ const backgroundMethods = [
54+ 'cosmosDB' ,
55+ 'eventGrid' ,
56+ 'eventHub' ,
57+ 'mySql' ,
58+ 'serviceBusQueue' ,
59+ 'serviceBusTopic' ,
60+ 'sql' ,
61+ 'storageBlob' ,
62+ 'storageQueue' ,
63+ 'timer' ,
64+ 'warmup' ,
65+ 'webPubSub'
66+ ]
67+ shim . wrap ( azureFunctions . app , backgroundMethods , wrapAzureBackgroundMethods )
68+ }
69+
70+ function wrapAzureHttpMethods ( shim , appMethod ) {
71+ return async function wrappedAzureHttpMethod ( ...args ) {
72+ // If the app doesn't need an options object, the user can pass the
73+ // handler function as the second argument
74+ // (e.g. `app.get('name', handler)`).
75+ // See https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#registering-a-function
76+ let handler
77+ if ( typeof args [ 1 ] === 'function' ) {
78+ handler = args [ 1 ]
79+ args [ 1 ] = { handler }
80+ } else {
81+ handler = args [ 1 ] . handler
82+ }
83+
84+ const tracer = shim . tracer
85+ args [ 1 ] . handler = tracer . transactionProxy ( async function wrappedHandler ( ...args ) {
86+ const [ request , context ] = args
87+ const ctx = tracer . getContext ( )
88+ const tx = tracer . getTransaction ( )
89+
90+ // Set the transaction name according to our spec (category + function name).
91+ tx . setPartialName ( `AzureFunction/${ context . functionName } ` )
92+
93+ const url = new URL ( request . url )
94+ const segment = tracer . createSegment ( {
95+ name : request . url ,
96+ recorder : recordWeb ,
97+ parent : ctx . segment ,
98+ transaction : tx
99+ } )
100+ segment . start ( )
101+
102+ const transport = url . protocol === 'https:' ? 'HTTPS' : 'HTTP'
103+ tx . type = TYPES . WEB
104+ tx . baseSegment = segment
105+ tx . parsedUrl = url
106+ tx . url = urltils . obfuscatePath ( _agent . config , url . pathname )
107+ tx . verb = request . method
108+ if ( url . port === '' ) {
109+ tx . port = transport === 'HTTPS' ? '443' : '80'
52110 } else {
53- handler = args [ 1 ] . handler
111+ tx . port = url . port
112+ }
113+
114+ tx . trace . attributes . addAttribute (
115+ DESTS . TRANS_EVENT | DESTS . ERROR_EVENT ,
116+ 'request.uri' ,
117+ tx . url
118+ )
119+ segment . addSpanAttribute ( 'request.uri' , tx . url )
120+ if ( request . method != null ) {
121+ segment . addSpanAttribute ( 'request.method' , request . method )
54122 }
123+ addAttributes ( { transaction : tx , functionContext : context } )
55124
56- const tracer = shim . tracer
57- args [ 1 ] . handler = tracer . transactionProxy ( async function wrappedHandler ( ...args ) {
58- const [ request , context ] = args
59- const ctx = tracer . getContext ( )
60- const tx = tracer . getTransaction ( )
61-
62- // Set the transaction name according to our spec (category + function name).
63- tx . setPartialName ( `AzureFunction/${ context . functionName } ` )
64-
65- const url = new URL ( request . url )
66- const segment = tracer . createSegment ( {
67- name : request . url ,
68- recorder : recordWeb ,
69- parent : ctx . segment ,
70- transaction : tx
71- } )
72- segment . start ( )
73-
74- const transport = url . protocol === 'https:' ? 'HTTPS' : 'HTTP'
75- tx . type = 'web'
76- tx . baseSegment = segment
77- tx . parsedUrl = url
78- tx . url = urltils . obfuscatePath ( agent . config , url . pathname )
79- tx . verb = request . method
80- if ( url . port === '' ) {
81- tx . port = transport === 'HTTPS' ? '443' : '80'
82- } else {
83- tx . port = url . port
84- }
125+ const queueTimeStamp = headerProcessing . getQueueTime ( _logger , request . headers )
126+ if ( queueTimeStamp ) {
127+ tx . queueTime = Date . now ( ) - queueTimeStamp
128+ }
85129
130+ synthetics . assignHeadersToTransaction ( _agent . config , tx , request . headers )
131+ if ( _agent . config . distributed_tracing . enabled === true ) {
132+ tx . acceptDistributedTraceHeaders ( transport , request . headers )
133+ }
134+
135+ const newContext = ctx . enterSegment ( { segment } )
136+ const boundHandler = tracer . bindFunction ( handler , newContext )
137+
138+ const result = await boundHandler ( ...args )
139+ // Responses should have a shape as described at:
140+ // https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#http-response
141+ if ( result . status ) {
86142 tx . trace . attributes . addAttribute (
87- DESTS . TRANS_EVENT | DESTS . ERROR_EVENT ,
88- 'request.uri ' ,
89- tx . url
143+ DESTS . TRANS_COMMON ,
144+ 'http.statusCode ' ,
145+ result . status
90146 )
91- segment . addSpanAttribute ( 'request.uri' , tx . url )
92- if ( request . method != null ) {
93- segment . addSpanAttribute ( 'request.method' , request . method )
94- }
95- addAttributes ( { transaction : tx , functionContext : context } )
96-
97- const queueTimeStamp = headerProcessing . getQueueTime ( logger , request . headers )
98- if ( queueTimeStamp ) {
99- tx . queueTime = Date . now ( ) - queueTimeStamp
100- }
101-
102- synthetics . assignHeadersToTransaction ( agent . config , tx , request . headers )
103- if ( agent . config . distributed_tracing . enabled === true ) {
104- tx . acceptDistributedTraceHeaders ( transport , request . headers )
105- }
106-
107- const newContext = ctx . enterSegment ( { segment } )
108- const boundHandler = tracer . bindFunction ( handler , newContext )
109-
110- const result = await boundHandler ( ...args )
111- // Responses should have a shape as described at:
112- // https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#http-response
113- if ( result . status ) {
114- tx . trace . attributes . addAttribute (
115- DESTS . TRANS_COMMON ,
116- 'http.statusCode' ,
117- result . status
118- )
119- }
120-
121- if ( coldStart === true ) {
122- tx . trace . attributes . addAttribute ( DESTS . TRANS_COMMON , 'faas.coldStart' , true )
123- coldStart = false
124- }
125-
126- tx . end ( )
127- return result
128- } )
147+ }
148+
149+ if ( coldStart === true ) {
150+ tx . trace . attributes . addAttribute ( DESTS . TRANS_COMMON , 'faas.coldStart' , true )
151+ coldStart = false
152+ }
153+
154+ tx . end ( )
155+ return result
156+ } )
157+
158+ await appMethod ( ...args )
159+ }
160+ }
129161
130- await appMethod ( ...args )
162+ function wrapAzureBackgroundMethods ( shim , appMethod ) {
163+ return async function wrappedAzureBackgroundMethod ( ...args ) {
164+ let handler
165+ if ( typeof args [ 1 ] === 'function' ) {
166+ handler = args [ 1 ]
167+ args [ 1 ] = { handler }
168+ } else {
169+ handler = args [ 1 ] . handler
131170 }
132- } )
171+
172+ const tracer = shim . tracer
173+ args [ 1 ] . handler = tracer . transactionProxy ( async function wrappedHandler ( ...args ) {
174+ const [ , context ] = args
175+ const ctx = tracer . getContext ( )
176+ const tx = tracer . getTransaction ( )
177+
178+ tx . setPartialName ( `AzureFunction/${ context . functionName } ` )
179+
180+ const segment = tracer . createSegment ( {
181+ name : `${ appMethod } -trigger` ,
182+ recorder : backgroundRecorder ,
183+ parent : ctx . segment ,
184+ transaction : tx
185+ } )
186+ segment . start ( )
187+
188+ tx . type = TYPES . BG
189+ tx . baseSegment = segment
190+ addAttributes ( { transaction : tx , functionContext : context } )
191+
192+ const newContext = ctx . enterSegment ( { segment } )
193+ const boundHandler = tracer . bindFunction ( handler , newContext )
194+
195+ const result = await boundHandler ( ...args )
196+ if ( coldStart === true ) {
197+ tx . trace . attributes . addAttribute ( DESTS . TRANS_COMMON , 'faas.coldStart' , true )
198+ coldStart = false
199+ }
200+
201+ tx . end ( )
202+ return result
203+ } )
204+
205+ await appMethod ( ...args )
206+ }
133207}
134208
135209/**
@@ -178,6 +252,7 @@ function mapTriggerType({ functionContext }) {
178252
179253 // Input types are found at:
180254 // https://github.com/Azure/azure-functions-nodejs-library/blob/138c021/src/trigger.ts
255+ // https://learn.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=isolated-process%2Cnode-v4%2Cpython-v2&pivots=programming-language-javascript#supported-bindings
181256 switch ( input ) {
182257 case 'httpTrigger' : {
183258 return 'http'
@@ -187,16 +262,25 @@ function mapTriggerType({ functionContext }) {
187262 return 'timer'
188263 }
189264
265+ case 'blobTrigger' :
190266 case 'cosmosDBTrigger' :
191- case 'sqlTrigger' :
192- case 'mysqlTrigger' : {
267+ case 'daprBindingTrigger' :
268+ case 'mysqlTrigger' :
269+ case 'queueTrigger' :
270+ case 'sqlTrigger' : {
193271 return 'datasource'
194272 }
195273
196- case 'queueTrigger' :
197- case 'serviceBusTrigger' :
198- case 'eventHubTrigger' :
274+ case 'daprTopicTrigger' :
199275 case 'eventGridTrigger' :
276+ case 'eventHubTrigger' :
277+ case 'kafkaTrigger' :
278+ case 'rabbitMQTrigger' :
279+ case 'redisListTrigger' :
280+ case 'redisPubSubTrigger' :
281+ case 'redisStreamTrigger' :
282+ case 'serviceBusTrigger' :
283+ case 'signalRTrigger' :
200284 case 'webPubSubTrigger' : {
201285 return 'pubsub'
202286 }
0 commit comments