Skip to content

Commit

Permalink
feat(azure-functions): support Azure Functions programming model v4 (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
trentm authored Jan 20, 2025
1 parent 4db0a45 commit bff2e23
Show file tree
Hide file tree
Showing 64 changed files with 1,162 additions and 317 deletions.
10 changes: 9 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,15 @@ updates:
- "eslint*"

- package-ecosystem: "npm"
directory: "/test/instrumentation/azure-functions/fixtures/AJsAzureFnApp"
directory: "/test/instrumentation/azure-functions/fixtures/azfunc3"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
reviewers:
- "elastic/apm-agent-node-js"

- package-ecosystem: "npm"
directory: "/test/instrumentation/azure-functions/fixtures/azfunc4"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
Expand Down
7 changes: 5 additions & 2 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ See the <<upgrade-to-v4>> guide.
[float]
===== Features
* 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].
({pull}4426[#4426])
[float]
===== Bug fixes
* Fix instrumentation of `@aws-sdk/client-s3`, `@aws-sdk/client-sqs`, and
`@aws-sdk/client-sns` for versions 3.723.0 and later. (Internally the AWS SDK
clients updated to `@smithy/smithy-client@4`.)
`@aws-sdk/client-sns` for versions 3.723.0 and later. Internally the AWS SDK
clients updated to `@smithy/smithy-client@4`. ({pull}4398[#4398])
[float]
===== Chores
Expand Down
23 changes: 9 additions & 14 deletions docs/azure-functions.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ endif::[]

=== Monitoring Node.js Azure Functions

The Node.js APM Agent can trace function invocations in an https://learn.microsoft.com/en-us/azure/azure-functions/[Azure Functions] app.
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].


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

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].
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].

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

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


[float]
Expand All @@ -103,7 +100,7 @@ For example:

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

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

This instrumentation does not send an APM transaction or error to APM server when
a handler has an `uncaughtException` or `unhandledRejection`.
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.
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.

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).

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).
Elastic APM's <<central-config,central configuration>> is not supported for Azure Functions.


[float]
Expand Down
2 changes: 1 addition & 1 deletion docs/supported-technologies.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ These are the frameworks that we officially support:
|=======================================================================
| Framework | Version | Note
| <<lambda,AWS Lambda>> | N/A |
| <<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].
| <<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]
| <<express,Express>> | ^4.0.0 |
| <<fastify,Fastify>> | >=1.0.0 | See also https://www.fastify.io/docs/latest/Reference/LTS/[Fastify's own LTS documentation]
| <<hapi,@hapi/hapi>> | >=17.9.0 <22.0.0 |
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = [

'examples/esbuild/dist/**',
'examples/typescript/dist/**',
'examples/an-azure-function-app/**',
'examples/azure-function-app/**',
'lib/opentelemetry-bridge/opentelemetry-core-mini/**',
'test/babel/out.js',
'test/lambda/fixtures/esbuild-bundled-handler/hello.js',
Expand Down
11 changes: 0 additions & 11 deletions examples/an-azure-function-app/Bye/index.js

This file was deleted.

19 changes: 0 additions & 19 deletions examples/an-azure-function-app/Hi/function.json

This file was deleted.

28 changes: 0 additions & 28 deletions examples/an-azure-function-app/Hi/index.js

This file was deleted.

3 changes: 0 additions & 3 deletions examples/an-azure-function-app/initapm.js

This file was deleted.

13 changes: 0 additions & 13 deletions examples/an-azure-function-app/package.json

This file was deleted.

File renamed without changes.
File renamed without changes.
15 changes: 15 additions & 0 deletions examples/azure-function-app/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
APP_NAME=$(USER)-example-azure-function-app

.PHONY: print-app-name
print-app-name:
@echo "APP_NAME: $(APP_NAME)"

.PHONY: publish
publish:
func azure functionapp publish "$(APP_NAME)"

# Note that the Azure Functions log stream is extremely flaky. Don't expect it
# to reliably be able to show logs from the deployed function app.
.PHONY: logstream
logstream:
func azure functionapp logstream "$(APP_NAME)"
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
This directory holds a very small Azure Function App implemented in Node.js
and setup to be traced by the Elastic APM agent. The App has two "functions":

1. `Hi` - an HTTP-triggered function that will call the `Bye` function, then
respond with `{"hi":"there"}`.
2. `Bye` - an HTTP-triggered function that will respond with `{"good":"bye"}`.
This directory holds a simple Azure Function (using v4 of the Node.js
programming model) implemented in Node.js and setup to be traced by the Elastic
APM agent. The App has a single function:

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

# Testing locally

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

2. Install the [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools),
which provide a `func` CLI tool for running Azure Functions locally for
development, and for publishing an Function App to Azure. One way to
install is via:

npm install -g azure-functions-core-tools@4

It is recommended that you **not** install it in the local `./node_modules`
folder, because its large install size will get in the way of publishing to
Azure.
development, and for publishing an Function App to Azure.

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

Expand All @@ -29,12 +22,12 @@ and setup to be traced by the Elastic APM agent. The App has two "functions":
export ELASTIC_APM_SECRET_TOKEN=...
```
4. `npm start`
4. `npm start` (This calls `func start` to run the Azure Function app locally.)
5. In a separate terminal, call the Azure Function via:
```
curl -i http://localhost:7071/api/Hi
curl -i http://localhost:7071/api/Hello
```
Expand All @@ -52,7 +45,7 @@ and setup to be traced by the Elastic APM agent. The App has two "functions":
4. Call your functions:
```
curl -i https://<APP_NAME>.azurewebsites.net/api/hi
curl -i https://<APP_NAME>.azurewebsites.net/api/hello
```
The result (after a minute for data to propagate) should be a `<APP_NAME>` service
Expand Down
15 changes: 15 additions & 0 deletions examples/azure-function-app/host.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
1 change: 1 addition & 0 deletions examples/azure-function-app/initapm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('elastic-apm-node').start()
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"AzureWebJobsStorage": "",

"REGION_NAME": "test-region-name",
"WEBSITE_SITE_NAME": "an-azure-function-app",
"WEBSITE_SITE_NAME": "example-azure-function-app",
"WEBSITE_INSTANCE_ID": "test-website-instance-id"
}
}
18 changes: 18 additions & 0 deletions examples/azure-function-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "example-azure-function-app",
"version": "2.0.0",
"description": "An example Azure Function app showing Elastic APM integration for tracing/monitoring",
"private": true,
"main": "{initapm.js,src/functions/*.js}",
"engines": {
"node": ">=20"
},
"scripts": {
"start": "func start",
"dev:sync-local-apm-agent-changes": "rsync -av ../../lib/ ./node_modules/elastic-apm-node/lib/"
},
"dependencies": {
"@azure/functions": "^4.0.0",
"elastic-apm-node": "^4.11.0"
}
}
24 changes: 24 additions & 0 deletions examples/azure-function-app/src/functions/Hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { app } = require('@azure/functions');

app.http('Hello', {
methods: ['GET'],
authLevel: 'anonymous',
handler: async (_request, _context) => {
const url = new URL('http://worldtimeapi.org/api/timezone/America/Vancouver');
const timeRes = await fetch(url, { signal: AbortSignal.timeout(5000) });
const timeBody = await timeRes.json();

const body = JSON.stringify({
hello: 'world',
'current time in Vancouver': timeBody.datetime
});
return {
status: 200,
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
},
body
};
},
});
Loading

0 comments on commit bff2e23

Please sign in to comment.