From 185e15baa8e2691e5b22b240a1d5825d3d0eae7b Mon Sep 17 00:00:00 2001 From: Brice Date: Tue, 4 Mar 2025 13:35:41 -0800 Subject: [PATCH 01/17] feat(metrics): add ability to pass functionName to middy and decorator --- packages/metrics/src/Metrics.ts | 27 +++- packages/metrics/src/middleware/middy.ts | 12 +- packages/metrics/src/types/Metrics.ts | 7 + .../metrics/tests/unit/logMetrics.test.ts | 139 ++++++++++++++++++ 4 files changed, 179 insertions(+), 6 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 93b2e35787..35da1b6a48 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -476,6 +476,17 @@ class Metrics extends Utility implements MetricsInterface { return Object.keys(this.storedMetrics).length > 0; } + /** + * Check if a function name has been defined. + * + * This is useful when you want to only set a function name if it is not already set. + * + * The method is primarily intended for internal use, but it is exposed for advanced use cases. + */ + public hasFunctionName(): boolean { + return this.functionName != null; + } + /** * Whether metrics are disabled. */ @@ -518,18 +529,26 @@ class Metrics extends Utility implements MetricsInterface { * - `captureColdStartMetric` - Whether to capture a `ColdStart` metric * - `defaultDimensions` - Default dimensions to add to all metrics * - `throwOnEmptyMetrics` - Whether to throw an error if no metrics are emitted + * - `functionName` - Set the function name used for cold starts * * @param options - Options to configure the behavior of the decorator, see {@link ExtraOptions} */ public logMetrics(options: ExtraOptions = {}): HandlerMethodDecorator { - const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = - options; + const { + throwOnEmptyMetrics, + defaultDimensions, + captureColdStartMetric, + functionName, + } = options; if (throwOnEmptyMetrics) { this.setThrowOnEmptyMetrics(throwOnEmptyMetrics); } if (defaultDimensions !== undefined) { this.setDefaultDimensions(defaultDimensions); } + if (functionName !== undefined) { + this.setFunctionName(functionName); + } return (_target, _propertyKey, descriptor) => { // biome-ignore lint/style/noNonNullAssertion: The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -543,7 +562,9 @@ class Metrics extends Utility implements MetricsInterface { context: Context, callback: Callback ): Promise { - metricsRef.functionName = context.functionName; + if (!metricsRef.hasFunctionName()) { + metricsRef.functionName = context.functionName; + } if (captureColdStartMetric) metricsRef.captureColdStartMetric(); let result: unknown; diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index e02a39d79a..7ec8a5eb38 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -62,9 +62,15 @@ const logMetrics = ( const logMetricsBefore = async (request: MiddyLikeRequest): Promise => { for (const metrics of metricsInstances) { - metrics.setFunctionName(request.context.functionName); - const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = - options; + const { + throwOnEmptyMetrics, + defaultDimensions, + captureColdStartMetric, + functionName, + } = options; + if (!metrics.hasFunctionName() || functionName) { + metrics.setFunctionName(functionName ?? request.context.functionName); + } if (throwOnEmptyMetrics) { metrics.setThrowOnEmptyMetrics(throwOnEmptyMetrics); } diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index da71711501..4e56004cff 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -112,6 +112,13 @@ type ExtraOptions = { * @see {@link MetricsInterface.captureColdStartMetric | `captureColdStartMetric()`} */ captureColdStartMetric?: boolean; + /** + * Set the metric instances function name for `ColdStart` metric. + * + * @default request.context.functionName + * @see {@link MetricsInterface.setFunctionName | `setFunctionName()`} + */ + functionName?: string; }; /** diff --git a/packages/metrics/tests/unit/logMetrics.test.ts b/packages/metrics/tests/unit/logMetrics.test.ts index 90234c1e60..1869966f6f 100644 --- a/packages/metrics/tests/unit/logMetrics.test.ts +++ b/packages/metrics/tests/unit/logMetrics.test.ts @@ -82,6 +82,55 @@ describe('LogMetrics decorator & Middy.js middleware', () => { ); }); + it('override the function name for cold start metric when using decorator', async () => { + // Prepare + const decoratorFunctionName = 'decorator-function-name'; + const functionName = 'function-name'; + const metrics = new Metrics({ + singleMetric: false, + namespace: DEFAULT_NAMESPACE, + }); + metrics.setFunctionName(functionName); + + vi.spyOn(metrics, 'publishStoredMetrics'); + class Test { + readonly #metricName: string; + + public constructor(name: string) { + this.#metricName = name; + } + + @metrics.logMetrics({ + captureColdStartMetric: true, + functionName: decoratorFunctionName, + }) + async handler(_event: unknown, _context: Context) { + this.addGreetingMetric(); + } + + addGreetingMetric() { + metrics.addMetric(this.#metricName, MetricUnit.Count, 1); + } + } + const lambda = new Test('greetings'); + const handler = lambda.handler.bind(lambda); + + // Act + await handler({}, {} as Context); + + // Assess + expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); + expect(console.log).toHaveBeenCalledTimes(2); + expect(console.log).toHaveEmittedNthEMFWith( + 1, + expect.objectContaining({ + [COLD_START_METRIC]: 1, + service: 'hello-world', + function_name: decoratorFunctionName, + }) + ); + }); + it('captures the cold start metric on the first invocation when using the Middy.js middleware', async () => { // Prepare const metrics = new Metrics({ @@ -109,6 +158,96 @@ describe('LogMetrics decorator & Middy.js middleware', () => { ); }); + it('sets the function name in the cold start metric when using the Middy.js middleware', async () => { + // Prepare + const contextFunctionName = 'lambda-function-context-name'; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + }); + + vi.spyOn(metrics, 'publishStoredMetrics'); + const handler = middy(async () => { + metrics.addMetric('greetings', MetricUnit.Count, 1); + }).use(logMetrics(metrics, { captureColdStartMetric: true })); + + // Act + await handler({}, { functionName: contextFunctionName } as Context); + + // Assess + expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedNthEMFWith( + 1, + expect.objectContaining({ + [COLD_START_METRIC]: 1, + service: 'hello-world', + function_name: contextFunctionName, + }) + ); + }); + + it('override the function name in the cold start metric when using the Middy.js middleware', async () => { + // Prepare + const contextFunctionName = 'lambda-function-context-name'; + const functionName = 'my-function'; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + }); + metrics.setFunctionName(functionName); + + vi.spyOn(metrics, 'publishStoredMetrics'); + const overrideFunctionName = 'overwritten-function-name'; + const handler = middy(async () => { + metrics.addMetric('greetings', MetricUnit.Count, 1); + }).use( + logMetrics(metrics, { + captureColdStartMetric: true, + functionName: overrideFunctionName, + }) + ); + + // Act + await handler({}, { functionName: contextFunctionName } as Context); + + // Assess + expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedNthEMFWith( + 1, + expect.objectContaining({ + [COLD_START_METRIC]: 1, + service: 'hello-world', + function_name: overrideFunctionName, + }) + ); + }); + + it('does not override existing function name in the cold start metric when using the Middy.js middleware', async () => { + // Prepare + const functionName = 'my-function'; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + }); + metrics.setFunctionName(functionName); + + vi.spyOn(metrics, 'publishStoredMetrics'); + const handler = middy(async () => { + metrics.addMetric('greetings', MetricUnit.Count, 1); + }).use(logMetrics(metrics, { captureColdStartMetric: true })); + + // Act + await handler({}, {} as Context); + + // Assess + expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedNthEMFWith( + 1, + expect.objectContaining({ + [COLD_START_METRIC]: 1, + service: 'hello-world', + function_name: functionName, + }) + ); + }); + it('includes default dimensions passed in the decorator', async () => { // Prepare const metrics = new Metrics({ From ff7970675081c118064ce1f085d3e9922e783a9b Mon Sep 17 00:00:00 2001 From: Brice Date: Tue, 4 Mar 2025 14:24:16 -0800 Subject: [PATCH 02/17] docs(metrics): add setting function name section --- docs/core/metrics.md | 32 ++++++++++++++++++- .../snippets/metrics/functionNameDecorator.ts | 21 ++++++++++++ .../snippets/metrics/functionNameMiddy.ts | 25 +++++++++++++++ examples/snippets/metrics/setFunctionName.ts | 19 +++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 examples/snippets/metrics/functionNameDecorator.ts create mode 100644 examples/snippets/metrics/functionNameMiddy.ts create mode 100644 examples/snippets/metrics/setFunctionName.ts diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 42bc929ebe..728821153f 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -184,7 +184,7 @@ You can call `addMetric()` with the same name multiple times. The values will be ### Adding default dimensions -You can add default dimensions to your metrics by passing them as parameters in 4 ways: +You can add default dimensions to your metrics by passing them as parameters in 4 ways: * in the constructor * in the [Middy-compatible](https://github.com/middyjs/middy){target=_blank} middleware @@ -230,6 +230,36 @@ You can add default dimensions to your metrics by passing them as parameters in If you'd like to remove them at some point, you can use the `clearDefaultDimensions` method. +### Setting function name + +When emitting cold start metrics, we use the `context.functionName` as the `function_name` + dimension. If you want to change the function name you can set the `functionName` by + passing it as a parameter in 3 ways: + +* in the [Middy-compatible](https://github.com/middyjs/middy){target=_blank} middleware +* using the `setFunctionName` method +* in the decorator + +=== "Middy middleware" + + ```typescript hl_lines="22" + --8<-- "examples/snippets/metrics/functionNameMiddy.ts" + ``` + +=== "setFunctionName method" + + ```typescript hl_lines="9" + --8<-- "examples/snippets/metrics/setFunctionName.ts" + ``` + +=== "with logMetrics decorator" + + ```typescript hl_lines="11" + --8<-- "examples/snippets/metrics/functionNameDecorator.ts" + ``` + + 1. Binding your handler method allows your handler to access `this` within the class methods. + ### Changing default timestamp When creating metrics, we use the current timestamp. If you want to change the timestamp of all the metrics you create, utilize the `setTimestamp` function. You can specify a datetime object or an integer representing an epoch timestamp in milliseconds. diff --git a/examples/snippets/metrics/functionNameDecorator.ts b/examples/snippets/metrics/functionNameDecorator.ts new file mode 100644 index 0000000000..4baf3f454f --- /dev/null +++ b/examples/snippets/metrics/functionNameDecorator.ts @@ -0,0 +1,21 @@ +import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; +import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics'; + +const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', +}); + +export class Lambda implements LambdaInterface { + // Decorate your handler class method + @metrics.logMetrics({ + functionName: 'my-function-name', + captureColdStartMetric: true, + }) + public async handler(_event: unknown, _context: unknown): Promise { + metrics.addMetric('successfulBooking', MetricUnit.Count, 1); + } +} + +const handlerClass = new Lambda(); +export const handler = handlerClass.handler.bind(handlerClass); // (1) diff --git a/examples/snippets/metrics/functionNameMiddy.ts b/examples/snippets/metrics/functionNameMiddy.ts new file mode 100644 index 0000000000..7b35212037 --- /dev/null +++ b/examples/snippets/metrics/functionNameMiddy.ts @@ -0,0 +1,25 @@ +import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics'; +import { logMetrics } from '@aws-lambda-powertools/metrics/middleware'; +import middy from '@middy/core'; + +const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', +}); + +const lambdaHandler = async ( + _event: unknown, + _context: unknown +): Promise => { + metrics.addMetric('successfulBooking', MetricUnit.Count, 1); +}; + +// Wrap the handler with middy +export const handler = middy(lambdaHandler) + // Use the middleware by passing the Metrics instance as a parameter + .use( + logMetrics(metrics, { + functionName: 'my-function-name', + captureColdStartMetric: true, + }) + ); diff --git a/examples/snippets/metrics/setFunctionName.ts b/examples/snippets/metrics/setFunctionName.ts new file mode 100644 index 0000000000..1c0a760161 --- /dev/null +++ b/examples/snippets/metrics/setFunctionName.ts @@ -0,0 +1,19 @@ +import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics'; + +const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', +}); + +// Setting function name must come before calling `captureColdStartMetric` +metrics.setFunctionName('my-function-name'); + +// Ensure we emit the cold start +metrics.captureColdStartMetric(); + +export const handler = async ( + _event: unknown, + _context: unknown +): Promise => { + metrics.addMetric('successfulBooking', MetricUnit.Count, 1); +}; From 27875ee600e4b3947101ea9c4bf8eaadc662fe0f Mon Sep 17 00:00:00 2001 From: Brice Date: Tue, 4 Mar 2025 14:53:23 -0800 Subject: [PATCH 03/17] docs(metrics): updated highlighted line for setting function name decorator example --- docs/core/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 728821153f..8f4875424a 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -254,7 +254,7 @@ When emitting cold start metrics, we use the `context.functionName` as the `func === "with logMetrics decorator" - ```typescript hl_lines="11" + ```typescript hl_lines="12" --8<-- "examples/snippets/metrics/functionNameDecorator.ts" ``` From 68439d6dffa409e3adbc13ae11f431266ce35e29 Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 13 Mar 2025 20:26:46 -0700 Subject: [PATCH 04/17] refactor(metrics): revert setting functionName in middy and decorator arguments --- packages/metrics/src/Metrics.ts | 12 +-- packages/metrics/src/middleware/middy.ts | 12 +-- packages/metrics/src/types/Metrics.ts | 14 +-- .../metrics/tests/unit/logMetrics.test.ts | 98 ++++++++++--------- 4 files changed, 67 insertions(+), 69 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 35da1b6a48..1c15db62cf 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -529,26 +529,18 @@ class Metrics extends Utility implements MetricsInterface { * - `captureColdStartMetric` - Whether to capture a `ColdStart` metric * - `defaultDimensions` - Default dimensions to add to all metrics * - `throwOnEmptyMetrics` - Whether to throw an error if no metrics are emitted - * - `functionName` - Set the function name used for cold starts * * @param options - Options to configure the behavior of the decorator, see {@link ExtraOptions} */ public logMetrics(options: ExtraOptions = {}): HandlerMethodDecorator { - const { - throwOnEmptyMetrics, - defaultDimensions, - captureColdStartMetric, - functionName, - } = options; + const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = + options; if (throwOnEmptyMetrics) { this.setThrowOnEmptyMetrics(throwOnEmptyMetrics); } if (defaultDimensions !== undefined) { this.setDefaultDimensions(defaultDimensions); } - if (functionName !== undefined) { - this.setFunctionName(functionName); - } return (_target, _propertyKey, descriptor) => { // biome-ignore lint/style/noNonNullAssertion: The descriptor.value is the method this decorator decorates, it cannot be undefined. diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index 7ec8a5eb38..334e467d80 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -62,14 +62,10 @@ const logMetrics = ( const logMetricsBefore = async (request: MiddyLikeRequest): Promise => { for (const metrics of metricsInstances) { - const { - throwOnEmptyMetrics, - defaultDimensions, - captureColdStartMetric, - functionName, - } = options; - if (!metrics.hasFunctionName() || functionName) { - metrics.setFunctionName(functionName ?? request.context.functionName); + const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = + options; + if (!metrics.hasFunctionName()) { + metrics.setFunctionName(request.context.functionName); } if (throwOnEmptyMetrics) { metrics.setThrowOnEmptyMetrics(throwOnEmptyMetrics); diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index 4e56004cff..c6b862c8fe 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -60,6 +60,13 @@ type MetricsOptions = { * @see {@link MetricsInterface.setDefaultDimensions | `setDefaultDimensions()`} */ defaultDimensions?: Dimensions; + /** + * Function name to use as dimension for the `ColdStart` metric. + * + * @default undefined + * @see {@link MetricsInterface.setFunctionName | `setFunctionName()`} + */ + functionName?: string; /** * Logger object to be used for emitting debug, warning, and error messages. * @@ -112,13 +119,6 @@ type ExtraOptions = { * @see {@link MetricsInterface.captureColdStartMetric | `captureColdStartMetric()`} */ captureColdStartMetric?: boolean; - /** - * Set the metric instances function name for `ColdStart` metric. - * - * @default request.context.functionName - * @see {@link MetricsInterface.setFunctionName | `setFunctionName()`} - */ - functionName?: string; }; /** diff --git a/packages/metrics/tests/unit/logMetrics.test.ts b/packages/metrics/tests/unit/logMetrics.test.ts index 1869966f6f..cf2daf3173 100644 --- a/packages/metrics/tests/unit/logMetrics.test.ts +++ b/packages/metrics/tests/unit/logMetrics.test.ts @@ -6,6 +6,11 @@ import { COLD_START_METRIC, DEFAULT_NAMESPACE } from '../../src/constants.js'; import { MetricUnit, Metrics } from '../../src/index.js'; import { logMetrics } from '../../src/middleware/middy.js'; +const contextFunctionName = 'context-function-name'; +const contextWithFunctionName = { + functionName: contextFunctionName, +} as Context; + describe('LogMetrics decorator & Middy.js middleware', () => { const ENVIRONMENT_VARIABLES = process.env; @@ -45,8 +50,8 @@ describe('LogMetrics decorator & Middy.js middleware', () => { const handler = lambda.handler.bind(lambda); // Act - await handler({}, {} as Context); - await handler({}, {} as Context); + await handler({}, contextWithFunctionName); + await handler({}, contextWithFunctionName); // Assess expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(2); @@ -56,6 +61,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { expect.objectContaining({ [COLD_START_METRIC]: 1, service: 'hello-world', + function_name: contextFunctionName, }) ); expect(console.log).toHaveEmittedNthMetricWith( @@ -82,15 +88,13 @@ describe('LogMetrics decorator & Middy.js middleware', () => { ); }); - it('override the function name for cold start metric when using decorator', async () => { + it('default function name in the cold start metric to context.functionName when using decorator', async () => { // Prepare - const decoratorFunctionName = 'decorator-function-name'; const functionName = 'function-name'; const metrics = new Metrics({ singleMetric: false, namespace: DEFAULT_NAMESPACE, }); - metrics.setFunctionName(functionName); vi.spyOn(metrics, 'publishStoredMetrics'); class Test { @@ -100,10 +104,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { this.#metricName = name; } - @metrics.logMetrics({ - captureColdStartMetric: true, - functionName: decoratorFunctionName, - }) + @metrics.logMetrics({ captureColdStartMetric: true }) async handler(_event: unknown, _context: Context) { this.addGreetingMetric(); } @@ -116,7 +117,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { const handler = lambda.handler.bind(lambda); // Act - await handler({}, {} as Context); + await handler({}, contextWithFunctionName); // Assess expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); @@ -126,87 +127,96 @@ describe('LogMetrics decorator & Middy.js middleware', () => { expect.objectContaining({ [COLD_START_METRIC]: 1, service: 'hello-world', - function_name: decoratorFunctionName, + function_name: contextFunctionName, }) ); }); - it('captures the cold start metric on the first invocation when using the Middy.js middleware', async () => { + it('does not override existing function name in the cold start metric when using decorator', async () => { // Prepare + const functionName = 'function-name'; const metrics = new Metrics({ singleMetric: false, namespace: DEFAULT_NAMESPACE, + functionName, }); + vi.spyOn(metrics, 'publishStoredMetrics'); - const handler = middy(async () => { - metrics.addMetric('greetings', MetricUnit.Count, 1); - }).use(logMetrics(metrics, { captureColdStartMetric: true })); + class Test { + readonly #metricName: string; + + public constructor(name: string) { + this.#metricName = name; + } + + @metrics.logMetrics({ captureColdStartMetric: true }) + async handler(_event: unknown, _context: Context) { + this.addGreetingMetric(); + } + + addGreetingMetric() { + metrics.addMetric(this.#metricName, MetricUnit.Count, 1); + } + } + const lambda = new Test('greetings'); + const handler = lambda.handler.bind(lambda); // Act - await handler({}, {} as Context); - await handler({}, {} as Context); + await handler({}, contextWithFunctionName); // Assess - expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(2); - expect(console.log).toHaveBeenCalledTimes(3); + expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); + expect(console.log).toHaveBeenCalledTimes(2); expect(console.log).toHaveEmittedNthEMFWith( 1, expect.objectContaining({ [COLD_START_METRIC]: 1, service: 'hello-world', + function_name: functionName, }) ); }); - it('sets the function name in the cold start metric when using the Middy.js middleware', async () => { + it('captures the cold start metric on the first invocation when using the Middy.js middleware', async () => { // Prepare - const contextFunctionName = 'lambda-function-context-name'; const metrics = new Metrics({ + singleMetric: false, namespace: DEFAULT_NAMESPACE, }); - vi.spyOn(metrics, 'publishStoredMetrics'); const handler = middy(async () => { metrics.addMetric('greetings', MetricUnit.Count, 1); }).use(logMetrics(metrics, { captureColdStartMetric: true })); // Act - await handler({}, { functionName: contextFunctionName } as Context); + await handler({}, contextWithFunctionName); + await handler({}, contextWithFunctionName); // Assess - expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); + expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(2); + expect(console.log).toHaveBeenCalledTimes(3); expect(console.log).toHaveEmittedNthEMFWith( 1, expect.objectContaining({ [COLD_START_METRIC]: 1, service: 'hello-world', - function_name: contextFunctionName, }) ); }); - it('override the function name in the cold start metric when using the Middy.js middleware', async () => { + it('set function name in the cold start metric to context.functionName when using the Middy.js middleware', async () => { // Prepare - const contextFunctionName = 'lambda-function-context-name'; - const functionName = 'my-function'; const metrics = new Metrics({ namespace: DEFAULT_NAMESPACE, }); - metrics.setFunctionName(functionName); vi.spyOn(metrics, 'publishStoredMetrics'); - const overrideFunctionName = 'overwritten-function-name'; const handler = middy(async () => { metrics.addMetric('greetings', MetricUnit.Count, 1); - }).use( - logMetrics(metrics, { - captureColdStartMetric: true, - functionName: overrideFunctionName, - }) - ); + }).use(logMetrics(metrics, { captureColdStartMetric: true })); // Act - await handler({}, { functionName: contextFunctionName } as Context); + await handler({}, contextWithFunctionName); // Assess expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); @@ -215,7 +225,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { expect.objectContaining({ [COLD_START_METRIC]: 1, service: 'hello-world', - function_name: overrideFunctionName, + function_name: contextFunctionName, }) ); }); @@ -225,8 +235,8 @@ describe('LogMetrics decorator & Middy.js middleware', () => { const functionName = 'my-function'; const metrics = new Metrics({ namespace: DEFAULT_NAMESPACE, + functionName, }); - metrics.setFunctionName(functionName); vi.spyOn(metrics, 'publishStoredMetrics'); const handler = middy(async () => { @@ -234,7 +244,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { }).use(logMetrics(metrics, { captureColdStartMetric: true })); // Act - await handler({}, {} as Context); + await handler({}, contextWithFunctionName); // Assess expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(1); @@ -264,7 +274,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { const handler = lambda.handler.bind(lambda); // Act - await handler({}, {} as Context); + await handler({}, contextWithFunctionName); // Assess expect(console.log).toHaveBeenCalledTimes(1); @@ -296,7 +306,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { ); // Act - await handler({}, {} as Context); + await handler({}, contextWithFunctionName); // Assess expect(console.log).toHaveBeenCalledTimes(1); @@ -400,8 +410,8 @@ describe('LogMetrics decorator & Middy.js middleware', () => { .use(myCustomMiddleware()); // Act - await handler({ idx: 0 }, {} as Context); - await handler({ idx: 1 }, {} as Context); + await handler({ idx: 0 }, contextWithFunctionName); + await handler({ idx: 1 }, contextWithFunctionName); // Assess expect(metrics.publishStoredMetrics).toHaveBeenCalledTimes(2); From 9efee79883d6a9cdd33741ce9003825cd93fb036 Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 13 Mar 2025 20:47:07 -0700 Subject: [PATCH 05/17] feat(metrics): allow setting functionName via ENV and constructor parameter --- packages/metrics/src/Metrics.ts | 10 ++++--- .../src/config/EnvironmentVariablesService.ts | 8 ++++++ .../src/types/ConfigServiceInterface.ts | 4 +++ .../tests/unit/initializeMetrics.test.ts | 27 ++++++++++++++++++- .../metrics/tests/unit/logMetrics.test.ts | 2 +- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 1c15db62cf..cede600d74 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -381,7 +381,7 @@ class Metrics extends Utility implements MetricsInterface { service: this.defaultDimensions.service, }); } - if (this.functionName != null) { + if (this.functionName) { singleMetric.addDimension('function_name', this.functionName); } singleMetric.addMetric(COLD_START_METRIC, MetricUnits.Count, 1); @@ -484,7 +484,7 @@ class Metrics extends Utility implements MetricsInterface { * The method is primarily intended for internal use, but it is exposed for advanced use cases. */ public hasFunctionName(): boolean { - return this.functionName != null; + return Boolean(this.functionName); } /** @@ -781,8 +781,8 @@ class Metrics extends Utility implements MetricsInterface { * * @param name - The function name */ - public setFunctionName(name: string): void { - this.functionName = name; + public setFunctionName(name?: string): void { + this.functionName = name || this.getEnvVarsService().getFunctionName(); } /** @@ -964,6 +964,7 @@ class Metrics extends Utility implements MetricsInterface { serviceName, singleMetric, defaultDimensions, + functionName, } = options; this.setEnvVarsService(); @@ -973,6 +974,7 @@ class Metrics extends Utility implements MetricsInterface { this.setNamespace(namespace); this.setService(serviceName); this.setDefaultDimensions(defaultDimensions); + this.setFunctionName(functionName); this.isSingleMetric = singleMetric || false; return this; diff --git a/packages/metrics/src/config/EnvironmentVariablesService.ts b/packages/metrics/src/config/EnvironmentVariablesService.ts index a611308649..45990d296c 100644 --- a/packages/metrics/src/config/EnvironmentVariablesService.ts +++ b/packages/metrics/src/config/EnvironmentVariablesService.ts @@ -12,6 +12,7 @@ class EnvironmentVariablesService implements ConfigServiceInterface { private namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE'; + private functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME'; private readonly disabledVariable = 'POWERTOOLS_METRICS_DISABLED'; @@ -22,6 +23,13 @@ class EnvironmentVariablesService return this.get(this.namespaceVariable); } + /** + * Get the value of the `POWERTOOLS_METRICS_FUNCTION_NAME` environment variable. + */ + public getFunctionName(): string { + return this.get(this.functionNameVariable); + } + /** * Get the value of the `POWERTOOLS_METRICS_DISABLED` or `POWERTOOLS_DEV` environment variables. * diff --git a/packages/metrics/src/types/ConfigServiceInterface.ts b/packages/metrics/src/types/ConfigServiceInterface.ts index 81caa2d53b..68a86e1571 100644 --- a/packages/metrics/src/types/ConfigServiceInterface.ts +++ b/packages/metrics/src/types/ConfigServiceInterface.ts @@ -12,6 +12,10 @@ interface ConfigServiceInterface extends ConfigServiceBaseInterface { * Get the value of the `POWERTOOLS_METRICS_NAMESPACE` environment variable. */ getNamespace(): string; + /** + * Get the value of the `POWERTOOLS_METRICS_FUNCTION_NAME` environment variable. + */ + getFunctionName(): string; } export type { ConfigServiceInterface }; diff --git a/packages/metrics/tests/unit/initializeMetrics.test.ts b/packages/metrics/tests/unit/initializeMetrics.test.ts index 663328a294..dc89977c7c 100644 --- a/packages/metrics/tests/unit/initializeMetrics.test.ts +++ b/packages/metrics/tests/unit/initializeMetrics.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { DEFAULT_NAMESPACE } from '../../src/constants.js'; +import { COLD_START_METRIC, DEFAULT_NAMESPACE } from '../../src/constants.js'; import { MetricUnit, Metrics } from '../../src/index.js'; import type { ConfigServiceInterface } from '../../src/types/index.js'; @@ -113,6 +113,28 @@ describe('Initialize Metrics', () => { ); }); + it('uses the function name provided in the environment variables', () => { + // Prepare + process.env.POWERTOOLS_METRICS_FUNCTION_NAME = + 'hello-world-function-name-from-env'; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + }); + + // Act + metrics.captureColdStartMetric(); + + // Assess + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedEMFWith( + expect.objectContaining({ + service: 'hello-world', + [COLD_START_METRIC]: 1, + function_name: 'hello-world-function-name-from-env', + }) + ); + }); + it('uses the custom config service provided', () => { // Prepare const configService = { @@ -128,6 +150,9 @@ describe('Initialize Metrics', () => { isValueTrue(value: string): boolean { return value === 'true'; }, + getFunctionName(): string { + return 'custom-function-name'; + }, }; const metrics = new Metrics({ singleMetric: true, diff --git a/packages/metrics/tests/unit/logMetrics.test.ts b/packages/metrics/tests/unit/logMetrics.test.ts index cf2daf3173..d6494cc3f5 100644 --- a/packages/metrics/tests/unit/logMetrics.test.ts +++ b/packages/metrics/tests/unit/logMetrics.test.ts @@ -204,7 +204,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { ); }); - it('set function name in the cold start metric to context.functionName when using the Middy.js middleware', async () => { + it('default function name in the cold start metric to context.functionName when using the Middy.js middleware', async () => { // Prepare const metrics = new Metrics({ namespace: DEFAULT_NAMESPACE, From 4de0264dc4dcaae87c17791090e836d6c88246f0 Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 13 Mar 2025 22:22:38 -0700 Subject: [PATCH 06/17] docs(metrics): update docs and mention functionName constructor parameter and environment variable --- docs/core/metrics.md | 38 +++++++------------ examples/snippets/metrics/functionName.ts | 19 ++++++++++ .../snippets/metrics/functionNameDecorator.ts | 21 ---------- .../snippets/metrics/functionNameMiddy.ts | 25 ------------ examples/snippets/metrics/setFunctionName.ts | 9 +++-- 5 files changed, 37 insertions(+), 75 deletions(-) create mode 100644 examples/snippets/metrics/functionName.ts delete mode 100644 examples/snippets/metrics/functionNameDecorator.ts delete mode 100644 examples/snippets/metrics/functionNameMiddy.ts diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 8f4875424a..6008fd7615 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -66,11 +66,12 @@ The library requires two settings. You can set them as environment variables, or These settings will be used across all metrics emitted: -| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | -| -------------------- | ---------------------------------------------------------------- | ------------------------------ | ------------------- | -------------- | ------------------- | --------------------- | -| **Service** | Optionally, sets **service** metric dimension across all metrics | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline` | `serviceName` | -| **Metric namespace** | Logical container where all metrics will be placed | `POWERTOOLS_METRICS_NAMESPACE` | `default_namespace` | Any string | `serverlessAirline` | `default_namespace` | -| **Enabled** | Whether to emit metrics to standard output or not | `POWERTOOLS_METRICS_ENABLED` | `true` | Boolean | `false` | | +| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | +|----------------------|------------------------------------------------------------------|------------------------------------|---------------------|----------------|---------------------|-----------------------| +| **Service** | Optionally, sets **service** metric dimension across all metrics | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline` | `serviceName` | +| **Metric namespace** | Logical container where all metrics will be placed | `POWERTOOLS_METRICS_NAMESPACE` | `default_namespace` | Any string | `serverlessAirline` | `default_namespace` | +| **Function Name** | Logical Lambda function name used for `ColdStart` metrics | `POWERTOOLS_METRICS_FUNCTION_NAME` | `undefined` | Any string | `my-function-name` | `functionName` | +| **Enabled** | Whether to emit metrics to standard output or not | `POWERTOOLS_METRICS_ENABLED` | `true` | Boolean | `false` | | !!! tip Use your application name or main service as the metric namespace to easily group all metrics @@ -87,7 +88,7 @@ The `Metrics` utility is instantiated outside of the Lambda handler. In doing th === "template.yml" - ```yaml hl_lines="9 10" + ```yaml hl_lines="8-10" Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -97,6 +98,7 @@ The `Metrics` utility is instantiated outside of the Lambda handler. In doing th Variables: POWERTOOLS_SERVICE_NAME: orders POWERTOOLS_METRICS_NAMESPACE: serverlessAirline + POWERTOOLS_METRICS_FUNCTION_NAME: my-function-name ``` You can initialize Metrics anywhere in your code - It'll keep track of your aggregate metrics in memory. @@ -232,34 +234,20 @@ If you'd like to remove them at some point, you can use the `clearDefaultDimensi ### Setting function name -When emitting cold start metrics, we use the `context.functionName` as the `function_name` - dimension. If you want to change the function name you can set the `functionName` by - passing it as a parameter in 3 ways: +When emitting cold start metrics, the `function_name` dimension defaults to `context.functionName`. If you want to change the value you can set the `functionName` parameter in the metrics constructor, call `setFunctionName` in the global scope, or define the environment variable `POWERTOOLS_METRICS_FUNCTION_NAME`. -* in the [Middy-compatible](https://github.com/middyjs/middy){target=_blank} middleware -* using the `setFunctionName` method -* in the decorator - -=== "Middy middleware" +=== "constructor" - ```typescript hl_lines="22" - --8<-- "examples/snippets/metrics/functionNameMiddy.ts" + ```typescript hl_lines="6" + --8<-- "examples/snippets/metrics/functionName.ts" ``` === "setFunctionName method" - ```typescript hl_lines="9" + ```typescript hl_lines="8" --8<-- "examples/snippets/metrics/setFunctionName.ts" ``` -=== "with logMetrics decorator" - - ```typescript hl_lines="12" - --8<-- "examples/snippets/metrics/functionNameDecorator.ts" - ``` - - 1. Binding your handler method allows your handler to access `this` within the class methods. - ### Changing default timestamp When creating metrics, we use the current timestamp. If you want to change the timestamp of all the metrics you create, utilize the `setTimestamp` function. You can specify a datetime object or an integer representing an epoch timestamp in milliseconds. diff --git a/examples/snippets/metrics/functionName.ts b/examples/snippets/metrics/functionName.ts new file mode 100644 index 0000000000..6a047d6fca --- /dev/null +++ b/examples/snippets/metrics/functionName.ts @@ -0,0 +1,19 @@ +import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics'; + +const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + functionName: 'my-function-name', +}); + +export const handler = async ( + _event: unknown, + _context: unknown +): Promise => { + // Capture cold start metric + metrics.captureColdStartMetric(); + + metrics.addMetric('successfulBooking', MetricUnit.Count, 1); + + metrics.publishStoredMetrics(); +}; diff --git a/examples/snippets/metrics/functionNameDecorator.ts b/examples/snippets/metrics/functionNameDecorator.ts deleted file mode 100644 index 4baf3f454f..0000000000 --- a/examples/snippets/metrics/functionNameDecorator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; -import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics'; - -const metrics = new Metrics({ - namespace: 'serverlessAirline', - serviceName: 'orders', -}); - -export class Lambda implements LambdaInterface { - // Decorate your handler class method - @metrics.logMetrics({ - functionName: 'my-function-name', - captureColdStartMetric: true, - }) - public async handler(_event: unknown, _context: unknown): Promise { - metrics.addMetric('successfulBooking', MetricUnit.Count, 1); - } -} - -const handlerClass = new Lambda(); -export const handler = handlerClass.handler.bind(handlerClass); // (1) diff --git a/examples/snippets/metrics/functionNameMiddy.ts b/examples/snippets/metrics/functionNameMiddy.ts deleted file mode 100644 index 7b35212037..0000000000 --- a/examples/snippets/metrics/functionNameMiddy.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics'; -import { logMetrics } from '@aws-lambda-powertools/metrics/middleware'; -import middy from '@middy/core'; - -const metrics = new Metrics({ - namespace: 'serverlessAirline', - serviceName: 'orders', -}); - -const lambdaHandler = async ( - _event: unknown, - _context: unknown -): Promise => { - metrics.addMetric('successfulBooking', MetricUnit.Count, 1); -}; - -// Wrap the handler with middy -export const handler = middy(lambdaHandler) - // Use the middleware by passing the Metrics instance as a parameter - .use( - logMetrics(metrics, { - functionName: 'my-function-name', - captureColdStartMetric: true, - }) - ); diff --git a/examples/snippets/metrics/setFunctionName.ts b/examples/snippets/metrics/setFunctionName.ts index 1c0a760161..675574667b 100644 --- a/examples/snippets/metrics/setFunctionName.ts +++ b/examples/snippets/metrics/setFunctionName.ts @@ -5,15 +5,16 @@ const metrics = new Metrics({ serviceName: 'orders', }); -// Setting function name must come before calling `captureColdStartMetric` metrics.setFunctionName('my-function-name'); -// Ensure we emit the cold start -metrics.captureColdStartMetric(); - export const handler = async ( _event: unknown, _context: unknown ): Promise => { + // Capture cold start metric + metrics.captureColdStartMetric(); + metrics.addMetric('successfulBooking', MetricUnit.Count, 1); + + metrics.publishStoredMetrics(); }; From 0616dc56aae854135f4aca997d8190029ba6454b Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 13 Mar 2025 23:22:43 -0700 Subject: [PATCH 07/17] docs(metrics): add reference to POWERTOOLS_METRICS_FUNCTION_NAME on homepage environment variable list --- docs/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 680d346464..ddf65e3acf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -352,9 +352,10 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al Explicit parameters take precedence over environment variables | Environment variable | Description | Utility | Default | -| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ------------------- | +| -------------------------------------------- |---------------------------------------------------------------------------------------------------------------| --------------------------------------- | ------------------- | | **POWERTOOLS_SERVICE_NAME** | Set service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` | | **POWERTOOLS_METRICS_NAMESPACE** | Set namespace used for metrics | [Metrics](core/metrics.md) | `default_namespace` | +| **POWERTOOLS_METRICS_FUNCTION_NAME** | Logical Lambda function name used for `ColdStart` metrics | [Metrics](core/metrics.md) | `undefined` | | **POWERTOOLS_METRICS_ENABLED** | Explicitly disables emitting metrics to stdout | [Metrics](core/metrics.md) | `true` | | **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](core/tracer.md) | `true` | | **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Capture Lambda or method return as metadata. | [Tracer](core/tracer.md) | `true` | From 1c4c25a618fb8c23e0927e0079c63a0b2ac53c7a Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 13 Mar 2025 23:48:49 -0700 Subject: [PATCH 08/17] refactor(metrics): cleanup code based on sonarqubecloud --- packages/metrics/src/config/EnvironmentVariablesService.ts | 2 +- packages/metrics/tests/unit/logMetrics.test.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/metrics/src/config/EnvironmentVariablesService.ts b/packages/metrics/src/config/EnvironmentVariablesService.ts index 45990d296c..3a2eafb406 100644 --- a/packages/metrics/src/config/EnvironmentVariablesService.ts +++ b/packages/metrics/src/config/EnvironmentVariablesService.ts @@ -12,7 +12,7 @@ class EnvironmentVariablesService implements ConfigServiceInterface { private namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE'; - private functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME'; + private readonly functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME'; private readonly disabledVariable = 'POWERTOOLS_METRICS_DISABLED'; diff --git a/packages/metrics/tests/unit/logMetrics.test.ts b/packages/metrics/tests/unit/logMetrics.test.ts index d6494cc3f5..a0e2a33447 100644 --- a/packages/metrics/tests/unit/logMetrics.test.ts +++ b/packages/metrics/tests/unit/logMetrics.test.ts @@ -90,7 +90,6 @@ describe('LogMetrics decorator & Middy.js middleware', () => { it('default function name in the cold start metric to context.functionName when using decorator', async () => { // Prepare - const functionName = 'function-name'; const metrics = new Metrics({ singleMetric: false, namespace: DEFAULT_NAMESPACE, From 2c816438f0ada721965b336bef0d35572184f1d8 Mon Sep 17 00:00:00 2001 From: Brice Date: Wed, 19 Mar 2025 00:49:16 -0700 Subject: [PATCH 09/17] refactor(metrics): deprecated setFunctionName and expand captureColdStartMetric to support accepting context --- packages/metrics/src/Metrics.ts | 71 ++++++------ .../src/config/EnvironmentVariablesService.ts | 2 +- packages/metrics/src/middleware/middy.ts | 5 +- packages/metrics/src/types/Metrics.ts | 34 +++--- packages/metrics/src/utils.ts | 21 ++++ .../tests/unit/coldStartMetric.test.ts | 56 ++++++++++ .../tests/unit/initializeMetrics.test.ts | 101 +++++++++++++++++- 7 files changed, 224 insertions(+), 66 deletions(-) create mode 100644 packages/metrics/src/utils.ts diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index cede600d74..bb071b5c83 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -29,6 +29,7 @@ import type { MetricsOptions, StoredMetrics, } from './types/index.js'; +import { getFirstDefinedValue } from './utils'; /** * The Metrics utility creates custom metrics asynchronously by logging metrics to standard output following {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html | Amazon CloudWatch Embedded Metric Format (EMF)}. @@ -371,8 +372,10 @@ class Metrics extends Utility implements MetricsInterface { * metrics.captureColdStartMetric(); * }; * ``` + * + * @param functionName - Optional function name to use as `function_name` dimension in the metric. It's used only if the `functionName` constructor parameter or environment variable are not set. */ - public captureColdStartMetric(): void { + public captureColdStartMetric(functionName?: string): void { if (!this.getColdStart()) return; const singleMetric = this.singleMetric(); @@ -381,8 +384,12 @@ class Metrics extends Utility implements MetricsInterface { service: this.defaultDimensions.service, }); } - if (this.functionName) { - singleMetric.addDimension('function_name', this.functionName); + const coldStartFunctionName = getFirstDefinedValue( + this.functionName, + functionName + ); + if (coldStartFunctionName) { + singleMetric.addDimension('function_name', coldStartFunctionName); } singleMetric.addMetric(COLD_START_METRIC, MetricUnits.Count, 1); } @@ -476,17 +483,6 @@ class Metrics extends Utility implements MetricsInterface { return Object.keys(this.storedMetrics).length > 0; } - /** - * Check if a function name has been defined. - * - * This is useful when you want to only set a function name if it is not already set. - * - * The method is primarily intended for internal use, but it is exposed for advanced use cases. - */ - public hasFunctionName(): boolean { - return Boolean(this.functionName); - } - /** * Whether metrics are disabled. */ @@ -554,10 +550,9 @@ class Metrics extends Utility implements MetricsInterface { context: Context, callback: Callback ): Promise { - if (!metricsRef.hasFunctionName()) { - metricsRef.functionName = context.functionName; + if (captureColdStartMetric) { + metricsRef.captureColdStartMetric(context.functionName); } - if (captureColdStartMetric) metricsRef.captureColdStartMetric(); let result: unknown; try { @@ -762,27 +757,13 @@ class Metrics extends Utility implements MetricsInterface { } /** - * Set the function name to be added to each metric as a dimension. - * - * When using the {@link Metrics.logMetrics | `logMetrics()`} decorator, or the Middy.js middleware, the function - * name is automatically inferred from the Lambda context. - * - * @example - * ```typescript - * import { Metrics } from '@aws-lambda-powertools/metrics'; - * - * const metrics = new Metrics({ - * namespace: 'serverlessAirline', - * serviceName: 'orders' - * }); - * - * metrics.setFunctionName('my-function-name'); - * ``` - * - * @param name - The function name + * @deprecated Override the function name for `ColdStart` metrics inferred from the context either via: + * - `functionName` constructor parameter + * - `POWERTOOLS_FUNCTION_NAME` environment variable + * - {@link Metrics.captureColdStartMetric() | `captureColdStartMetric('myFunctionName')`} method */ - public setFunctionName(name?: string): void { - this.functionName = name || this.getEnvVarsService().getFunctionName(); + public setFunctionName(name: string): void { + this.functionName = name; } /** @@ -930,6 +911,20 @@ class Metrics extends Utility implements MetricsInterface { this.envVarsService = new EnvironmentVariablesService(); } + /** + * Set the function name for the cold start metric. + * + * @param functionName - The function name to be used for the cold start metric set in the constructor + */ + protected setFunctionNameForColdStartMetric(functionName?: string): void { + const constructorFunctionName = functionName; + const envFunctionName = this.getEnvVarsService().getFunctionName(); + this.functionName = getFirstDefinedValue( + constructorFunctionName, + envFunctionName + ); + } + /** * Set the namespace to be used. * @@ -974,7 +969,7 @@ class Metrics extends Utility implements MetricsInterface { this.setNamespace(namespace); this.setService(serviceName); this.setDefaultDimensions(defaultDimensions); - this.setFunctionName(functionName); + this.setFunctionNameForColdStartMetric(functionName); this.isSingleMetric = singleMetric || false; return this; diff --git a/packages/metrics/src/config/EnvironmentVariablesService.ts b/packages/metrics/src/config/EnvironmentVariablesService.ts index 3a2eafb406..2f230873b5 100644 --- a/packages/metrics/src/config/EnvironmentVariablesService.ts +++ b/packages/metrics/src/config/EnvironmentVariablesService.ts @@ -11,7 +11,7 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface { - private namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE'; + private readonly namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE'; private readonly functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME'; private readonly disabledVariable = 'POWERTOOLS_METRICS_DISABLED'; diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index 334e467d80..d06a0977e4 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -64,9 +64,6 @@ const logMetrics = ( for (const metrics of metricsInstances) { const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; - if (!metrics.hasFunctionName()) { - metrics.setFunctionName(request.context.functionName); - } if (throwOnEmptyMetrics) { metrics.setThrowOnEmptyMetrics(throwOnEmptyMetrics); } @@ -74,7 +71,7 @@ const logMetrics = ( metrics.setDefaultDimensions(defaultDimensions); } if (captureColdStartMetric) { - metrics.captureColdStartMetric(); + metrics.captureColdStartMetric(request.context.functionName); } } diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index c6b862c8fe..b6251fee9b 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -63,7 +63,13 @@ type MetricsOptions = { /** * Function name to use as dimension for the `ColdStart` metric. * - * @default undefined + * When not provided, the function name is inferred either via: + * - `POWERTOOLS_FUNCTION_NAME` environment variable + * - AWS Lambda function context, **only** when using the {@link MetricsInterface.logMetrics | `logMetrics()`} decorator or the Middy.js middleware + * - `functionName` parameter in the {@link MetricsInterface.captureColdStartMetric | `captureColdStartMetric()`} method + * + * If none of the above are available, the `ColdStart` metric will not include a function name dimension. + * * @see {@link MetricsInterface.setFunctionName | `setFunctionName()`} */ functionName?: string; @@ -279,8 +285,10 @@ interface MetricsInterface { * metrics.captureColdStartMetric(); * }; * ``` + * + * @param functionName - Optional function name to use as `function_name` dimension in the metric. It's used only if the `functionName` constructor parameter or environment variable are not set. */ - captureColdStartMetric(): void; + captureColdStartMetric(functionName?: string): void; /** * Clear all previously set default dimensions. * @@ -452,24 +460,10 @@ interface MetricsInterface { */ setDefaultDimensions(dimensions: Dimensions | undefined): void; /** - * Set the function name to be added to each metric as a dimension. - * - * When using the {@link MetricsInterface.logMetrics | `logMetrics()`} decorator, or the Middy.js middleware, the function - * name is automatically inferred from the Lambda context. - * - * @example - * ```typescript - * import { Metrics } from '@aws-lambda-powertools/metrics'; - * - * const metrics = new Metrics({ - * namespace: 'serverlessAirline', - * serviceName: 'orders' - * }); - * - * metrics.setFunctionName('my-function-name'); - * ``` - * - * @param name - The function name + * @deprecated Override the function name for `ColdStart` metrics inferred from the context either via: + * - `functionName` constructor parameter + * - `POWERTOOLS_FUNCTION_NAME` environment variable + * - {@link Metrics.captureColdStartMetric() | `captureColdStartMetric('myFunctionName')`} method */ setFunctionName(name: string): void; /** diff --git a/packages/metrics/src/utils.ts b/packages/metrics/src/utils.ts new file mode 100644 index 0000000000..ba8b1eea78 --- /dev/null +++ b/packages/metrics/src/utils.ts @@ -0,0 +1,21 @@ +/** + * Get the first value that is defined. + * + * This is useful so that we can define the order of which variables to use + * while also defining an order of fallbacks. + * + * @param values - The incoming strings to verify of they are defined. + */ +const getFirstDefinedValue = ( + ...values: Array +): T | undefined => { + for (const value of values) { + if (value && value.trim().length > 0) { + return value; + } + } + + return undefined; +}; + +export { getFirstDefinedValue }; diff --git a/packages/metrics/tests/unit/coldStartMetric.test.ts b/packages/metrics/tests/unit/coldStartMetric.test.ts index 9a9cba37c9..ff9c3f7f19 100644 --- a/packages/metrics/tests/unit/coldStartMetric.test.ts +++ b/packages/metrics/tests/unit/coldStartMetric.test.ts @@ -86,6 +86,62 @@ describe('ColdStart metric', () => { ); }); + it('does not override the function name from constructor in the cold start metric', () => { + // Prepare + const functionName = 'my-function'; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + functionName: 'another-function', + }); + + // Act + metrics.captureColdStartMetric(functionName); + + // Assess + expect(console.log).toHaveEmittedEMFWith( + expect.objectContaining({ + service: 'hello-world', + [COLD_START_METRIC]: 1, + function_name: 'another-function', + }) + ); + }); + + it.each([ + { + case: 'empty string', + functionName: '', + }, + { + case: 'undefined', + functionName: undefined, + }, + ])( + 'does not include the function name if not set or invalid ($case)', + ({ functionName }) => { + // Prepare + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + }); + + // Act + metrics.captureColdStartMetric(functionName); + + // Assess + expect(console.log).toHaveEmittedEMFWith( + expect.objectContaining({ + service: 'hello-world', + [COLD_START_METRIC]: 1, + }) + ); + expect(console.log).toHaveEmittedEMFWith( + expect.not.objectContaining({ + function_name: 'my-function', + }) + ); + } + ); + it('emits the metric only once', () => { // Prepare const metrics = new Metrics({ diff --git a/packages/metrics/tests/unit/initializeMetrics.test.ts b/packages/metrics/tests/unit/initializeMetrics.test.ts index dc89977c7c..a6b4a62a9d 100644 --- a/packages/metrics/tests/unit/initializeMetrics.test.ts +++ b/packages/metrics/tests/unit/initializeMetrics.test.ts @@ -113,10 +113,31 @@ describe('Initialize Metrics', () => { ); }); + it('prioritizes the function name provided in the constructor', () => { + // Prepare + process.env.POWERTOOLS_METRICS_FUNCTION_NAME = 'another-function'; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + functionName: 'my-function-name', + }); + + // Act + metrics.captureColdStartMetric(); + + // Assess + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedEMFWith( + expect.objectContaining({ + service: 'hello-world', + [COLD_START_METRIC]: 1, + function_name: 'my-function-name', + }) + ); + }); + it('uses the function name provided in the environment variables', () => { // Prepare - process.env.POWERTOOLS_METRICS_FUNCTION_NAME = - 'hello-world-function-name-from-env'; + process.env.POWERTOOLS_METRICS_FUNCTION_NAME = 'another-function'; const metrics = new Metrics({ namespace: DEFAULT_NAMESPACE, }); @@ -130,11 +151,85 @@ describe('Initialize Metrics', () => { expect.objectContaining({ service: 'hello-world', [COLD_START_METRIC]: 1, - function_name: 'hello-world-function-name-from-env', + function_name: 'another-function', }) ); }); + it.each([ + { + case: 'an empty string', + functionName: '', + }, + { + case: 'undefined', + functionName: undefined, + }, + ])( + 'does not set the function name from env when is $case', + ({ functionName }) => { + // Prepare + process.env.POWERTOOLS_METRICS_FUNCTION_NAME = functionName; + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + }); + + // Act + metrics.captureColdStartMetric(); + + // Assess + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedEMFWith( + expect.objectContaining({ + service: 'hello-world', + [COLD_START_METRIC]: 1, + }) + ); + expect(console.log).toHaveEmittedEMFWith( + expect.not.objectContaining({ + function_name: expect.anything(), + }) + ); + } + ); + + it.each([ + { + case: 'an empty string', + functionName: '', + }, + { + case: 'undefined', + functionName: undefined, + }, + ])( + 'does not set the function name from constructor when is $case', + ({ functionName }) => { + // Prepare + const metrics = new Metrics({ + namespace: DEFAULT_NAMESPACE, + functionName, + }); + + // Act + metrics.captureColdStartMetric(); + + // Assess + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveEmittedEMFWith( + expect.objectContaining({ + service: 'hello-world', + [COLD_START_METRIC]: 1, + }) + ); + expect(console.log).toHaveEmittedEMFWith( + expect.not.objectContaining({ + function_name: expect.anything(), + }) + ); + } + ); + it('uses the custom config service provided', () => { // Prepare const configService = { From 1c099b4f7bf8d262554604af62bc2b2d7b389e50 Mon Sep 17 00:00:00 2001 From: Brice Date: Wed, 19 Mar 2025 01:26:22 -0700 Subject: [PATCH 10/17] docs(metrics): update to become more inline with implementation --- docs/core/metrics.md | 40 +++++++++---------- docs/index.md | 34 ++++++++-------- ...ctionName.ts => captureColdStartMetric.ts} | 4 +- 3 files changed, 38 insertions(+), 40 deletions(-) rename examples/snippets/metrics/{setFunctionName.ts => captureColdStartMetric.ts} (82%) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 6008fd7615..9e00c736bd 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -66,12 +66,12 @@ The library requires two settings. You can set them as environment variables, or These settings will be used across all metrics emitted: -| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | -|----------------------|------------------------------------------------------------------|------------------------------------|---------------------|----------------|---------------------|-----------------------| +| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | +|----------------------|------------------------------------------------------------------|------------------------------------|-------------------|----------------|---------------------|-----------------------| | **Service** | Optionally, sets **service** metric dimension across all metrics | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline` | `serviceName` | | **Metric namespace** | Logical container where all metrics will be placed | `POWERTOOLS_METRICS_NAMESPACE` | `default_namespace` | Any string | `serverlessAirline` | `default_namespace` | -| **Function Name** | Logical Lambda function name used for `ColdStart` metrics | `POWERTOOLS_METRICS_FUNCTION_NAME` | `undefined` | Any string | `my-function-name` | `functionName` | -| **Enabled** | Whether to emit metrics to standard output or not | `POWERTOOLS_METRICS_ENABLED` | `true` | Boolean | `false` | | +| **Function Name** | Function name used as dimension for the `ColdStart` metric | `POWERTOOLS_METRICS_FUNCTION_NAME` | [See docs](#capturing-a-cold-start-invocation-as-metric) | Any string | `my-function-name` | `functionName` | +| **Enabled** | Whether to emit metrics to standard output or not | `POWERTOOLS_METRICS_ENABLED` | `true` | Boolean | `false` | | !!! tip Use your application name or main service as the metric namespace to easily group all metrics @@ -232,22 +232,6 @@ You can add default dimensions to your metrics by passing them as parameters in If you'd like to remove them at some point, you can use the `clearDefaultDimensions` method. -### Setting function name - -When emitting cold start metrics, the `function_name` dimension defaults to `context.functionName`. If you want to change the value you can set the `functionName` parameter in the metrics constructor, call `setFunctionName` in the global scope, or define the environment variable `POWERTOOLS_METRICS_FUNCTION_NAME`. - -=== "constructor" - - ```typescript hl_lines="6" - --8<-- "examples/snippets/metrics/functionName.ts" - ``` - -=== "setFunctionName method" - - ```typescript hl_lines="8" - --8<-- "examples/snippets/metrics/setFunctionName.ts" - ``` - ### Changing default timestamp When creating metrics, we use the current timestamp. If you want to change the timestamp of all the metrics you create, utilize the `setTimestamp` function. You can specify a datetime object or an integer representing an epoch timestamp in milliseconds. @@ -423,6 +407,22 @@ This has the advantage of keeping cold start metric separate from your applicati !!! info "We do not emit 0 as a value for the ColdStart metric for cost-efficiency reasons. [Let us know](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new?assignees=&labels=feature-request%2C+triage&template=feature_request.md&title=) if you'd prefer a flag to override it." +#### Setting function name + +When emitting cold start metrics, the `function_name` dimension defaults to `context.functionName`. If you want to change the value you can set the `functionName` parameter in the metrics constructor, define the environment variable `POWERTOOLS_METRICS_FUNCTION_NAME`, or pass a value to `captureColdStartMetric`. + +=== "constructor" + + ```typescript hl_lines="6" + --8<-- "examples/snippets/metrics/functionName.ts" + ``` + +=== "captureColdStartMetric method" + + ```typescript hl_lines="8" + --8<-- "examples/snippets/metrics/captureColdStartMetric.ts" + ``` + ## Advanced ### Adding metadata diff --git a/docs/index.md b/docs/index.md index ddf65e3acf..0deee5e4b1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -351,23 +351,23 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al ???+ info Explicit parameters take precedence over environment variables -| Environment variable | Description | Utility | Default | -| -------------------------------------------- |---------------------------------------------------------------------------------------------------------------| --------------------------------------- | ------------------- | -| **POWERTOOLS_SERVICE_NAME** | Set service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` | -| **POWERTOOLS_METRICS_NAMESPACE** | Set namespace used for metrics | [Metrics](core/metrics.md) | `default_namespace` | -| **POWERTOOLS_METRICS_FUNCTION_NAME** | Logical Lambda function name used for `ColdStart` metrics | [Metrics](core/metrics.md) | `undefined` | -| **POWERTOOLS_METRICS_ENABLED** | Explicitly disables emitting metrics to stdout | [Metrics](core/metrics.md) | `true` | -| **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Capture Lambda or method return as metadata. | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Capture Lambda or method exception as metadata. | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Capture HTTP(s) requests as segments. | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_LOGGER_LOG_EVENT** | Log incoming event | [Logger](core/logger.md) | `false` | -| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](core/logger.md) | `0` | -| **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](core/logger.md) | `false` | -| **POWERTOOLS_LOG_LEVEL** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | [Logger](core/logger.md) | `INFO` | -| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](utilities/parameters.md) | `5` | -| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Set whether to decrypt or not values retrieved from AWS Systems Manager Parameters Store | [Parameters](utilities/parameters.md) | `false` | -| **POWERTOOLS_IDEMPOTENCY_DISABLED** | Disable the Idempotency logic without changing your code, useful for testing | [Idempotency](utilities/idempotency.md) | `false` | +| Environment variable | Description | Utility | Default | +| -------------------------------------------- |---------------------------------------------------------------------------------------------------------------| --------------------------------------- |-----------------------------| +| **POWERTOOLS_SERVICE_NAME** | Set service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` | +| **POWERTOOLS_METRICS_NAMESPACE** | Set namespace used for metrics | [Metrics](core/metrics.md) | `default_namespace` | +| **POWERTOOLS_METRICS_FUNCTION_NAME** | Function name used as dimension for the `ColdStart` metric | [Metrics](core/metrics.md) | [See docs](core/metrics/#capturing-a-cold-start-invocation-as-metric) | +| **POWERTOOLS_METRICS_ENABLED** | Explicitly disables emitting metrics to stdout | [Metrics](core/metrics.md) | `true` | +| **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Capture Lambda or method return as metadata. | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Capture Lambda or method exception as metadata. | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Capture HTTP(s) requests as segments. | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_LOGGER_LOG_EVENT** | Log incoming event | [Logger](core/logger.md) | `false` | +| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](core/logger.md) | `0` | +| **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](core/logger.md) | `false` | +| **POWERTOOLS_LOG_LEVEL** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | [Logger](core/logger.md) | `INFO` | +| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](utilities/parameters.md) | `5` | +| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Set whether to decrypt or not values retrieved from AWS Systems Manager Parameters Store | [Parameters](utilities/parameters.md) | `false` | +| **POWERTOOLS_IDEMPOTENCY_DISABLED** | Disable the Idempotency logic without changing your code, useful for testing | [Idempotency](utilities/idempotency.md) | `false` | Each Utility page provides information on example values and allowed values. diff --git a/examples/snippets/metrics/setFunctionName.ts b/examples/snippets/metrics/captureColdStartMetric.ts similarity index 82% rename from examples/snippets/metrics/setFunctionName.ts rename to examples/snippets/metrics/captureColdStartMetric.ts index 675574667b..0657cb64a5 100644 --- a/examples/snippets/metrics/setFunctionName.ts +++ b/examples/snippets/metrics/captureColdStartMetric.ts @@ -5,14 +5,12 @@ const metrics = new Metrics({ serviceName: 'orders', }); -metrics.setFunctionName('my-function-name'); - export const handler = async ( _event: unknown, _context: unknown ): Promise => { // Capture cold start metric - metrics.captureColdStartMetric(); + metrics.captureColdStartMetric('my-function-name'); metrics.addMetric('successfulBooking', MetricUnit.Count, 1); From 61cfd8c47aa0154bd27cad4341fb6a4100f67336 Mon Sep 17 00:00:00 2001 From: Brice Date: Wed, 19 Mar 2025 01:33:52 -0700 Subject: [PATCH 11/17] doc updates --- docs/core/metrics.md | 6 ++++++ packages/metrics/src/Metrics.ts | 2 +- packages/metrics/src/types/Metrics.ts | 2 +- packages/metrics/src/utils.ts | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 9e00c736bd..567646db17 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -411,6 +411,12 @@ This has the advantage of keeping cold start metric separate from your applicati When emitting cold start metrics, the `function_name` dimension defaults to `context.functionName`. If you want to change the value you can set the `functionName` parameter in the metrics constructor, define the environment variable `POWERTOOLS_METRICS_FUNCTION_NAME`, or pass a value to `captureColdStartMetric`. +The priority of the `function_name` is defined as: + +1. `functionName` constructor option +2. `POWERTOOLS_METRICS_FUNCTION_NAME` environment variable +3. `context.functionName` if using logMetrics decorator or Middy middleware OR override function name in `captureColdStartMetric` call + === "constructor" ```typescript hl_lines="6" diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index bb071b5c83..fbabfdec93 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -760,7 +760,7 @@ class Metrics extends Utility implements MetricsInterface { * @deprecated Override the function name for `ColdStart` metrics inferred from the context either via: * - `functionName` constructor parameter * - `POWERTOOLS_FUNCTION_NAME` environment variable - * - {@link Metrics.captureColdStartMetric() | `captureColdStartMetric('myFunctionName')`} method + * - {@link Metrics.captureColdStartMetric | `captureColdStartMetric('myFunctionName')`} method */ public setFunctionName(name: string): void { this.functionName = name; diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index b6251fee9b..a964b3953e 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -463,7 +463,7 @@ interface MetricsInterface { * @deprecated Override the function name for `ColdStart` metrics inferred from the context either via: * - `functionName` constructor parameter * - `POWERTOOLS_FUNCTION_NAME` environment variable - * - {@link Metrics.captureColdStartMetric() | `captureColdStartMetric('myFunctionName')`} method + * - {@link Metrics.captureColdStartMetric | `captureColdStartMetric('myFunctionName')`} method */ setFunctionName(name: string): void; /** diff --git a/packages/metrics/src/utils.ts b/packages/metrics/src/utils.ts index ba8b1eea78..a752ead2a8 100644 --- a/packages/metrics/src/utils.ts +++ b/packages/metrics/src/utils.ts @@ -1,5 +1,5 @@ /** - * Get the first value that is defined. + * Return the first non-empty string value that is defined. * * This is useful so that we can define the order of which variables to use * while also defining an order of fallbacks. From 2378cb2228941800c0266538a3c46f975c4aeba3 Mon Sep 17 00:00:00 2001 From: Brice Date: Wed, 19 Mar 2025 01:49:39 -0700 Subject: [PATCH 12/17] more doc updates --- docs/core/metrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 567646db17..0794cc2ee3 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -411,11 +411,11 @@ This has the advantage of keeping cold start metric separate from your applicati When emitting cold start metrics, the `function_name` dimension defaults to `context.functionName`. If you want to change the value you can set the `functionName` parameter in the metrics constructor, define the environment variable `POWERTOOLS_METRICS_FUNCTION_NAME`, or pass a value to `captureColdStartMetric`. -The priority of the `function_name` is defined as: +The priority of the `function_name` dimension value is defined as: 1. `functionName` constructor option 2. `POWERTOOLS_METRICS_FUNCTION_NAME` environment variable -3. `context.functionName` if using logMetrics decorator or Middy middleware OR override function name in `captureColdStartMetric` call +3. The value passed in the `captureColdStartMetric` call, or `context.functionName` if using logMetrics decorator or Middy middleware === "constructor" From f5e5ac0b0a51a960529244eef0e34127c50627e4 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 20 Mar 2025 13:13:23 +0100 Subject: [PATCH 13/17] chore: align with suggested implementation --- docs/core/metrics.md | 14 ++++++------ .../metrics/captureColdStartMetric.ts | 1 - examples/snippets/metrics/functionName.ts | 1 - packages/metrics/src/Metrics.ts | 22 +++++++++---------- .../src/config/EnvironmentVariablesService.ts | 1 - packages/metrics/src/types/Metrics.ts | 2 +- packages/metrics/src/utils.ts | 21 ------------------ 7 files changed, 19 insertions(+), 43 deletions(-) delete mode 100644 packages/metrics/src/utils.ts diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 0794cc2ee3..f2d61eabf9 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -66,12 +66,12 @@ The library requires two settings. You can set them as environment variables, or These settings will be used across all metrics emitted: -| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | -|----------------------|------------------------------------------------------------------|------------------------------------|-------------------|----------------|---------------------|-----------------------| -| **Service** | Optionally, sets **service** metric dimension across all metrics | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline` | `serviceName` | -| **Metric namespace** | Logical container where all metrics will be placed | `POWERTOOLS_METRICS_NAMESPACE` | `default_namespace` | Any string | `serverlessAirline` | `default_namespace` | -| **Function Name** | Function name used as dimension for the `ColdStart` metric | `POWERTOOLS_METRICS_FUNCTION_NAME` | [See docs](#capturing-a-cold-start-invocation-as-metric) | Any string | `my-function-name` | `functionName` | -| **Enabled** | Whether to emit metrics to standard output or not | `POWERTOOLS_METRICS_ENABLED` | `true` | Boolean | `false` | | +| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | +|----------------------|------------------------------------------------------------------|------------------------------------|----------------------------------------------------------|----------------|---------------------|-----------------------| +| **Service** | Optionally, sets **service** metric dimension across all metrics | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline` | `serviceName` | +| **Metric namespace** | Logical container where all metrics will be placed | `POWERTOOLS_METRICS_NAMESPACE` | `default_namespace` | Any string | `serverlessAirline` | `default_namespace` | +| **Function Name** | Function name used as dimension for the `ColdStart` metric | `POWERTOOLS_METRICS_FUNCTION_NAME` | [See docs](#capturing-a-cold-start-invocation-as-metric) | Any string | `my-function-name` | `functionName` | +| **Enabled** | Whether to emit metrics to standard output or not | `POWERTOOLS_METRICS_ENABLED` | `true` | Boolean | `false` | | !!! tip Use your application name or main service as the metric namespace to easily group all metrics @@ -425,7 +425,7 @@ The priority of the `function_name` dimension value is defined as: === "captureColdStartMetric method" - ```typescript hl_lines="8" + ```typescript hl_lines="12" --8<-- "examples/snippets/metrics/captureColdStartMetric.ts" ``` diff --git a/examples/snippets/metrics/captureColdStartMetric.ts b/examples/snippets/metrics/captureColdStartMetric.ts index 0657cb64a5..289934f9a7 100644 --- a/examples/snippets/metrics/captureColdStartMetric.ts +++ b/examples/snippets/metrics/captureColdStartMetric.ts @@ -9,7 +9,6 @@ export const handler = async ( _event: unknown, _context: unknown ): Promise => { - // Capture cold start metric metrics.captureColdStartMetric('my-function-name'); metrics.addMetric('successfulBooking', MetricUnit.Count, 1); diff --git a/examples/snippets/metrics/functionName.ts b/examples/snippets/metrics/functionName.ts index 6a047d6fca..2811a45af1 100644 --- a/examples/snippets/metrics/functionName.ts +++ b/examples/snippets/metrics/functionName.ts @@ -10,7 +10,6 @@ export const handler = async ( _event: unknown, _context: unknown ): Promise => { - // Capture cold start metric metrics.captureColdStartMetric(); metrics.addMetric('successfulBooking', MetricUnit.Count, 1); diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index fbabfdec93..376b665fa4 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -29,7 +29,6 @@ import type { MetricsOptions, StoredMetrics, } from './types/index.js'; -import { getFirstDefinedValue } from './utils'; /** * The Metrics utility creates custom metrics asynchronously by logging metrics to standard output following {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html | Amazon CloudWatch Embedded Metric Format (EMF)}. @@ -384,12 +383,11 @@ class Metrics extends Utility implements MetricsInterface { service: this.defaultDimensions.service, }); } - const coldStartFunctionName = getFirstDefinedValue( - this.functionName, - functionName - ); - if (coldStartFunctionName) { - singleMetric.addDimension('function_name', coldStartFunctionName); + for (const value of [this.functionName, functionName]) { + if (value && value.trim().length > 0) { + singleMetric.addDimension('function_name', value); + break; + } } singleMetric.addMetric(COLD_START_METRIC, MetricUnits.Count, 1); } @@ -919,10 +917,12 @@ class Metrics extends Utility implements MetricsInterface { protected setFunctionNameForColdStartMetric(functionName?: string): void { const constructorFunctionName = functionName; const envFunctionName = this.getEnvVarsService().getFunctionName(); - this.functionName = getFirstDefinedValue( - constructorFunctionName, - envFunctionName - ); + for (const value of [constructorFunctionName, envFunctionName]) { + if (value && value.trim().length > 0) { + this.functionName = value; + break; + } + } } /** diff --git a/packages/metrics/src/config/EnvironmentVariablesService.ts b/packages/metrics/src/config/EnvironmentVariablesService.ts index 2f230873b5..6fb6c6719c 100644 --- a/packages/metrics/src/config/EnvironmentVariablesService.ts +++ b/packages/metrics/src/config/EnvironmentVariablesService.ts @@ -13,7 +13,6 @@ class EnvironmentVariablesService { private readonly namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE'; private readonly functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME'; - private readonly disabledVariable = 'POWERTOOLS_METRICS_DISABLED'; /** diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index a964b3953e..ccd914351e 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -463,7 +463,7 @@ interface MetricsInterface { * @deprecated Override the function name for `ColdStart` metrics inferred from the context either via: * - `functionName` constructor parameter * - `POWERTOOLS_FUNCTION_NAME` environment variable - * - {@link Metrics.captureColdStartMetric | `captureColdStartMetric('myFunctionName')`} method + * - {@link MetricsInterface.captureColdStartMetric | `captureColdStartMetric()`} method */ setFunctionName(name: string): void; /** diff --git a/packages/metrics/src/utils.ts b/packages/metrics/src/utils.ts deleted file mode 100644 index a752ead2a8..0000000000 --- a/packages/metrics/src/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Return the first non-empty string value that is defined. - * - * This is useful so that we can define the order of which variables to use - * while also defining an order of fallbacks. - * - * @param values - The incoming strings to verify of they are defined. - */ -const getFirstDefinedValue = ( - ...values: Array -): T | undefined => { - for (const value of values) { - if (value && value.trim().length > 0) { - return value; - } - } - - return undefined; -}; - -export { getFirstDefinedValue }; From 21dd0c804384e431d117c1e9b3f4fca229cb5a30 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 20 Mar 2025 13:15:45 +0100 Subject: [PATCH 14/17] chore: ignore deprecated method from coverage --- packages/metrics/src/Metrics.ts | 4 ++-- .../tests/unit/coldStartMetric.test.ts | 21 ------------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 376b665fa4..1be10686cd 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -760,9 +760,9 @@ class Metrics extends Utility implements MetricsInterface { * - `POWERTOOLS_FUNCTION_NAME` environment variable * - {@link Metrics.captureColdStartMetric | `captureColdStartMetric('myFunctionName')`} method */ - public setFunctionName(name: string): void { + /* v8 ignore start */ public setFunctionName(name: string): void { this.functionName = name; - } + } /* v8 ignore end */ /** * Set the flag to throw an error if no metrics are emitted. diff --git a/packages/metrics/tests/unit/coldStartMetric.test.ts b/packages/metrics/tests/unit/coldStartMetric.test.ts index ff9c3f7f19..e63ee9eb72 100644 --- a/packages/metrics/tests/unit/coldStartMetric.test.ts +++ b/packages/metrics/tests/unit/coldStartMetric.test.ts @@ -65,27 +65,6 @@ describe('ColdStart metric', () => { ); }); - it('includes the function name in the cold start metric', () => { - // Prepare - const functionName = 'my-function'; - const metrics = new Metrics({ - namespace: DEFAULT_NAMESPACE, - }); - metrics.setFunctionName(functionName); - - // Act - metrics.captureColdStartMetric(); - - // Assess - expect(console.log).toHaveEmittedEMFWith( - expect.objectContaining({ - service: 'hello-world', - [COLD_START_METRIC]: 1, - function_name: 'my-function', - }) - ); - }); - it('does not override the function name from constructor in the cold start metric', () => { // Prepare const functionName = 'my-function'; From 29a6280d1cc5445ea0530d1364623f7c5e405b10 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 20 Mar 2025 13:22:40 +0100 Subject: [PATCH 15/17] chore: format table --- docs/index.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0deee5e4b1..cb6256f2ba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -351,23 +351,23 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al ???+ info Explicit parameters take precedence over environment variables -| Environment variable | Description | Utility | Default | -| -------------------------------------------- |---------------------------------------------------------------------------------------------------------------| --------------------------------------- |-----------------------------| -| **POWERTOOLS_SERVICE_NAME** | Set service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` | -| **POWERTOOLS_METRICS_NAMESPACE** | Set namespace used for metrics | [Metrics](core/metrics.md) | `default_namespace` | -| **POWERTOOLS_METRICS_FUNCTION_NAME** | Function name used as dimension for the `ColdStart` metric | [Metrics](core/metrics.md) | [See docs](core/metrics/#capturing-a-cold-start-invocation-as-metric) | -| **POWERTOOLS_METRICS_ENABLED** | Explicitly disables emitting metrics to stdout | [Metrics](core/metrics.md) | `true` | -| **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Capture Lambda or method return as metadata. | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Capture Lambda or method exception as metadata. | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Capture HTTP(s) requests as segments. | [Tracer](core/tracer.md) | `true` | -| **POWERTOOLS_LOGGER_LOG_EVENT** | Log incoming event | [Logger](core/logger.md) | `false` | -| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](core/logger.md) | `0` | -| **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](core/logger.md) | `false` | -| **POWERTOOLS_LOG_LEVEL** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | [Logger](core/logger.md) | `INFO` | -| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](utilities/parameters.md) | `5` | -| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Set whether to decrypt or not values retrieved from AWS Systems Manager Parameters Store | [Parameters](utilities/parameters.md) | `false` | -| **POWERTOOLS_IDEMPOTENCY_DISABLED** | Disable the Idempotency logic without changing your code, useful for testing | [Idempotency](utilities/idempotency.md) | `false` | +| Environment variable | Description | Utility | Default | +| -------------------------------------------- |------------------------------------------------------------------------------------------| --------------------------------------- |-------------------------------------------------| +| **POWERTOOLS_SERVICE_NAME** | Set service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` | +| **POWERTOOLS_METRICS_NAMESPACE** | Set namespace used for metrics | [Metrics](core/metrics.md) | `default_namespace` | +| **POWERTOOLS_METRICS_FUNCTION_NAME** | Function name used as dimension for the `ColdStart` metric | [Metrics](core/metrics.md) | [See docs](core/metrics/#setting-function-name) | +| **POWERTOOLS_METRICS_ENABLED** | Explicitly disables emitting metrics to stdout | [Metrics](core/metrics.md) | `true` | +| **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Capture Lambda or method return as metadata. | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Capture Lambda or method exception as metadata. | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Capture HTTP(s) requests as segments. | [Tracer](core/tracer.md) | `true` | +| **POWERTOOLS_LOGGER_LOG_EVENT** | Log incoming event | [Logger](core/logger.md) | `false` | +| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](core/logger.md) | `0` | +| **POWERTOOLS_DEV** | Pretty-print logs, disable metrics flushing, and disable traces - use for dev only | See section below | `false` | +| **POWERTOOLS_LOG_LEVEL** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | [Logger](core/logger.md) | `INFO` | +| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](utilities/parameters.md) | `5` | +| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Set whether to decrypt or not values retrieved from AWS Systems Manager Parameters Store | [Parameters](utilities/parameters.md) | `false` | +| **POWERTOOLS_IDEMPOTENCY_DISABLED** | Disable the Idempotency logic without changing your code, useful for testing | [Idempotency](utilities/idempotency.md) | `false` | Each Utility page provides information on example values and allowed values. From d69c2a099ac4bd5045ae56a0bcf978d114ba7b90 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 20 Mar 2025 15:16:38 +0100 Subject: [PATCH 16/17] chore: address review comments --- packages/metrics/src/Metrics.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 1be10686cd..68d46a941a 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -383,11 +383,9 @@ class Metrics extends Utility implements MetricsInterface { service: this.defaultDimensions.service, }); } - for (const value of [this.functionName, functionName]) { - if (value && value.trim().length > 0) { - singleMetric.addDimension('function_name', value); - break; - } + const value = this.functionName?.trim() || functionName?.trim(); + if (value && value.length > 0) { + singleMetric.addDimension('function_name', value); } singleMetric.addMetric(COLD_START_METRIC, MetricUnits.Count, 1); } @@ -915,13 +913,10 @@ class Metrics extends Utility implements MetricsInterface { * @param functionName - The function name to be used for the cold start metric set in the constructor */ protected setFunctionNameForColdStartMetric(functionName?: string): void { - const constructorFunctionName = functionName; - const envFunctionName = this.getEnvVarsService().getFunctionName(); - for (const value of [constructorFunctionName, envFunctionName]) { - if (value && value.trim().length > 0) { - this.functionName = value; - break; - } + const value = + functionName?.trim() || this.getEnvVarsService().getFunctionName().trim(); + if (value && value.length > 0) { + this.functionName = value; } } From 9631937c2289a42e37cfc90bfc962085fa646e9c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 20 Mar 2025 15:19:08 +0100 Subject: [PATCH 17/17] chore: address sonar --- packages/metrics/src/Metrics.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 68d46a941a..a3a743e96d 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -383,7 +383,7 @@ class Metrics extends Utility implements MetricsInterface { service: this.defaultDimensions.service, }); } - const value = this.functionName?.trim() || functionName?.trim(); + const value = this.functionName?.trim() ?? functionName?.trim(); if (value && value.length > 0) { singleMetric.addDimension('function_name', value); } @@ -914,7 +914,7 @@ class Metrics extends Utility implements MetricsInterface { */ protected setFunctionNameForColdStartMetric(functionName?: string): void { const value = - functionName?.trim() || this.getEnvVarsService().getFunctionName().trim(); + functionName?.trim() ?? this.getEnvVarsService().getFunctionName().trim(); if (value && value.length > 0) { this.functionName = value; }