Skip to content

feat: add execution id support #592

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

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3d279d7
feat: add execution id support.
liuyunnnn Feb 22, 2024
f7f3a49
add --enable-execution-id.
liuyunnnn Feb 22, 2024
29fc127
Force execution id support on nodejs version to be at least 12.17.0.
liuyunnnn Feb 22, 2024
c2a204b
import async_hooks dynamically.
liuyunnnn Feb 23, 2024
bf56546
revert package-lock.
liuyunnnn Feb 23, 2024
106b0f9
Fix test and dependency.
liuyunnnn Feb 23, 2024
dfdecb2
fix test.
liuyunnnn Feb 23, 2024
da044b2
Fix latest support node js version.
liuyunnnn Feb 23, 2024
15e2384
get rid of text-encoding.
liuyunnnn Feb 23, 2024
998e393
Addressed commets.
liuyunnnn Feb 26, 2024
a898052
Fixed format.
liuyunnnn Feb 26, 2024
a212010
Add method getExecutionId() to allow users get execution id.
liuyunnnn Feb 27, 2024
37eebd7
Control enabling or not by env var LOG_EXECUTION_ID.
liuyunnnn Feb 27, 2024
79270d5
Error handler to catch all unhandled exceptions and log with executio…
liuyunnnn Feb 27, 2024
58871f0
Update doc
liuyunnnn Feb 27, 2024
19b0de7
Addressed comments
liuyunnnn Feb 28, 2024
a01bd34
fixed format.
liuyunnnn Feb 28, 2024
ad41e7b
Addressed comments.
liuyunnnn Mar 4, 2024
2ae9bf8
Updated dependencies to use node:crypto
liuyunnnn Mar 4, 2024
5fc4025
Update dependencies to use node:crypto
liuyunnnn Mar 4, 2024
c72c7b6
fixed tsconfig
liuyunnnn Mar 4, 2024
12d1c5b
fixed dependencies.
liuyunnnn Mar 4, 2024
1ab7659
Seperate execution context middleware into two and add execution id, …
liuyunnnn Mar 4, 2024
859a7fe
Update docs
liuyunnnn Mar 4, 2024
d18088d
delete getExecutionId()
liuyunnnn Mar 4, 2024
19a3a4c
cleanup getExecutionId().
liuyunnnn Mar 4, 2024
363b4d4
add getExecutionId() back as req.executionId is not accessible to eve…
liuyunnnn Mar 5, 2024
409c5c9
Update documents.
liuyunnnn Mar 5, 2024
ea14efc
addressed comments and remove getExecutionId().
liuyunnnn Mar 6, 2024
fcb6a40
import type AsyncLocalStorage
liuyunnnn Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 48 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

[![npm version](https://img.shields.io/npm/v/@google-cloud/functions-framework.svg)](https://www.npmjs.com/package/@google-cloud/functions-framework) [![npm downloads](https://img.shields.io/npm/dm/@google-cloud/functions-framework.svg)](https://npmcharts.com/compare/@google-cloud/functions-framework?minimal=true)

[![Node unit CI][ff_node_unit_img]][ff_node_unit_link] [![Node lint CI][ff_node_lint_img]][ff_node_lint_link] [![Node conformace CI][ff_node_conformance_img]][ff_node_conformance_link] ![Security Scorecard](https://api.securityscorecards.dev/projects/github.com/GoogleCloudPlatform/functions-framework-nodejs/badge)
[![Node unit CI][ff_node_unit_img]][ff_node_unit_link] [![Node lint CI][ff_node_lint_img]][ff_node_lint_link] [![Node conformace CI][ff_node_conformance_img]][ff_node_conformance_link] ![Security Scorecard](https://api.securityscorecards.dev/projects/github.com/GoogleCloudPlatform/functions-framework-nodejs/badge)

An open source FaaS (Function as a Service) framework based on [Express](https://expressjs.com/)
for writing portable Node.js functions -- brought to you by the Google Cloud Functions team.

The Functions Framework lets you write lightweight functions that run in many
different environments, including:

* [Google Cloud Functions](https://cloud.google.com/functions/)
* Your local development machine
* [Cloud Run](https://cloud.google.com/run/) and [Cloud Run for Anthos](https://cloud.google.com/anthos/run)
* [Knative](https://github.com/knative/)-based environments
- [Google Cloud Functions](https://cloud.google.com/functions/)
- Your local development machine
- [Cloud Run](https://cloud.google.com/run/) and [Cloud Run for Anthos](https://cloud.google.com/anthos/run)
- [Knative](https://github.com/knative/)-based environments

The framework allows you to go from:

Expand Down Expand Up @@ -62,29 +62,29 @@ npm install @google-cloud/functions-framework

1. Create an `index.js` file with the following contents:

```js
exports.helloWorld = (req, res) => {
res.send('Hello, World');
};
```
```js
exports.helloWorld = (req, res) => {
res.send('Hello, World');
};
```

1. Run the following command:

```sh
npx @google-cloud/functions-framework --target=helloWorld
```
```sh
npx @google-cloud/functions-framework --target=helloWorld
```

1. Open http://localhost:8080/ in your browser and see _Hello, World_.

### Quickstart: Set up a new project

1. Create a `package.json` file using `npm init`:
1. Create a `package.json` file using `npm init`:

```sh
npm init
```

1. Create an `index.js` file with the following contents:
1. Create an `index.js` file with the following contents:

```js
const functions = require('@google-cloud/functions-framework');
Expand All @@ -94,22 +94,22 @@ npm install @google-cloud/functions-framework
});
```

1. Now install the Functions Framework:
1. Now install the Functions Framework:

```sh
npm install @google-cloud/functions-framework
```

1. Add a `start` script to `package.json`, with configuration passed via
command-line arguments:
1. Add a `start` script to `package.json`, with configuration passed via
command-line arguments:

```js
"scripts": {
"start": "functions-framework --target=helloWorld"
}
```
```js
"scripts": {
"start": "functions-framework --target=helloWorld"
}
```

1. Use `npm start` to start the built-in local development server:
1. Use `npm start` to start the built-in local development server:

```sh
npm start
Expand All @@ -119,7 +119,7 @@ command-line arguments:
URL: http://localhost:8080/
```

1. Send requests to this function using `curl` from another terminal window:
1. Send requests to this function using `curl` from another terminal window:

```sh
curl localhost:8080
Expand All @@ -131,28 +131,28 @@ command-line arguments:
1. Install [Docker](https://store.docker.com/search?type=edition&offering=community) and the [`pack` tool](https://buildpacks.io/docs/install-pack/).

1. Build a container from your function using the Functions [buildpacks](https://github.com/GoogleCloudPlatform/buildpacks):
```sh
pack build \
--builder gcr.io/buildpacks/builder:v1 \
--env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \
--env GOOGLE_FUNCTION_TARGET=helloWorld \
my-first-function
```

```sh
pack build \
--builder gcr.io/buildpacks/builder:v1 \
--env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \
--env GOOGLE_FUNCTION_TARGET=helloWorld \
my-first-function
```

1. Start the built container:
```sh
docker run --rm -p 8080:8080 my-first-function
# Output: Serving function...
```

```sh
docker run --rm -p 8080:8080 my-first-function
# Output: Serving function...
```

1. Send requests to this function using `curl` from another terminal window:
```sh
curl localhost:8080
# Output: Hello, World!
```

```sh
curl localhost:8080
# Output: Hello, World!
```

## Run your function on serverless platforms

Expand Down Expand Up @@ -190,6 +190,7 @@ ignored.
| `--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function` |
| `--signature-type` | `FUNCTION_SIGNATURE_TYPE` | The signature used when writing your function. Controls unmarshalling rules and determines which arguments are used to invoke your function. Default: `http`; accepted values: `http` or `event` or `cloudevent` |
| `--source` | `FUNCTION_SOURCE` | The path to the directory of your function. Default: `cwd` (the current working directory) |
| / | `LOG_EXECUTION_ID` | Enables execution IDs in logs, either `true` or `false`. When not specified, default to enable. Requires Node.js 13.0.0 or later. |

You can set command-line flags in your `package.json` via the `start` script.
For example:
Expand Down Expand Up @@ -232,7 +233,7 @@ Note that your function must use the `cloudevent`-style function signature:
```js
const functions = require('@google-cloud/functions-framework');

functions.cloudEvent('helloCloudEvents', (cloudevent) => {
functions.cloudEvent('helloCloudEvents', cloudevent => {
console.log(cloudevent.specversion);
console.log(cloudevent.type);
console.log(cloudevent.source);
Expand All @@ -253,8 +254,8 @@ Contributions to this library are welcome and encouraged. See
[CONTRIBUTING](CONTRIBUTING.md) for more information on how to get started.

[ff_node_unit_img]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/unit.yml/badge.svg
[ff_node_unit_link]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/unit.yml
[ff_node_unit_link]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/unit.yml
[ff_node_lint_img]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/lint.yml/badge.svg
[ff_node_lint_link]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/lint.yml
[ff_node_lint_link]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/lint.yml
[ff_node_conformance_img]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/conformance.yml/badge.svg
[ff_node_conformance_link]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/conformance.yml
[ff_node_conformance_link]: https://github.com/GoogleCloudPlatform/functions-framework-nodejs/actions/workflows/conformance.yml
23 changes: 23 additions & 0 deletions docs/generated/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,29 @@
],
"extendsTokenRanges": []
},
{
"kind": "Variable",
"canonicalReference": "@google-cloud/functions-framework!getExecutionId:var",
"docComment": "/**\n * Gets the request-specific execution id\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getExecutionId: "
},
{
"kind": "Content",
"text": "() => string | undefined"
}
],
"fileUrlPath": "src/function_registry.ts",
"isReadonly": true,
"releaseTag": "Public",
"name": "getExecutionId",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "TypeAlias",
"canonicalReference": "@google-cloud/functions-framework!HandlerFunction:type",
Expand Down
3 changes: 3 additions & 0 deletions docs/generated/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export interface EventFunctionWithCallback {
(data: {}, context: Context, callback: Function): any;
}

// @public
export const getExecutionId: () => string | undefined;

// @public
export type HandlerFunction<T = unknown, U = unknown> = HttpFunction | EventFunction | EventFunctionWithCallback | CloudEventFunction<T> | CloudEventFunctionWithCallback<T> | TypedFunction<T, U>;

Expand Down
74 changes: 74 additions & 0 deletions src/execution_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as semver from 'semver';
import {Request, Response, NextFunction} from 'express';
import {requiredNodeJsVersion} from './options';

export const TRACE_CONTEXT_HEADER_KEY = 'X-Cloud-Trace-Context';
export const FUNCTION_EXECUTION_ID_HEADER_KEY = 'function-execution-id';

export interface ExecutionContext {
executionId: string;
traceId?: string;
spanId?: string;
}

const TRACE_CONTEXT_PATTERN =
/^(?<traceId>\w+)\/(?<spanId>\d+);o=(?<options>.+)$/;

function generateExecutionId() {
const timestampPart = Date.now().toString(36).slice(-6);
const randomPart = Math.random().toString(36).slice(-6);
return timestampPart + randomPart;
}

let asyncLocalStorage: any;

export async function executionContextMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
if (semver.lt(process.versions.node, requiredNodeJsVersion)) {
// Skip for unsupported Node.js version.
next();
return;
}
const asyncHooks = await import('node:async_hooks');
if (!asyncLocalStorage) {
asyncLocalStorage = new asyncHooks.AsyncLocalStorage();
}

let executionId = req.header(FUNCTION_EXECUTION_ID_HEADER_KEY);
if (!executionId) {
executionId = generateExecutionId();
}

let traceId, spanId, options;
const cloudTraceContext = req.header(TRACE_CONTEXT_HEADER_KEY);
if (cloudTraceContext) {
const match = cloudTraceContext.match(TRACE_CONTEXT_PATTERN);
if (match?.groups) {
({traceId, spanId, options} = match.groups);
}
}

const executionContext = <ExecutionContext>{
executionId: executionId,
traceId: traceId,
spanId: spanId,
};

asyncLocalStorage.run(executionContext, () => {
next();
});
}

export function getCurrentContext(): ExecutionContext | undefined {
if (!asyncLocalStorage) {
return undefined;
}
return asyncLocalStorage.getStore();
}

export const getCurrentExecutionId = (): string | undefined => {
return getCurrentContext()?.executionId;
};
9 changes: 9 additions & 0 deletions src/function_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
JsonInvocationFormat,
} from './functions';
import {SignatureType} from './types';
import {getCurrentExecutionId} from './execution_context';

interface RegisteredFunction<T, U> {
signatureType: SignatureType;
Expand Down Expand Up @@ -65,6 +66,14 @@ export const isValidFunctionName = (functionName: string): boolean => {
return regex.test(functionName);
};

/**
* Gets the request-specific execution id
* @public
*/
export const getExecutionId = (): string | undefined => {
return getCurrentExecutionId();
};

/**
* Get a declaratively registered function
* @param functionName the name with which the function was registered
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ export * from './functions';
/**
* @public
*/
export {http, cloudEvent, typed} from './function_registry';
export {http, cloudEvent, typed, getExecutionId} from './function_registry';
Loading