Skip to content

Commit bff2e23

Browse files
authored
feat(azure-functions): support Azure Functions programming model v4 (#4426)
See https://learn.microsoft.com/en-ca/azure/azure-functions/functions-node-upgrade-v4 This builds on the work in #4178 by @qzxlkj, which provide most of the runtime code fix. The rest of this is adding testing and updated examples and docs. Refs: #4178 Closes: #3185
1 parent 4db0a45 commit bff2e23

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1162
-317
lines changed

.github/dependabot.yml

+9-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@ updates:
4848
- "eslint*"
4949

5050
- package-ecosystem: "npm"
51-
directory: "/test/instrumentation/azure-functions/fixtures/AJsAzureFnApp"
51+
directory: "/test/instrumentation/azure-functions/fixtures/azfunc3"
52+
schedule:
53+
interval: "weekly"
54+
open-pull-requests-limit: 5
55+
reviewers:
56+
- "elastic/apm-agent-node-js"
57+
58+
- package-ecosystem: "npm"
59+
directory: "/test/instrumentation/azure-functions/fixtures/azfunc4"
5260
schedule:
5361
interval: "weekly"
5462
open-pull-requests-limit: 5

CHANGELOG.asciidoc

+5-2
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ See the <<upgrade-to-v4>> guide.
4141
[float]
4242
===== Features
4343
44+
* Support instrumentation of Azure Functions using the https://learn.microsoft.com/en-ca/azure/azure-functions/functions-node-upgrade-v4[v4 Node.js programming model].
45+
({pull}4426[#4426])
46+
4447
[float]
4548
===== Bug fixes
4649
4750
* Fix instrumentation of `@aws-sdk/client-s3`, `@aws-sdk/client-sqs`, and
48-
`@aws-sdk/client-sns` for versions 3.723.0 and later. (Internally the AWS SDK
49-
clients updated to `@smithy/smithy-client@4`.)
51+
`@aws-sdk/client-sns` for versions 3.723.0 and later. Internally the AWS SDK
52+
clients updated to `@smithy/smithy-client@4`. ({pull}4398[#4398])
5053
5154
[float]
5255
===== Chores

docs/azure-functions.asciidoc

+9-14
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ endif::[]
99

1010
=== Monitoring Node.js Azure Functions
1111

12-
The Node.js APM Agent can trace function invocations in an https://learn.microsoft.com/en-us/azure/azure-functions/[Azure Functions] app.
12+
The Node.js APM Agent can trace function invocations in an https://learn.microsoft.com/en-us/azure/azure-functions/[Azure Functions] app, using either v3 or https://learn.microsoft.com/en-us/azure/azure-functions/functions-node-upgrade-v4[v4 of the Node.js programming model].
1313

1414

1515
[float]
@@ -36,7 +36,7 @@ will result in unreasonably large deployments that will be very slow to publish
3636
and will run your Azure Function app VM out of disk space.
3737
====
3838

39-
You can also take a look at and use this https://github.com/elastic/apm-agent-nodejs/tree/main/examples/an-azure-function-app/[Azure Functions example app with Elastic APM already integrated].
39+
You can also take a look at and use this https://github.com/elastic/apm-agent-nodejs/tree/main/examples/azure-function-app/[Azure Functions example app with Elastic APM already integrated].
4040

4141
[float]
4242
[[azure-functions-setup]]
@@ -69,17 +69,14 @@ require('elastic-apm-node').start({
6969
----
7070
<1> Optional <<configuration,configuration options>> can be added here.
7171

72-
2. Add a "main" entry to your package.json pointing to the app init file.
72+
2. Change the "main" entry in your "package.json" to point to the initapm.js file.
7373
+
7474
[source,json]
7575
----
7676
...
77-
"main": "initapm.js",
77+
"main": "{initapm.js,src/functions/*.js}",
7878
...
7979
----
80-
+
81-
If your application already has a "main" init file, you can instead add the
82-
`require('elastic-apm-node').start()` to top of that file.
8380

8481

8582
[float]
@@ -103,7 +100,7 @@ For example:
103100

104101
image::./images/azure-functions-configuration.png[Configuring the APM Agent in the Azure Portal]
105102

106-
For local testing via `func start` you can set these environment variables in
103+
For local testing via `func start`, you can set these environment variables in
107104
your terminal, or in the "local.settings.json" file. See the
108105
<<configuration,agent configuration guide>> for full details on supported
109106
configuration variables.
@@ -127,13 +124,11 @@ of time, so allow a minute or so for data to appear.
127124
[[azure-functions-limitations]]
128125
==== Limitations
129126

130-
This instrumentation does not send an APM transaction or error to APM server when
131-
a handler has an `uncaughtException` or `unhandledRejection`.
132-
The Azure Functions Node.js reference https://learn.microsoft.com/en-ca/azure/azure-functions/functions-reference-node#use-async-and-await[has a section] with best practices for avoiding these cases.
127+
Distributed tracing for incoming HTTP requests to Azure Functions (using v4 of the programming model) does *not* work, because of a issue with Azure's handling of trace-context. See https://github.com/elastic/apm-agent-nodejs/pull/4426#issuecomment-2596922653[this] for details.
128+
129+
Azure Functions instrumentation currently does _not_ collect system metrics in the background because of a concern with unintentionally increasing Azure Functions costs (for Consumption plans).
133130

134-
Azure Functions instrumentation currently does _not_ collect system metrics in
135-
the background because of a concern with unintentionally increasing Azure
136-
Functions costs (for Consumption plans).
131+
Elastic APM's <<central-config,central configuration>> is not supported for Azure Functions.
137132

138133

139134
[float]

docs/supported-technologies.asciidoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ These are the frameworks that we officially support:
6363
|=======================================================================
6464
| Framework | Version | Note
6565
| <<lambda,AWS Lambda>> | N/A |
66-
| <<azure-functions,Azure Functions>> | ~4 | See https://learn.microsoft.com/en-ca/azure/azure-functions/set-runtime-version[the guide on Azure Functions runtime versions].
66+
| <<azure-functions,Azure Functions>> | v3, v4 | https://learn.microsoft.com/en-us/azure/azure-functions/functions-node-upgrade-v4[Node.js programming model v3 and v4]
6767
| <<express,Express>> | ^4.0.0 |
6868
| <<fastify,Fastify>> | >=1.0.0 | See also https://www.fastify.io/docs/latest/Reference/LTS/[Fastify's own LTS documentation]
6969
| <<hapi,@hapi/hapi>> | >=17.9.0 <22.0.0 |

eslint.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports = [
3030

3131
'examples/esbuild/dist/**',
3232
'examples/typescript/dist/**',
33-
'examples/an-azure-function-app/**',
33+
'examples/azure-function-app/**',
3434
'lib/opentelemetry-bridge/opentelemetry-core-mini/**',
3535
'test/babel/out.js',
3636
'test/lambda/fixtures/esbuild-bundled-handler/hello.js',

examples/an-azure-function-app/Bye/index.js

-11
This file was deleted.

examples/an-azure-function-app/Hi/function.json

-19
This file was deleted.

examples/an-azure-function-app/Hi/index.js

-28
This file was deleted.

examples/an-azure-function-app/initapm.js

-3
This file was deleted.

examples/an-azure-function-app/package.json

-13
This file was deleted.
File renamed without changes.

examples/azure-function-app/Makefile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
APP_NAME=$(USER)-example-azure-function-app
2+
3+
.PHONY: print-app-name
4+
print-app-name:
5+
@echo "APP_NAME: $(APP_NAME)"
6+
7+
.PHONY: publish
8+
publish:
9+
func azure functionapp publish "$(APP_NAME)"
10+
11+
# Note that the Azure Functions log stream is extremely flaky. Don't expect it
12+
# to reliably be able to show logs from the deployed function app.
13+
.PHONY: logstream
14+
logstream:
15+
func azure functionapp logstream "$(APP_NAME)"

examples/an-azure-function-app/README.md examples/azure-function-app/README.md

+10-17
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
This directory holds a very small Azure Function App implemented in Node.js
2-
and setup to be traced by the Elastic APM agent. The App has two "functions":
3-
4-
1. `Hi` - an HTTP-triggered function that will call the `Bye` function, then
5-
respond with `{"hi":"there"}`.
6-
2. `Bye` - an HTTP-triggered function that will respond with `{"good":"bye"}`.
1+
This directory holds a simple Azure Function (using v4 of the Node.js
2+
programming model) implemented in Node.js and setup to be traced by the Elastic
3+
APM agent. The App has a single function:
74

5+
- `Hello`: an HTTP-triggered function that will call worldtimeapi.org to get
6+
the current time in Vancouver and respond with
7+
`{"hello": "world", "current time in Vancouver": "..."}`
88

99
# Testing locally
1010

@@ -13,14 +13,7 @@ and setup to be traced by the Elastic APM agent. The App has two "functions":
1313

1414
2. Install the [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools),
1515
which provide a `func` CLI tool for running Azure Functions locally for
16-
development, and for publishing an Function App to Azure. One way to
17-
install is via:
18-
19-
npm install -g azure-functions-core-tools@4
20-
21-
It is recommended that you **not** install it in the local `./node_modules`
22-
folder, because its large install size will get in the way of publishing to
23-
Azure.
16+
development, and for publishing an Function App to Azure.
2417

2518
3. Set environment variable to configure the APM agent, for example:
2619

@@ -29,12 +22,12 @@ and setup to be traced by the Elastic APM agent. The App has two "functions":
2922
export ELASTIC_APM_SECRET_TOKEN=...
3023
```
3124
32-
4. `npm start`
25+
4. `npm start` (This calls `func start` to run the Azure Function app locally.)
3326
3427
5. In a separate terminal, call the Azure Function via:
3528
3629
```
37-
curl -i http://localhost:7071/api/Hi
30+
curl -i http://localhost:7071/api/Hello
3831
```
3932
4033
@@ -52,7 +45,7 @@ and setup to be traced by the Elastic APM agent. The App has two "functions":
5245
4. Call your functions:
5346
5447
```
55-
curl -i https://<APP_NAME>.azurewebsites.net/api/hi
48+
curl -i https://<APP_NAME>.azurewebsites.net/api/hello
5649
```
5750
5851
The result (after a minute for data to propagate) should be a `<APP_NAME>` service

examples/azure-function-app/host.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "2.0",
3+
"logging": {
4+
"applicationInsights": {
5+
"samplingSettings": {
6+
"isEnabled": true,
7+
"excludedTypes": "Request"
8+
}
9+
}
10+
},
11+
"extensionBundle": {
12+
"id": "Microsoft.Azure.Functions.ExtensionBundle",
13+
"version": "[4.*, 5.0.0)"
14+
}
15+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('elastic-apm-node').start()

examples/an-azure-function-app/local.settings.json examples/azure-function-app/local.settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"AzureWebJobsStorage": "",
66

77
"REGION_NAME": "test-region-name",
8-
"WEBSITE_SITE_NAME": "an-azure-function-app",
8+
"WEBSITE_SITE_NAME": "example-azure-function-app",
99
"WEBSITE_INSTANCE_ID": "test-website-instance-id"
1010
}
1111
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "example-azure-function-app",
3+
"version": "2.0.0",
4+
"description": "An example Azure Function app showing Elastic APM integration for tracing/monitoring",
5+
"private": true,
6+
"main": "{initapm.js,src/functions/*.js}",
7+
"engines": {
8+
"node": ">=20"
9+
},
10+
"scripts": {
11+
"start": "func start",
12+
"dev:sync-local-apm-agent-changes": "rsync -av ../../lib/ ./node_modules/elastic-apm-node/lib/"
13+
},
14+
"dependencies": {
15+
"@azure/functions": "^4.0.0",
16+
"elastic-apm-node": "^4.11.0"
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { app } = require('@azure/functions');
2+
3+
app.http('Hello', {
4+
methods: ['GET'],
5+
authLevel: 'anonymous',
6+
handler: async (_request, _context) => {
7+
const url = new URL('http://worldtimeapi.org/api/timezone/America/Vancouver');
8+
const timeRes = await fetch(url, { signal: AbortSignal.timeout(5000) });
9+
const timeBody = await timeRes.json();
10+
11+
const body = JSON.stringify({
12+
hello: 'world',
13+
'current time in Vancouver': timeBody.datetime
14+
});
15+
return {
16+
status: 200,
17+
headers: {
18+
'Content-Type': 'application/json',
19+
'Content-Length': Buffer.byteLength(body)
20+
},
21+
body
22+
};
23+
},
24+
});

0 commit comments

Comments
 (0)