From 8fbe2dd1eddf6247dd7c55915e7b66e1a8ad82c2 Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Fri, 17 Dec 2021 22:04:44 +0100 Subject: [PATCH 01/29] feat(metric): add logic, unit tests for the middy middleware --- packages/metrics/.eslintrc.json | 231 ------------------ packages/metrics/lerna | 0 packages/metrics/package-lock.json | 18 +- packages/metrics/package.json | 1 + packages/metrics/src/Metrics.ts | 17 +- packages/metrics/src/MetricsInterface.ts | 28 +-- packages/metrics/src/middleware/index.ts | 1 + packages/metrics/src/middleware/middy.ts | 32 +++ packages/metrics/src/types/Metrics.ts | 42 ++-- packages/metrics/tests/unit/Metrics.test.ts | 18 +- .../tests/unit/middleware/middy.test.ts | 147 +++++++++++ packages/metrics/tsconfig.json | 3 +- 12 files changed, 259 insertions(+), 279 deletions(-) delete mode 100644 packages/metrics/.eslintrc.json delete mode 100644 packages/metrics/lerna create mode 100644 packages/metrics/src/middleware/index.ts create mode 100644 packages/metrics/src/middleware/middy.ts create mode 100644 packages/metrics/tests/unit/middleware/middy.test.ts diff --git a/packages/metrics/.eslintrc.json b/packages/metrics/.eslintrc.json deleted file mode 100644 index 75b9d88bb5..0000000000 --- a/packages/metrics/.eslintrc.json +++ /dev/null @@ -1,231 +0,0 @@ -{ - "env": { - "jest": true, - "node": true - }, - "root": true, - "plugins": [ - "@typescript-eslint", - "import" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module", - "project": "./tsconfig.es.json" - }, - "extends": [ - "plugin:import/typescript" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] - }, - "import/resolver": { - "node": {}, - "typescript": { - "project": "./tsconfig.es.json", - "alwaysTryTypes": true - } - } - }, - "ignorePatterns": [ - "*.js", - "!.projenrc.js", - "*.d.ts", - "node_modules/", - "*.generated.ts", - "coverage" - ], - "rules": { - "indent": [ - "off" - ], - "@typescript-eslint/indent": [ - "error", - 2 - ], - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "comma-dangle": [ - "error", - "always-multiline" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "array-bracket-spacing": [ - "error", - "never" - ], - "array-bracket-newline": [ - "error", - "consistent" - ], - "object-curly-spacing": [ - "error", - "always" - ], - "object-curly-newline": [ - "error", - { - "multiline": true, - "consistent": true - } - ], - "object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true - } - ], - "keyword-spacing": [ - "error" - ], - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "space-before-blocks": [ - "error" - ], - "curly": [ - "error", - "multi-line", - "consistent" - ], - "@typescript-eslint/member-delimiter-style": [ - "error" - ], - "semi": [ - "error", - "always" - ], - "max-len": [ - "error", - { - "code": 150, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - "ignoreRegExpLiterals": true - } - ], - "quote-props": [ - "error", - "consistent-as-needed" - ], - "@typescript-eslint/no-require-imports": [ - "error" - ], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/test/**", - "**/build-tools/**" - ], - "optionalDependencies": false, - "peerDependencies": true - } - ], - "import/no-unresolved": [ - "error" - ], - "import/order": [ - "warn", - { - "groups": [ - "builtin", - "external" - ], - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ], - "no-duplicate-imports": [ - "error" - ], - "no-shadow": [ - "off" - ], - "@typescript-eslint/no-shadow": [ - "error" - ], - "key-spacing": [ - "error" - ], - "no-multiple-empty-lines": [ - "error" - ], - "@typescript-eslint/no-floating-promises": [ - "error" - ], - "no-return-await": [ - "off" - ], - "@typescript-eslint/return-await": [ - "error" - ], - "no-trailing-spaces": [ - "error" - ], - "dot-notation": [ - "error" - ], - "no-bitwise": [ - "error" - ], - "@typescript-eslint/member-ordering": [ - "error", - { - "default": [ - "public-static-field", - "public-static-method", - "protected-static-field", - "protected-static-method", - "private-static-field", - "private-static-method", - "field", - "constructor", - "method" - ] - } - ] - }, - "overrides": [ - { - "files": [ - ".projenrc.js" - ], - "rules": { - "@typescript-eslint/no-require-imports": "off", - "import/no-extraneous-dependencies": "off" - } - } - ] -} diff --git a/packages/metrics/lerna b/packages/metrics/lerna deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/metrics/package-lock.json b/packages/metrics/package-lock.json index 7690819460..d7dbce7463 100644 --- a/packages/metrics/package-lock.json +++ b/packages/metrics/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@aws-lambda-powertools/metrics", - "version": "0.0.0", + "version": "0.2.0-beta.3", "license": "MIT-0", "dependencies": { "@aws-lambda-powertools/commons": "^0.0.2", @@ -14,6 +14,7 @@ }, "devDependencies": { "@commitlint/cli": "^15.0.0", + "@middy/core": "^2.5.3", "@types/aws-lambda": "^8.10.72", "@types/jest": "^27.0.0", "@types/node": "^16.6.0", @@ -1290,6 +1291,15 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@middy/core": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@middy/core/-/core-2.5.3.tgz", + "integrity": "sha512-hCXlRglDu48sl03IjDGjcJwfwElIPAf0fwBu08kqE80qpnouZ1hGH1lZbkbJjAhz6DkSR+79YArUQKUYaM2k1g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8133,6 +8143,12 @@ "chalk": "^4.0.0" } }, + "@middy/core": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@middy/core/-/core-2.5.3.tgz", + "integrity": "sha512-hCXlRglDu48sl03IjDGjcJwfwElIPAf0fwBu08kqE80qpnouZ1hGH1lZbkbJjAhz6DkSR+79YArUQKUYaM2k1g==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 311a44ccbc..4d788a6a86 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -37,6 +37,7 @@ "typedocMain": "src/index.ts", "devDependencies": { "@commitlint/cli": "^15.0.0", + "@middy/core": "^2.5.3", "@types/aws-lambda": "^8.10.72", "@types/jest": "^27.0.0", "@types/node": "^16.6.0", diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index e61cd12b31..62ea4930d3 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -1,12 +1,12 @@ import { MetricsInterface } from '.'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; import { - DecoratorOptions, + MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, StoredMetrics, - MetricsOptions, + ExtraOptions, MetricUnit, MetricUnits, } from './types'; @@ -165,7 +165,6 @@ class Metrics implements MetricsInterface { this.storedMetrics = {}; } - /** * Throw an Error if the metrics buffer is empty. * @@ -212,7 +211,7 @@ class Metrics implements MetricsInterface { * * @decorator Class */ - public logMetrics(options: DecoratorOptions = {}): HandlerMethodDecorator { + public logMetrics(options: ExtraOptions = {}): HandlerMethodDecorator { const { raiseOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; if (raiseOnEmptyMetrics) { this.raiseOnEmptyMetrics(); @@ -228,7 +227,8 @@ class Metrics implements MetricsInterface { if (captureColdStartMetric) this.captureColdStartMetric(); try { - const result = originalMethod?.apply(this, [event, context, callback]); + const result = originalMethod?.apply(this, [ event, context, callback ]); + return result; } finally { this.purgeStoredMetrics(); @@ -284,7 +284,7 @@ class Metrics implements MetricsInterface { {}, ); - const dimensionNames = [...Object.keys(this.defaultDimensions), ...Object.keys(this.dimensions)]; + const dimensionNames = [ ...Object.keys(this.defaultDimensions), ...Object.keys(this.dimensions) ]; return { _aws: { @@ -334,6 +334,7 @@ class Metrics implements MetricsInterface { return new Metrics({ namespace: this.namespace, service: this.dimensions.service, + defaultDimensions: this.defaultDimensions, singleMetric: true, }); } @@ -373,6 +374,10 @@ class Metrics implements MetricsInterface { singleMetric.addMetric('ColdStart', MetricUnits.Count, 1); } + public setFunctionName(value: string): void { + this.functionName = value; + } + private getCurrentDimensionsCount(): number { return Object.keys(this.dimensions).length + Object.keys(this.defaultDimensions).length; } diff --git a/packages/metrics/src/MetricsInterface.ts b/packages/metrics/src/MetricsInterface.ts index 5375003032..00e26ba8aa 100644 --- a/packages/metrics/src/MetricsInterface.ts +++ b/packages/metrics/src/MetricsInterface.ts @@ -1,20 +1,20 @@ import { Metrics } from './Metrics'; -import { MetricUnit, EmfOutput, HandlerMethodDecorator, Dimensions, DecoratorOptions } from './types'; +import { MetricUnit, EmfOutput, HandlerMethodDecorator, Dimensions, MetricsOptions } from './types'; interface MetricsInterface { - addDimension(name: string, value: string): void; - addDimensions(dimensions: {[key: string]: string}): void; - addMetadata(key: string, value: string): void; - addMetric(name: string, unit:MetricUnit, value:number): void; - clearDimensions(): void; - clearMetadata(): void; - clearMetrics(): void; - clearDefaultDimensions(): void; - logMetrics(options?: DecoratorOptions): HandlerMethodDecorator; - purgeStoredMetrics(): void; - serializeMetrics(): EmfOutput; - setDefaultDimensions(dimensions: Dimensions | undefined): void; - singleMetric(): Metrics; + addDimension(name: string, value: string): void + addDimensions(dimensions: {[key: string]: string}): void + addMetadata(key: string, value: string): void + addMetric(name: string, unit:MetricUnit, value:number): void + clearDimensions(): void + clearMetadata(): void + clearMetrics(): void + clearDefaultDimensions(): void + logMetrics(options?: MetricsOptions): HandlerMethodDecorator + purgeStoredMetrics(): void + serializeMetrics(): EmfOutput + setDefaultDimensions(dimensions: Dimensions | undefined): void + singleMetric(): Metrics } export { diff --git a/packages/metrics/src/middleware/index.ts b/packages/metrics/src/middleware/index.ts new file mode 100644 index 0000000000..fcc794fc08 --- /dev/null +++ b/packages/metrics/src/middleware/index.ts @@ -0,0 +1 @@ +export * from './middy'; \ No newline at end of file diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts new file mode 100644 index 0000000000..00e6ea9be5 --- /dev/null +++ b/packages/metrics/src/middleware/middy.ts @@ -0,0 +1,32 @@ +import type { Metrics } from '../Metrics'; +import middy from '@middy/core'; +import { ExtraOptions } from '../types'; + +const logMetrics = (metrics: Metrics, options: ExtraOptions = {}): middy.MiddlewareObj => { + const logMetricsBefore = async (request: middy.Request): Promise => { + metrics.setFunctionName(request.context.functionName); + const { raiseOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; + if (raiseOnEmptyMetrics !== undefined) { + metrics.raiseOnEmptyMetrics(); + } + if (defaultDimensions !== undefined) { + metrics.setDefaultDimensions(defaultDimensions); + } + if (captureColdStartMetric !== undefined) { + metrics.captureColdStartMetric(); + } + }; + + const logMetricsAfter = async (): Promise => { + metrics.purgeStoredMetrics(); + }; + + return { + before: logMetricsBefore, + after: logMetricsAfter + }; +}; + +export { + logMetrics, +}; \ No newline at end of file diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index d3f7219b0d..5670902f16 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -6,23 +6,23 @@ import { MetricUnit } from './MetricUnit'; type Dimensions = { [key: string]: string }; type MetricsOptions = { - customConfigService?: ConfigServiceInterface; - namespace?: string; - service?: string; - singleMetric?: boolean; - defaultDimensions?: Dimensions; + customConfigService?: ConfigServiceInterface + namespace?: string + service?: string + singleMetric?: boolean + defaultDimensions?: Dimensions }; type EmfOutput = { - [key: string]: string | number | object; + [key: string]: string | number | object _aws: { - Timestamp: number; + Timestamp: number CloudWatchMetrics: { - Namespace: string; - Dimensions: [string[]]; + Namespace: string + Dimensions: [string[]] Metrics: { Name: string; Unit: MetricUnit }[]; - }[]; - }; + }[] + } }; type HandlerMethodDecorator = ( @@ -38,7 +38,7 @@ type HandlerMethodDecorator = ( * * ```typescript * - * const metricsOptions: DecoratorOptions = { + * const metricsOptions: MetricsOptions = { * raiseOnEmptyMetrics: true, * defaultDimensions: {'environment': 'dev'}, * captureColdStartMetric: true, @@ -50,20 +50,20 @@ type HandlerMethodDecorator = ( * } * ``` */ -type DecoratorOptions = { - raiseOnEmptyMetrics?: boolean; - defaultDimensions?: Dimensions; - captureColdStartMetric?: boolean; +type ExtraOptions = { + raiseOnEmptyMetrics?: boolean + defaultDimensions?: Dimensions + captureColdStartMetric?: boolean }; type StoredMetric = { - name: string; - unit: MetricUnit; - value: number; + name: string + unit: MetricUnit + value: number }; type StoredMetrics = { - [key: string]: StoredMetric; + [key: string]: StoredMetric }; -export { DecoratorOptions, Dimensions, EmfOutput, HandlerMethodDecorator, MetricsOptions, StoredMetrics }; +export { MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, ExtraOptions, StoredMetrics }; diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index f6b3c5c310..1134453f55 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -11,7 +11,7 @@ const DEFAULT_NAMESPACE = 'default_namespace'; const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); interface LooseObject { - [key: string]: string; + [key: string]: string } describe('Class: Metrics', () => { @@ -261,7 +261,7 @@ describe('Class: Metrics', () => { await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked again!')); - const loggedData = [JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0])]; + const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(3); expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); @@ -395,7 +395,7 @@ describe('Class: Metrics', () => { } await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); - const loggedData = [JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0])]; + const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(2); expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics.length).toBe(100); @@ -404,7 +404,14 @@ describe('Class: Metrics', () => { }); describe('Feature: Output validation ', () => { - test('Should use default namespace if no namepace is set', () => { + + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + + beforeEach(() => { + consoleSpy.mockClear(); + }); + + test('Should use default namespace if no namespace is set', () => { delete process.env.POWERTOOLS_METRICS_NAMESPACE; const metrics = new Metrics(); @@ -412,6 +419,7 @@ describe('Class: Metrics', () => { const serializedMetrics = metrics.serializeMetrics(); expect(serializedMetrics._aws.CloudWatchMetrics[0].Namespace).toBe(DEFAULT_NAMESPACE); + expect(console.warn).toHaveBeenNthCalledWith(1, 'Namespace should be defined, default used'); }); }); @@ -457,7 +465,7 @@ describe('Class: Metrics', () => { } await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); - const loggedData = [JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0])]; + const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(2); expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts new file mode 100644 index 0000000000..935601f1ea --- /dev/null +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -0,0 +1,147 @@ +import { logMetrics } from '../../../../metrics/src/middleware'; +import { Metrics, MetricUnits } from '../../../../metrics/src'; +import middy from '@middy/core'; +import { ExtraOptions } from '../../../src/types'; + +const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); +const mockDate = new Date(1466424490000); +const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string); + +describe('Middy middleware', () => { + + beforeEach(() => { + jest.resetModules(); + consoleSpy.mockClear(); + dateSpy.mockClear(); + }); + + describe('logMetrics', () => { + + test('when a metrics instance is passed WITH custom options, it prints the metrics in the stdout', async () => { + + // Prepare + const metrics = new Metrics({ namespace:'ServerlessAirline', service:'orders' }); + + const lambdaHandler = (): void => { + metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + }; + const metricsOptions: ExtraOptions = { + raiseOnEmptyMetrics: true, + defaultDimensions: { environment : 'prod', aws_region: 'eu-central-1' }, + captureColdStartMetric: true + }; + const handler = middy(lambdaHandler).use(logMetrics(metrics, metricsOptions)); + const event = { foo: 'bar' }; + const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); + + const awsRequestId = getRandomInt().toString(); + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', + awsRequestId: awsRequestId, + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + + // Act + await handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ + '_aws': { + 'Timestamp': 1466424490000, + 'CloudWatchMetrics': [{ + 'Namespace': 'ServerlessAirline', + 'Dimensions': [ + [ 'environment', 'aws_region', 'service', 'function_name' ] + ], + 'Metrics': [{ 'Name': 'ColdStart', 'Unit': 'Count' }], + }], + }, + 'environment': 'prod', + 'aws_region' : 'eu-central-1', + 'service': 'orders', + 'function_name': 'foo-bar-function', + 'ColdStart': 1, + })); + expect(console.log).toHaveBeenNthCalledWith(2, JSON.stringify({ + '_aws': { + 'Timestamp': 1466424490000, + 'CloudWatchMetrics': [{ + 'Namespace': 'ServerlessAirline', + 'Dimensions': [ + [ 'environment', 'aws_region', 'service' ] + ], + 'Metrics': [{ 'Name': 'SuccessfulBooking', 'Unit': 'Count' }], + }], + }, + 'environment': 'prod', + 'aws_region' : 'eu-central-1', + 'service': 'orders', + 'SuccessfulBooking': 1, + })); + + }); + + test('when a metrics instance is passed WITHOUT custom options, it prints the metrics in the stdout', async () => { + + // Prepare + const metrics = new Metrics({ namespace:'ServerlessAirline', service:'orders' }); + + const lambdaHandler = (): void => { + metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics)); + const event = { foo: 'bar' }; + const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); + + const awsRequestId = getRandomInt().toString(); + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', + awsRequestId: awsRequestId, + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + + // Act + await handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ + '_aws': { + 'Timestamp': 1466424490000, + 'CloudWatchMetrics': [{ + 'Namespace': 'ServerlessAirline', + 'Dimensions': [ + ['service'] + ], + 'Metrics': [{ 'Name': 'SuccessfulBooking', 'Unit': 'Count' }], + }], + }, + 'service': 'orders', + 'SuccessfulBooking': 1, + })); + + }); + + }); + +}); \ No newline at end of file diff --git a/packages/metrics/tsconfig.json b/packages/metrics/tsconfig.json index 9bd3b0686e..582618277f 100644 --- a/packages/metrics/tsconfig.json +++ b/packages/metrics/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "pretty": true, "baseUrl": "src/", - "rootDirs": [ "src/" ] + "rootDirs": [ "src/" ], + "esModuleInterop": true }, "include": [ "src/**/*"], "exclude": [ "./node_modules"], From 79e5aae8963aad802d56192b39f27e72250078bb Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Fri, 17 Dec 2021 22:12:05 +0100 Subject: [PATCH 02/29] chore(metrics): lint changes --- packages/metrics/src/Metrics.ts | 122 +++++++++--------- packages/metrics/src/config/ConfigService.ts | 6 +- .../src/config/ConfigServiceInterface.ts | 6 +- packages/metrics/src/types/Metrics.ts | 2 +- packages/metrics/tests/unit/Metrics.test.ts | 6 +- 5 files changed, 72 insertions(+), 70 deletions(-) diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 62ea4930d3..ecd069c778 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -149,24 +149,13 @@ class Metrics implements MetricsInterface { if (this.isSingleMetric) this.purgeStoredMetrics(); } - public clearDefaultDimensions(): void { - this.defaultDimensions = {}; - } - - public clearDimensions(): void { - this.dimensions = {}; - } - - public clearMetadata(): void { - this.metadata = {}; - } - - public clearMetrics(): void { - this.storedMetrics = {}; - } - /** - * Throw an Error if the metrics buffer is empty. + * Create a singleMetric to capture cold start. + * If it's a cold start invocation, this feature will: + * * Create a separate EMF blob solely containing a metric named ColdStart + * * Add function_name and service dimensions + * + * This has the advantage of keeping cold start metric separate from your application metrics, where you might have unrelated dimensions. * * @example * @@ -177,13 +166,38 @@ class Metrics implements MetricsInterface { * const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); * * export const handler = async (event: any, context: Context) => { - * metrics.raiseOnEmptyMetrics(); - * metrics.purgeStoredMetrics(); // will throw since no metrics added. + * metrics.captureColdStartMetric(); * } * ``` */ - public raiseOnEmptyMetrics(): void { - this.shouldRaiseOnEmptyMetrics = true; + public captureColdStartMetric(): void { + if (!this.isColdStart) return; + this.isColdStart = false; + const singleMetric = this.singleMetric(); + + if (this.dimensions.service) { + singleMetric.addDimension('service', this.dimensions.service); + } + if (this.functionName != null) { + singleMetric.addDimension('function_name', this.functionName); + } + singleMetric.addMetric('ColdStart', MetricUnits.Count, 1); + } + + public clearDefaultDimensions(): void { + this.defaultDimensions = {}; + } + + public clearDimensions(): void { + this.dimensions = {}; + } + + public clearMetadata(): void { + this.metadata = {}; + } + + public clearMetrics(): void { + this.storedMetrics = {}; } /** @@ -228,7 +242,7 @@ class Metrics implements MetricsInterface { if (captureColdStartMetric) this.captureColdStartMetric(); try { const result = originalMethod?.apply(this, [ event, context, callback ]); - + return result; } finally { this.purgeStoredMetrics(); @@ -259,6 +273,27 @@ class Metrics implements MetricsInterface { this.storedMetrics = {}; } + /** + * Throw an Error if the metrics buffer is empty. + * + * @example + * + * ```typescript + * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; + * import { Context } from 'aws-lambda'; + * + * const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + * + * export const handler = async (event: any, context: Context) => { + * metrics.raiseOnEmptyMetrics(); + * metrics.purgeStoredMetrics(); // will throw since no metrics added. + * } + * ``` + */ + public raiseOnEmptyMetrics(): void { + this.shouldRaiseOnEmptyMetrics = true; + } + /** * Function to create the right object compliant with Cloudwatch EMF (Event Metric Format). * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html for more details @@ -315,6 +350,10 @@ class Metrics implements MetricsInterface { this.defaultDimensions = targetDimensions; } + public setFunctionName(value: string): void { + this.functionName = value; + } + /** * CloudWatch EMF uses the same dimensions across all your metrics. Use singleMetric if you have a metric that should have different dimensions. * @@ -339,45 +378,6 @@ class Metrics implements MetricsInterface { }); } - /** - * Create a singleMetric to capture cold start. - * If it's a cold start invocation, this feature will: - * * Create a separate EMF blob solely containing a metric named ColdStart - * * Add function_name and service dimensions - * - * This has the advantage of keeping cold start metric separate from your application metrics, where you might have unrelated dimensions. - * - * @example - * - * ```typescript - * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - * import { Context } from 'aws-lambda'; - * - * const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); - * - * export const handler = async (event: any, context: Context) => { - * metrics.captureColdStartMetric(); - * } - * ``` - */ - public captureColdStartMetric(): void { - if (!this.isColdStart) return; - this.isColdStart = false; - const singleMetric = this.singleMetric(); - - if (this.dimensions.service) { - singleMetric.addDimension('service', this.dimensions.service); - } - if (this.functionName != null) { - singleMetric.addDimension('function_name', this.functionName); - } - singleMetric.addMetric('ColdStart', MetricUnits.Count, 1); - } - - public setFunctionName(value: string): void { - this.functionName = value; - } - private getCurrentDimensionsCount(): number { return Object.keys(this.dimensions).length + Object.keys(this.defaultDimensions).length; } diff --git a/packages/metrics/src/config/ConfigService.ts b/packages/metrics/src/config/ConfigService.ts index fec8169e2a..a1fef20267 100644 --- a/packages/metrics/src/config/ConfigService.ts +++ b/packages/metrics/src/config/ConfigService.ts @@ -2,9 +2,9 @@ import { ConfigServiceInterface } from '.'; abstract class ConfigService implements ConfigServiceInterface { - abstract get(name: string): string; - abstract getNamespace(): string; - abstract getService(): string; + public abstract get(name: string): string; + public abstract getNamespace(): string; + public abstract getService(): string; } diff --git a/packages/metrics/src/config/ConfigServiceInterface.ts b/packages/metrics/src/config/ConfigServiceInterface.ts index be86bc651d..65406c5037 100644 --- a/packages/metrics/src/config/ConfigServiceInterface.ts +++ b/packages/metrics/src/config/ConfigServiceInterface.ts @@ -1,8 +1,8 @@ interface ConfigServiceInterface { - get?(name: string): string; - getNamespace(): string; - getService(): string; + get?(name: string): string + getNamespace(): string + getService(): string } diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index 5670902f16..0ceb2bc551 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -20,7 +20,7 @@ type EmfOutput = { CloudWatchMetrics: { Namespace: string Dimensions: [string[]] - Metrics: { Name: string; Unit: MetricUnit }[]; + Metrics: { Name: string; Unit: MetricUnit }[] }[] } }; diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 1134453f55..dfbed52f0e 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -1,5 +1,7 @@ import { ContextExamples as dummyContext, LambdaInterface } from '@aws-lambda-powertools/commons'; import { Context, Callback } from 'aws-lambda'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import * as dummyEvent from '../../../../tests/resources/events/custom/hello-world.json'; import { Metrics, MetricUnits } from '../../src/'; import { populateEnvironmentVariables } from '../helpers'; @@ -227,7 +229,7 @@ describe('Class: Metrics', () => { test('Cold start metric should only be written out once and flushed automatically', async () => { const metrics = new Metrics({ namespace: 'test' }); - const handler = async (event: any, context: Context) => { + const handler = async (_event, _context): Promise => { // Should generate only one log metrics.captureColdStartMetric(); }; @@ -361,7 +363,7 @@ describe('Class: Metrics', () => { expect.assertions(1); const metrics = new Metrics({ namespace: 'test' }); - const handler = async (event: any, context: Context) => { + const handler = async (_event, _context): Promise => { metrics.raiseOnEmptyMetrics(); // Logic goes here metrics.purgeStoredMetrics(); From 9c1644dbe0e5438fb24da4fabda06959091fae0a Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Tue, 21 Dec 2021 19:00:36 +0100 Subject: [PATCH 03/29] docs(metrics): logMetric middleware and examples --- docs/core/metrics.md | 329 +++++++++++++----- packages/metrics/README.md | 2 +- packages/metrics/examples/cold-start.ts | 22 +- .../metrics/examples/constructor-options.ts | 24 +- .../metrics/examples/decorator/cold-start.ts | 26 ++ .../examples/decorator/constructor-options.ts | 21 ++ .../default-dimensions-constructor.ts | 27 ++ .../examples/decorator/default-dimensions.ts | 33 ++ .../metrics/examples/decorator/dimensions.ts | 28 ++ .../examples/decorator/empty-metrics.ts | 25 ++ .../metrics/examples/decorator/hello-world.ts | 27 ++ .../examples/decorator/manual-flushing.ts | 27 ++ .../{ => decorator}/programatic-access.ts | 0 .../examples/decorator/single-metric.ts | 28 ++ .../default-dimensions-constructor.ts | 31 +- .../metrics/examples/default-dimensions.ts | 38 +- packages/metrics/examples/dimensions.ts | 31 +- packages/metrics/examples/empty-metrics.ts | 25 +- packages/metrics/examples/hello-world.ts | 27 +- packages/metrics/examples/manual-flushing.ts | 24 +- .../metrics/examples/programmatic-access.ts | 25 ++ packages/metrics/examples/single-metric.ts | 28 +- packages/metrics/package.json | 14 +- packages/metrics/src/Metrics.ts | 4 +- .../tests/unit/middleware/middy.test.ts | 26 +- 25 files changed, 647 insertions(+), 245 deletions(-) create mode 100644 packages/metrics/examples/decorator/cold-start.ts create mode 100644 packages/metrics/examples/decorator/constructor-options.ts create mode 100644 packages/metrics/examples/decorator/default-dimensions-constructor.ts create mode 100644 packages/metrics/examples/decorator/default-dimensions.ts create mode 100644 packages/metrics/examples/decorator/dimensions.ts create mode 100644 packages/metrics/examples/decorator/empty-metrics.ts create mode 100644 packages/metrics/examples/decorator/hello-world.ts create mode 100644 packages/metrics/examples/decorator/manual-flushing.ts rename packages/metrics/examples/{ => decorator}/programatic-access.ts (100%) create mode 100644 packages/metrics/examples/decorator/single-metric.ts create mode 100644 packages/metrics/examples/programmatic-access.ts diff --git a/docs/core/metrics.md b/docs/core/metrics.md index ea69419f1e..1d277e5043 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -32,14 +32,26 @@ Metric has two global settings that will be used across all metrics emitted: Setting | Description | Environment variable | Constructor parameter ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- -**Metric namespace** | Logical container where all metrics will be placed e.g. `ServerlessAirline` | `POWERTOOLS_METRICS_NAMESPACE` | `namespace` +**Metric namespace** | Logical container where all metrics will be placed e.g. `serverlessAirline` | `POWERTOOLS_METRICS_NAMESPACE` | `namespace` **Service** | Optionally, sets **service** metric dimension across all metrics e.g. `payment` | `POWERTOOLS_SERVICE_NAME` | `service` !!! tip "Use your application or main service as the metric namespace to easily group all metrics" > Example using AWS Serverless Application Model (SAM) -=== "template.yml" +=== "index.ts" + + ```typescript hl_lines="5 7" + import { Metrics } from '@aws-lambda-powertools/metrics'; + + + // Sets metric namespace and service via env var + const metrics = new Metrics(); + // OR Sets metric namespace, and service as a metrics parameters + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + ``` + +=== "sam-template.yml" ```yaml hl_lines="9 10" Resources: @@ -50,22 +62,9 @@ Setting | Description | Environment variable | Constructor parameter Environment: Variables: POWERTOOLS_SERVICE_NAME: payment - POWERTOOLS_METRICS_NAMESPACE: ServerlessAirline + POWERTOOLS_METRICS_NAMESPACE: serverlessAirline ``` -[//]:# (START EDITING FROM HERE DOWN) - -=== "index.ts" - - ```typescript hl_lines="5 7" - import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - - - // Sets metric namespace and service via env var - const metrics = new Metrics(); - // OR Sets metric namespace, and service as a metrics parameters - const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); - ``` You can initialize Metrics anywhere in your code - It'll keep track of your aggregate metrics in memory. @@ -80,10 +79,10 @@ You can create metrics using `addMetric`, and you can create dimensions for all import { Context } from 'aws-lambda'; - const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); export const handler = async (event: any, context: Context) => { - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); } ``` === "Metrics with custom dimensions" @@ -93,11 +92,11 @@ You can create metrics using `addMetric`, and you can create dimensions for all import { Context } from 'aws-lambda'; - const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); export const handler = async (event: any, context: Context) => { metrics.addDimension('environment', 'prod'); - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); } ``` @@ -112,9 +111,49 @@ You can create metrics using `addMetric`, and you can create dimensions for all ### Adding default dimensions -You can use `setDefaultDimensions` method to persist dimensions across Lambda invocations. +You can use add default dimensions to your metrics by passing them as parameters in 4 ways: + +* in the constructor +* in the Middy middleware +* using the setDefaultDimensions` method +* in the decorator + +If you'd like to remove them at some point, you can use `clearDefaultDimensions` method. +See examples below: + +=== "constructor" + + ```typescript hl_lines="7" + import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + + const metrics = new Metrics({ + namespace:"serverlessAirline", + service:"orders", + defaultDimensions: { 'environment': 'prod', 'anotherDimension': 'whatever' } + }); + + export const handler = async (event: any, context: Context) => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + } + ``` + +=== "logMetrics middleware" + + ```typescript hl_lines="5" + import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + import middy from '@middy/core'; + + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); -If you'd like to remove them at some point, you can use `clearDefaultDimensions` method. + const lambdaHandler = async (event: any, context: Context) => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + } + + export const handler = middy(lambdaHandler) + .use(logMetrics(metrics, { defaultDimensions:{ 'environment': 'prod', 'anotherDimension': 'whatever' } })); + ``` === "setDefaultDimensions method" @@ -122,22 +161,23 @@ If you'd like to remove them at some point, you can use `clearDefaultDimensions` import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; import { Context } from 'aws-lambda'; - const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); metrics.setDefaultDimensions({ 'environment': 'prod', 'anotherDimension': 'whatever' }); export const handler = async (event: any, context: Context) => { - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); } ``` === "with logMetrics decorator" - ```typescript hl_lines="6 11" + ```typescript hl_lines="6 12" import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; import { Context, Callback } from 'aws-lambda'; + import middy from '@middy/core'; - const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); const DEFAULT_DIMENSIONS = {"environment": "prod", "another": "one"}; @@ -145,7 +185,7 @@ If you'd like to remove them at some point, you can use `clearDefaultDimensions` @metrics.logMetrics({defaultDimensions: DEFAULT_DIMENSIONS}) public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); } ``` @@ -153,26 +193,87 @@ If you'd like to remove them at some point, you can use `clearDefaultDimensions` As you finish adding all your metrics, you need to serialize and flush them to standard output. -#### Using Decorator +You can flush metrics automatically using one of the following methods: - You can do that automatically with the `logMetrics` decorator. +* [Middy-compatible](https://github.com/middyjs/middy){target=_blank} middleware +* class decorator +* manually -!!! warning - Decorators can only be attached to a class declaration, method, accessor, property, or parameter. Therefore, if you are more into standard function, check the next section instead. See the [official doc](https://www.typescriptlang.org/docs/handbook/decorators.html) for more details. +The middleware and the decorator also **validate**, **serialize**, and **flush** all your metrics. During metrics validation, if no metrics are provided then a warning will be logged, but no exception will be raised. + +!!! warning "Metric validation" + If metrics are provided, and any of the following criteria are not met, a **`RangeError`** exception will be raised: + + * Maximum of 9 dimensions + * Namespace is set, and no more than one + * Metric units must be [supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) + + +#### Using a middleware + +See below an example of how to automatically flush metrics with the Middy-compatible `logMetrics` middleware. + + +```typescript hl_lines="3 8 11-12" + import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + import middy from '@middy/core'; + + const metrics = new Metrics({namespace:"exampleApplication", service:"exampleService"}); + + const lambdaHandler = async (event: any, context: Context) => { + metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1); + } + + export const handler = middy(lambdaHandler) + .use(logMetrics(metrics)); +``` + +=== "Example CloudWatch Logs excerpt" + + ```json hl_lines="2 7 10 15 22" + { + "bookingConfirmation": 1.0, + "_aws": { + "Timestamp": 1592234975665, + "CloudWatchMetrics": [ + { + "Namespace": "exampleApplication", + "Dimensions": [ + [ + "service" + ] + ], + "Metrics": [ + { + "Name": "bookingConfirmation", + "Unit": "Count" + } + ] + } + ] + }, + "service": "exampleService" + } + ``` + +#### Using the class decorator + +Decorators can only be attached to a class declaration, method, accessor, property, or parameter. Therefore, if prefer to write your handler as a standard function, check the middleware or manual methods instead. +See the [official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/decorators.html) for more details. -This decorator also **validates**, **serializes**, and **flushes** all your metrics. During metrics validation, if no metrics are provided then a warning will be logged, but no exception will be raised. ```typescript hl_lines="8" import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; import { Context, Callback } from 'aws-lambda'; -const metrics = new Metrics({namespace:"ExampleApplication", service:"ExampleService"}); +const metrics = new Metrics({namespace:"exampleApplication", service:"exampleService"}); export class MyFunction { @metrics.logMetrics() public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('BookingConfirmation', MetricUnits.Count, 1); + metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1); } } ``` @@ -181,12 +282,12 @@ export class MyFunction { ```json hl_lines="2 7 10 15 22" { - "BookingConfirmation": 1.0, + "bookingConfirmation": 1.0, "_aws": { "Timestamp": 1592234975665, "CloudWatchMetrics": [ { - "Namespace": "ExampleApplication", + "Namespace": "exampleApplication", "Dimensions": [ [ "service" @@ -194,27 +295,21 @@ export class MyFunction { ], "Metrics": [ { - "Name": "BookingConfirmation", + "Name": "bookingConfirmation", "Unit": "Count" } ] } ] }, - "service": "ExampleService" + "service": "exampleService" } ``` -!!! tip "Metric validation" - If metrics are provided, and any of the following criteria are not met, **`SchemaValidationError`** exception will be raised: - - * Maximum of 9 dimensions - * Namespace is set, and no more than one - * Metric units must be [supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) #### Manually -If you prefer not to use `logMetrics` decorator because you might want to encapsulate additional logic or avoid having to go for classes encapsulation when doing so, you can manually flush with `purgeStoredMetrics` and clear metrics as follows: +If you wish to do so, you can manually flush the metrics with `purgeStoredMetrics` and clear metrics as follows: !!! warning Metrics, dimensions and namespace validation still applies. @@ -232,48 +327,72 @@ const lambdaHandler: Handler = async () => { }; ``` -#### Raising SchemaValidationError on empty metrics +#### Throwing a RangeError when no metrics are emitted -If you want to ensure that at least one metric is emitted, you can pass `raiseOnEmptyMetrics` to the **logMetrics** decorator: +If you want to ensure that at least one metric is emitted before you flush them, you can use the `raiseOnEmptyMetrics` parameter and pass it to the middleware or decorator: -```typescript hl_lines="7" -import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - -const metrics = new Metrics(); +```typescript hl_lines="12" + import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + import middy from '@middy/core'; -class Lambda implements LambdaInterface { + const metrics = new Metrics({namespace:"exampleApplication", service:"exampleService"}); - @metrics.logMetrics({raiseOnEmptyMetrics: true}) - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - // This will throw an error unless at least one metric is added + const lambdaHandler = async (event: any, context: Context) => { + metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1); } -} + + export const handler = middy(lambdaHandler) + .use(logMetrics(metrics, { raiseOnEmptyMetrics: true })); ``` -### Capturing cold start metric +### Capturing a cold start invocation as metric -You can optionally capture cold start metrics with `logMetrics` decorator via `captureColdStartMetric` param. +You can optionally capture cold start metrics with the `logMetrics` middleware or decorator via the `captureColdStartMetric` param. -```typescript hl_lines="7" -import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; +=== "logMetrics middleware" -const metrics = new Metrics(); + ```typescript hl_lines="12" + import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + import middy from '@middy/core'; -class Lambda implements LambdaInterface { + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); - @metrics.logMetrics({ captureColdStartMetric: true }) - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - ... -``` + const lambdaHandler = async (event: any, context: Context) => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + } + + export const handler = middy(lambdaHandler) + .use(logMetrics(metrics, { captureColdStartMetric: true } })); + ``` + +=== "logMetrics decorator" + + ```typescript hl_lines="9" + import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; + import { Context, Callback } from 'aws-lambda'; + import middy from '@middy/core'; + + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + + export class MyFunction { + + @metrics.logMetrics({ captureColdStartMetric: true }) + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + } + } + ``` If it's a cold start invocation, this feature will: * Create a separate EMF blob solely containing a metric named `ColdStart` -* Add `function_name` and `service` dimensions +* Add `function_name`, `service` and default dimensions This has the advantage of keeping cold start metric separate from your application metrics, where you might have unrelated dimensions. -!!! info "We do not emit 0 as a value for ColdStart metric for cost reasons. [Let us know](https://github.com/awslabs/aws-lambda-powertools-typecsript/issues/new?assignees=&labels=feature-request%2C+triage&template=feature_request.md&title=) if you'd prefer a flag to override it" +!!! info "We do not emit 0 as a value for the ColdStart metric for cost-efficiency reasons. [Let us know](https://github.com/awslabs/aws-lambda-powertools-typecsript/issues/new?assignees=&labels=feature-request%2C+triage&template=feature_request.md&title=) if you'd prefer a flag to override it." ## Advanced @@ -281,34 +400,34 @@ This has the advantage of keeping cold start metric separate from your applicati You can add high-cardinality data as part of your Metrics log with `addMetadata` method. This is useful when you want to search highly contextual information along with your metrics in your logs. -!!! info +!!! warning **This will not be available during metrics visualization** - Use **dimensions** for this purpose -```typescript hl_lines="9" -import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - -const metrics = new Metrics(); +```typescript hl_lines="8" + import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + import middy from '@middy/core'; -class Lambda implements LambdaInterface { + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetadata('booking_id', 'booking_uuid'); - //Your Logic + const lambdaHandler = async (event: any, context: Context) => { + metrics.addMetadata('bookingId', '7051cd10-6283-11ec-90d6-0242ac120003'); } -} + + export const handler = middy(lambdaHandler) + .use(logMetrics(metrics)); ``` === "Example CloudWatch Logs excerpt" ```json hl_lines="23" { - "SuccessfulBooking": 1.0, + "successfulBooking": 1.0, "_aws": { "Timestamp": 1592234975665, "CloudWatchMetrics": [ { - "Namespace": "ExampleApplication", + "Namespace": "exampleApplication", "Dimensions": [ [ "service" @@ -316,7 +435,7 @@ class Lambda implements LambdaInterface { ], "Metrics": [ { - "Name": "SuccessfulBooking", + "Name": "successfulBooking", "Unit": "Count" } ] @@ -324,31 +443,59 @@ class Lambda implements LambdaInterface { ] }, "service": "booking", - "booking_id": "booking_uuid" + "bookingId": "7051cd10-6283-11ec-90d6-0242ac120003" } ``` -### Single metric with a different dimension +### Single metric with different dimensions CloudWatch EMF uses the same dimensions across all your metrics. Use `singleMetric` if you have a metric that should have different dimensions. !!! info - Generally, this would be an edge case since you [pay for unique metric](https://aws.amazon.com/cloudwatch/pricing). Keep the following formula in mind: + For cost-efficiency, this feature would be used sparsely since you [pay for unique metric](https://aws.amazon.com/cloudwatch/pricing). Keep the following formula in mind: **unique metric = (metric_name + dimension_name + dimension_value)** +=== "logMetrics middleware" + + ```typescript hl_lines="8 10 12" + import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; + import { Context } from 'aws-lambda'; + import middy from '@middy/core'; + + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + + const lambdaHandler = async (event: any, context: Context) => { + const singleMetric = metrics.singleMetric(); + metrics.addDimension('outerDimension', 'true'); + singleMetric.addDimension('innerDimension', 'true'); + metrics.addMetric('testMetric', MetricUnits.Count, 10); + singleMetric.addMetric('singleMetric', MetricUnits.Percent, 50); + } - ```typescript hl_lines="6-7" - class Lambda implements LambdaInterface { + export const handler = middy(lambdaHandler) + .use(logMetrics(metrics, { captureColdStartMetric: true } })); + ``` + +=== "logMetrics decorator" + + ```typescript hl_lines="11 13 15" + import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; + import { Context, Callback } from 'aws-lambda'; + import middy from '@middy/core'; + + const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + + export class MyFunction { @metrics.logMetrics() public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { const singleMetric = metrics.singleMetric(); - metrics.addDimension('OuterDimension', 'true'); - singleMetric.addDimension('InnerDimension', 'true'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); - singleMetric.addMetric('single-metric', MetricUnits.Percent, 50); + metrics.addDimension('outerDimension', 'true'); + singleMetric.addDimension('innerDimension', 'true'); + metrics.addMetric('testMetric', MetricUnits.Count, 10); + singleMetric.addMetric('singleMetric', MetricUnits.Percent, 50); } } ``` diff --git a/packages/metrics/README.md b/packages/metrics/README.md index c2f5ae9a63..7b823d4b98 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -24,7 +24,7 @@ Metrics has two global settings that will be used across all metrics emitted: |Setting|Description|Environment Variable|Constructor Parameter| |---|---|---|---| -|Metric namespace|Logical container where all metrics will be placed e.g. ServerlessAirline|POWERTOOLS_METRICS_NAMESPACE|namespace| +|Metric namespace|Logical container where all metrics will be placed e.g. serverlessAirline|POWERTOOLS_METRICS_NAMESPACE|namespace| |Service|Optionally, sets service metric dimension across all metrics e.g. payment|POWERTOOLS_SERVICE_NAME|service| ```typescript diff --git a/packages/metrics/examples/cold-start.ts b/packages/metrics/examples/cold-start.ts index 693e8d5bba..be861e795b 100644 --- a/packages/metrics/examples/cold-start.ts +++ b/packages/metrics/examples/cold-start.ts @@ -1,9 +1,9 @@ import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; import { populateEnvironmentVariables } from '../tests/helpers'; -import { Callback, Context } from 'aws-lambda/handler'; import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); @@ -12,15 +12,15 @@ process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; const metrics = new Metrics(); -class Lambda implements LambdaInterface { +const lambdaHandler = async (): Promise => { - @metrics.logMetrics({ captureColdStartMetric: true }) - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addDimension('OuterDimension', 'true'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); - } + metrics.addDimension('custom-dimension', 'true'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); -} +}; -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked again!')); \ No newline at end of file +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics, { captureColdStartMetric: true })); + +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked again!')); \ No newline at end of file diff --git a/packages/metrics/examples/constructor-options.ts b/packages/metrics/examples/constructor-options.ts index 67be0ace59..2bc994ee15 100644 --- a/packages/metrics/examples/constructor-options.ts +++ b/packages/metrics/examples/constructor-options.ts @@ -1,21 +1,25 @@ import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { populateEnvironmentVariables } from '../tests/helpers'; import { Metrics, MetricUnits } from '../src'; -import { LambdaInterface } from "./utils/lambda"; -import { Callback, Context } from "aws-lambda/handler"; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; const metrics = new Metrics({ namespace: 'hello-world-constructor', service: 'hello-world-service-constructor' }); -class Lambda implements LambdaInterface { - - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('test-metric', MetricUnits.Count, 10); +const lambdaHandler = async (): Promise => { + metrics.addMetric('test-metric', MetricUnits.Count, 10); +}; - } +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics)); -} -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/cold-start.ts b/packages/metrics/examples/decorator/cold-start.ts new file mode 100644 index 0000000000..693e8d5bba --- /dev/null +++ b/packages/metrics/examples/decorator/cold-start.ts @@ -0,0 +1,26 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { populateEnvironmentVariables } from '../tests/helpers'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics, MetricUnits } from '../src'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +const metrics = new Metrics(); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics({ captureColdStartMetric: true }) + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addDimension('OuterDimension', 'true'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked again!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/constructor-options.ts b/packages/metrics/examples/decorator/constructor-options.ts new file mode 100644 index 0000000000..67be0ace59 --- /dev/null +++ b/packages/metrics/examples/decorator/constructor-options.ts @@ -0,0 +1,21 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { Metrics, MetricUnits } from '../src'; +import { LambdaInterface } from "./utils/lambda"; +import { Callback, Context } from "aws-lambda/handler"; + +const metrics = new Metrics({ + namespace: 'hello-world-constructor', + service: 'hello-world-service-constructor' +}); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics() + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addMetric('test-metric', MetricUnits.Count, 10); + + } + +} +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/default-dimensions-constructor.ts b/packages/metrics/examples/decorator/default-dimensions-constructor.ts new file mode 100644 index 0000000000..cb01c59c61 --- /dev/null +++ b/packages/metrics/examples/decorator/default-dimensions-constructor.ts @@ -0,0 +1,27 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world-constructor'; +process.env.POWERTOOLS_SERVICE_NAME = 'hello-world-service-constructor'; + +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics, MetricUnits } from '../src'; + +const metrics = new Metrics({ defaultDimensions:{ 'application': 'hello-world' } }); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics() + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addMetric('test-metric', MetricUnits.Count, 10); + + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/default-dimensions.ts b/packages/metrics/examples/decorator/default-dimensions.ts new file mode 100644 index 0000000000..9c8a06ddcb --- /dev/null +++ b/packages/metrics/examples/decorator/default-dimensions.ts @@ -0,0 +1,33 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics, MetricUnits } from '../src'; + +const metrics = new Metrics(); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics({ defaultDimensions:{ 'application': 'hello-world' } }) + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + + metrics.addDimension('environment', 'dev'); + metrics.addDimension('application', 'hello-world-dev'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); + const metricsObject = metrics.serializeMetrics(); + metrics.clearMetrics(); + metrics.clearDimensions(); + metrics.addMetric('new-test-metric', MetricUnits.Count, 5); + console.log(JSON.stringify(metricsObject)); + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/dimensions.ts b/packages/metrics/examples/decorator/dimensions.ts new file mode 100644 index 0000000000..0058344517 --- /dev/null +++ b/packages/metrics/examples/decorator/dimensions.ts @@ -0,0 +1,28 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics, MetricUnits } from '../src'; + +const metrics = new Metrics(); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics() + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + + metrics.addDimension('environment', 'dev'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); + + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/empty-metrics.ts b/packages/metrics/examples/decorator/empty-metrics.ts new file mode 100644 index 0000000000..3c9a4ac63d --- /dev/null +++ b/packages/metrics/examples/decorator/empty-metrics.ts @@ -0,0 +1,25 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics } from '../src'; + +const metrics = new Metrics(); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics({ raiseOnEmptyMetrics: true }) + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/hello-world.ts b/packages/metrics/examples/decorator/hello-world.ts new file mode 100644 index 0000000000..088cb0a0c4 --- /dev/null +++ b/packages/metrics/examples/decorator/hello-world.ts @@ -0,0 +1,27 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; +process.env.POWERTOOLS_SERVICE_NAME = 'hello-world-service'; + +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics, MetricUnits } from '../src'; + +const metrics = new Metrics(); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics() + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addMetric('test-metric', MetricUnits.Count, 10); + + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/decorator/manual-flushing.ts b/packages/metrics/examples/decorator/manual-flushing.ts new file mode 100644 index 0000000000..3e4b0621dc --- /dev/null +++ b/packages/metrics/examples/decorator/manual-flushing.ts @@ -0,0 +1,27 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { Handler } from 'aws-lambda'; +import { Metrics, MetricUnits } from '../src'; + +const metrics = new Metrics(); + +const lambdaHandler: Handler = async () => { + + metrics.addMetric('test-metric', MetricUnits.Count, 10); + metrics.purgeStoredMetrics(); + //Metrics will be logged and cleared + + return { + foo: 'bar' + }; + +}; + +lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/programatic-access.ts b/packages/metrics/examples/decorator/programatic-access.ts similarity index 100% rename from packages/metrics/examples/programatic-access.ts rename to packages/metrics/examples/decorator/programatic-access.ts diff --git a/packages/metrics/examples/decorator/single-metric.ts b/packages/metrics/examples/decorator/single-metric.ts new file mode 100644 index 0000000000..c19ba4aa5e --- /dev/null +++ b/packages/metrics/examples/decorator/single-metric.ts @@ -0,0 +1,28 @@ +import { populateEnvironmentVariables } from '../tests/helpers'; +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { Callback, Context } from 'aws-lambda/handler'; +import { Metrics, MetricUnits } from '../src'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +const metrics = new Metrics(); + +class Lambda implements LambdaInterface { + + @metrics.logMetrics() + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + const singleMetric = metrics.singleMetric(); + metrics.addDimension('OuterDimension', 'true'); + singleMetric.addDimension('InnerDimension', 'true'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); + singleMetric.addMetric('single-metric', MetricUnits.Percent, 50); + } + +} + +new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/default-dimensions-constructor.ts b/packages/metrics/examples/default-dimensions-constructor.ts index cb01c59c61..651139cfd9 100644 --- a/packages/metrics/examples/default-dimensions-constructor.ts +++ b/packages/metrics/examples/default-dimensions-constructor.ts @@ -1,27 +1,22 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); // Additional runtime variables -process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world-constructor'; -process.env.POWERTOOLS_SERVICE_NAME = 'hello-world-service-constructor'; - -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics, MetricUnits } from '../src'; - -const metrics = new Metrics({ defaultDimensions:{ 'application': 'hello-world' } }); - -class Lambda implements LambdaInterface { +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('test-metric', MetricUnits.Count, 10); +const metrics = new Metrics({ defaultDimensions:{ 'application': 'my-application' } }); - } +const lambdaHandler = async (): Promise => { + metrics.addMetric('test-metric', MetricUnits.Count, 10); +}; -} +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics)); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/default-dimensions.ts b/packages/metrics/examples/default-dimensions.ts index 9c8a06ddcb..aeb04a2e8b 100644 --- a/packages/metrics/examples/default-dimensions.ts +++ b/packages/metrics/examples/default-dimensions.ts @@ -1,33 +1,31 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); // Additional runtime variables process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics, MetricUnits } from '../src'; - const metrics = new Metrics(); -class Lambda implements LambdaInterface { +const lambdaHandler = async (): Promise => { + metrics.addDimension('environment', 'dev'); + metrics.addDimension('application', 'hello-world-dev'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); + metrics.addMetric('new-test-metric-with-dimensions', MetricUnits.Count, 5); + metrics.addMetric('new-test-metric-without-dimensions', MetricUnits.Count, 5); - @metrics.logMetrics({ defaultDimensions:{ 'application': 'hello-world' } }) - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + // Optional: clear metrics and dimensions created till now + // metrics.clearMetrics(); + // metrics.clearDimensions(); - metrics.addDimension('environment', 'dev'); - metrics.addDimension('application', 'hello-world-dev'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); - const metricsObject = metrics.serializeMetrics(); - metrics.clearMetrics(); - metrics.clearDimensions(); - metrics.addMetric('new-test-metric', MetricUnits.Count, 5); - console.log(JSON.stringify(metricsObject)); - } +}; -} +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics, { defaultDimensions:{ 'application': 'hello-world' } })); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); diff --git a/packages/metrics/examples/dimensions.ts b/packages/metrics/examples/dimensions.ts index 0058344517..b59066ad6d 100644 --- a/packages/metrics/examples/dimensions.ts +++ b/packages/metrics/examples/dimensions.ts @@ -1,28 +1,23 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); // Additional runtime variables process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics, MetricUnits } from '../src'; - -const metrics = new Metrics(); - -class Lambda implements LambdaInterface { - - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - - metrics.addDimension('environment', 'dev'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); +const metrics = new Metrics({ defaultDimensions:{ 'application': 'my-application' } }); - } +const lambdaHandler = async (): Promise => { + metrics.addDimension('environment', 'dev'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); +}; -} +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics)); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/empty-metrics.ts b/packages/metrics/examples/empty-metrics.ts index 3c9a4ac63d..01aaefccfd 100644 --- a/packages/metrics/examples/empty-metrics.ts +++ b/packages/metrics/examples/empty-metrics.ts @@ -1,25 +1,22 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); // Additional runtime variables process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics } from '../src'; - const metrics = new Metrics(); -class Lambda implements LambdaInterface { - - @metrics.logMetrics({ raiseOnEmptyMetrics: true }) - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - - } +const lambdaHandler = async (): Promise => { + // No metric added +}; -} +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics, { raiseOnEmptyMetrics: true })); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/hello-world.ts b/packages/metrics/examples/hello-world.ts index 088cb0a0c4..d001602de3 100644 --- a/packages/metrics/examples/hello-world.ts +++ b/packages/metrics/examples/hello-world.ts @@ -1,27 +1,22 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); // Additional runtime variables process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; -process.env.POWERTOOLS_SERVICE_NAME = 'hello-world-service'; - -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics, MetricUnits } from '../src'; const metrics = new Metrics(); -class Lambda implements LambdaInterface { - - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('test-metric', MetricUnits.Count, 10); - - } +const lambdaHandler = async (): Promise => { + metrics.addMetric('test-metric', MetricUnits.Count, 10); +}; -} +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics})); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/manual-flushing.ts b/packages/metrics/examples/manual-flushing.ts index 3e4b0621dc..5c978930b4 100644 --- a/packages/metrics/examples/manual-flushing.ts +++ b/packages/metrics/examples/manual-flushing.ts @@ -1,27 +1,23 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); // Additional runtime variables process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { Handler } from 'aws-lambda'; -import { Metrics, MetricUnits } from '../src'; - const metrics = new Metrics(); -const lambdaHandler: Handler = async () => { - +const lambdaHandler = async (): Promise => { metrics.addMetric('test-metric', MetricUnits.Count, 10); metrics.purgeStoredMetrics(); - //Metrics will be logged and cleared - - return { - foo: 'bar' - }; - }; -lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics)); + +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/programmatic-access.ts b/packages/metrics/examples/programmatic-access.ts new file mode 100644 index 0000000000..b461f69ae7 --- /dev/null +++ b/packages/metrics/examples/programmatic-access.ts @@ -0,0 +1,25 @@ +import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; +import { populateEnvironmentVariables } from '../tests/helpers'; +import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; + +// Populate runtime +populateEnvironmentVariables(); +// Additional runtime variables +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; + +const metrics = new Metrics(); + +const lambdaHandler = async (): Promise => { + metrics.addMetric('test-metric', MetricUnits.Count, 10); + const metricsObject = metrics.serializeMetrics(); + metrics.clearMetrics(); + console.log(JSON.stringify(metricsObject)); +}; + +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics)); + +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/examples/single-metric.ts b/packages/metrics/examples/single-metric.ts index c19ba4aa5e..aec3d36bfb 100644 --- a/packages/metrics/examples/single-metric.ts +++ b/packages/metrics/examples/single-metric.ts @@ -1,9 +1,9 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { Callback, Context } from 'aws-lambda/handler'; +import { populateEnvironmentVariables } from '../tests/helpers'; import { Metrics, MetricUnits } from '../src'; +import middy from '@middy/core'; +import { logMetrics } from '../src/middleware/middy'; // Populate runtime populateEnvironmentVariables(); @@ -12,17 +12,15 @@ process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; const metrics = new Metrics(); -class Lambda implements LambdaInterface { - - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - const singleMetric = metrics.singleMetric(); - metrics.addDimension('OuterDimension', 'true'); - singleMetric.addDimension('InnerDimension', 'true'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); - singleMetric.addMetric('single-metric', MetricUnits.Percent, 50); - } +const lambdaHandler = async (): Promise => { + const singleMetric = metrics.singleMetric(); + metrics.addDimension('OuterDimension', 'true'); + singleMetric.addDimension('InnerDimension', 'true'); + metrics.addMetric('test-metric', MetricUnits.Count, 10); + singleMetric.addMetric('single-metric', MetricUnits.Percent, 50); +}; -} +const handlerWithMiddleware = middy(lambdaHandler) + .use(logMetrics(metrics)); -new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file +handlerWithMiddleware(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); \ No newline at end of file diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 4d788a6a86..f1d9fcaf0b 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -26,9 +26,19 @@ "example:empty-metrics": "ts-node examples/empty-metrics.ts", "example:single-metric": "ts-node examples/single-metric.ts", "example:cold-start": "ts-node examples/cold-start.ts", - "example:programatic-access": "ts-node examples/programatic-access.ts", + "example:programatic-access": "ts-node examples/programmatic-access.ts", "example:constructor-options": "ts-node examples/constructor-options.ts", - "example:default-dimensions-constructor": "ts-node examples/default-dimensions-constructor.ts" + "example:default-dimensions-constructor": "ts-node examples/default-dimensions-constructor.ts", + "example:hello-world-decorator": "ts-node examples/decorator/hello-world.ts", + "example:manual-flushing-decorator": "ts-node examples/decorator/manual-flushing.ts", + "example:dimensions-decorator": "ts-node examples/decorator/dimensions.ts", + "example:default-dimensions-decorator": "ts-node examples/decorator/default-dimensions.ts", + "example:empty-metrics-decorator": "ts-node examples/decorator/empty-metrics.ts", + "example:single-metric-decorator": "ts-node examples/decorator/single-metric.ts", + "example:cold-start-decorator": "ts-node examples/decorator/cold-start.ts", + "example:programatic-access-decorator": "ts-node examples/decorator/programatic-access.ts", + "example:constructor-options-decorator": "ts-node examples/decorator/constructor-options.ts", + "example:default-dimensions-constructor-decorator": "ts-node examples/decorator/default-dimensions-constructor.ts" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/metrics#readme", "license": "MIT-0", diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index ecd069c778..6a51589d37 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -163,7 +163,7 @@ class Metrics implements MetricsInterface { * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; * import { Context } from 'aws-lambda'; * - * const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + * const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); * * export const handler = async (event: any, context: Context) => { * metrics.captureColdStartMetric(); @@ -282,7 +282,7 @@ class Metrics implements MetricsInterface { * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; * import { Context } from 'aws-lambda'; * - * const metrics = new Metrics({namespace:"ServerlessAirline", service:"orders"}); + * const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); * * export const handler = async (event: any, context: Context) => { * metrics.raiseOnEmptyMetrics(); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index 935601f1ea..abd2488541 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -20,11 +20,11 @@ describe('Middy middleware', () => { test('when a metrics instance is passed WITH custom options, it prints the metrics in the stdout', async () => { // Prepare - const metrics = new Metrics({ namespace:'ServerlessAirline', service:'orders' }); + const metrics = new Metrics({ namespace:'serverlessAirline', service:'orders' }); const lambdaHandler = (): void => { - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); }; const metricsOptions: ExtraOptions = { raiseOnEmptyMetrics: true, @@ -59,7 +59,7 @@ describe('Middy middleware', () => { '_aws': { 'Timestamp': 1466424490000, 'CloudWatchMetrics': [{ - 'Namespace': 'ServerlessAirline', + 'Namespace': 'serverlessAirline', 'Dimensions': [ [ 'environment', 'aws_region', 'service', 'function_name' ] ], @@ -76,17 +76,17 @@ describe('Middy middleware', () => { '_aws': { 'Timestamp': 1466424490000, 'CloudWatchMetrics': [{ - 'Namespace': 'ServerlessAirline', + 'Namespace': 'serverlessAirline', 'Dimensions': [ [ 'environment', 'aws_region', 'service' ] ], - 'Metrics': [{ 'Name': 'SuccessfulBooking', 'Unit': 'Count' }], + 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], }], }, 'environment': 'prod', 'aws_region' : 'eu-central-1', 'service': 'orders', - 'SuccessfulBooking': 1, + 'successfulBooking': 1, })); }); @@ -94,11 +94,11 @@ describe('Middy middleware', () => { test('when a metrics instance is passed WITHOUT custom options, it prints the metrics in the stdout', async () => { // Prepare - const metrics = new Metrics({ namespace:'ServerlessAirline', service:'orders' }); + const metrics = new Metrics({ namespace:'serverlessAirline', service:'orders' }); const lambdaHandler = (): void => { - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); - metrics.addMetric('SuccessfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); }; const handler = middy(lambdaHandler).use(logMetrics(metrics)); @@ -129,15 +129,15 @@ describe('Middy middleware', () => { '_aws': { 'Timestamp': 1466424490000, 'CloudWatchMetrics': [{ - 'Namespace': 'ServerlessAirline', + 'Namespace': 'serverlessAirline', 'Dimensions': [ ['service'] ], - 'Metrics': [{ 'Name': 'SuccessfulBooking', 'Unit': 'Count' }], + 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], }], }, 'service': 'orders', - 'SuccessfulBooking': 1, + 'successfulBooking': 1, })); }); From f4301950c199c8890505dad2f176954c0bb1337b Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Tue, 21 Dec 2021 19:22:39 +0100 Subject: [PATCH 04/29] test(metrics): add array input for middleware --- packages/metrics/src/middleware/middy.ts | 33 +++++++----- packages/metrics/tests/unit/Metrics.test.ts | 4 +- .../tests/unit/middleware/middy.test.ts | 53 +++++++++++++++++++ 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index 00e6ea9be5..c699e2a3c6 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -2,23 +2,30 @@ import type { Metrics } from '../Metrics'; import middy from '@middy/core'; import { ExtraOptions } from '../types'; -const logMetrics = (metrics: Metrics, options: ExtraOptions = {}): middy.MiddlewareObj => { +const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): middy.MiddlewareObj => { + const metricsInstances = target instanceof Array ? target : [target]; + const logMetricsBefore = async (request: middy.Request): Promise => { - metrics.setFunctionName(request.context.functionName); - const { raiseOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; - if (raiseOnEmptyMetrics !== undefined) { - metrics.raiseOnEmptyMetrics(); - } - if (defaultDimensions !== undefined) { - metrics.setDefaultDimensions(defaultDimensions); - } - if (captureColdStartMetric !== undefined) { - metrics.captureColdStartMetric(); - } + metricsInstances.forEach((metrics: Metrics) => { + metrics.setFunctionName(request.context.functionName); + const { raiseOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; + if (raiseOnEmptyMetrics !== undefined) { + metrics.raiseOnEmptyMetrics(); + } + if (defaultDimensions !== undefined) { + metrics.setDefaultDimensions(defaultDimensions); + } + if (captureColdStartMetric !== undefined) { + metrics.captureColdStartMetric(); + } + }); + }; const logMetricsAfter = async (): Promise => { - metrics.purgeStoredMetrics(); + metricsInstances.forEach((metrics: Metrics) => { + metrics.purgeStoredMetrics(); + }); }; return { diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index dfbed52f0e..8144b5ecc5 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -229,7 +229,7 @@ describe('Class: Metrics', () => { test('Cold start metric should only be written out once and flushed automatically', async () => { const metrics = new Metrics({ namespace: 'test' }); - const handler = async (_event, _context): Promise => { + const handler = async (_event: unknown, _context: unknown): Promise => { // Should generate only one log metrics.captureColdStartMetric(); }; @@ -363,7 +363,7 @@ describe('Class: Metrics', () => { expect.assertions(1); const metrics = new Metrics({ namespace: 'test' }); - const handler = async (_event, _context): Promise => { + const handler = async (_event: unknown, _context: unknown): Promise => { metrics.raiseOnEmptyMetrics(); // Logic goes here metrics.purgeStoredMetrics(); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index abd2488541..4e37c15c63 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -142,6 +142,59 @@ describe('Middy middleware', () => { }); + test('when an array of Metrics instances is passed, it prints the metrics in the stdout', async () => { + + // Prepare + const metrics = new Metrics({ namespace:'serverlessAirline', service:'orders' }); + + const lambdaHandler = (): void => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + }; + const metricsOptions: ExtraOptions = { + raiseOnEmptyMetrics: true + }; + const handler = middy(lambdaHandler).use(logMetrics([metrics], metricsOptions)); + const event = { foo: 'bar' }; + const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); + + const awsRequestId = getRandomInt().toString(); + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', + awsRequestId: awsRequestId, + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + + // Act + await handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ + '_aws': { + 'Timestamp': 1466424490000, + 'CloudWatchMetrics': [{ + 'Namespace': 'serverlessAirline', + 'Dimensions': [ + ['service'] + ], + 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], + }], + }, + 'service': 'orders', + 'successfulBooking': 1, + })); + + }); + }); }); \ No newline at end of file From 69100ce9ed7d777ae616123f9ccd3e051812e93b Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:31:29 +0100 Subject: [PATCH 05/29] Update packages/metrics/package.json Co-authored-by: Andrea Amorosi --- packages/metrics/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/metrics/package.json b/packages/metrics/package.json index f1d9fcaf0b..26fd40d892 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -26,7 +26,7 @@ "example:empty-metrics": "ts-node examples/empty-metrics.ts", "example:single-metric": "ts-node examples/single-metric.ts", "example:cold-start": "ts-node examples/cold-start.ts", - "example:programatic-access": "ts-node examples/programmatic-access.ts", + "example:programmatic-access": "ts-node examples/programmatic-access.ts", "example:constructor-options": "ts-node examples/constructor-options.ts", "example:default-dimensions-constructor": "ts-node examples/default-dimensions-constructor.ts", "example:hello-world-decorator": "ts-node examples/decorator/hello-world.ts", From d3d13ea045ed6e71537a9ca1ff84464f3dab90a1 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:05:52 +0100 Subject: [PATCH 06/29] Update docs/core/metrics.md Co-authored-by: ijemmy --- 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 1d277e5043..a463bd279d 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -115,7 +115,7 @@ You can use add default dimensions to your metrics by passing them as parameters * in the constructor * in the Middy middleware -* using the setDefaultDimensions` method +* using the `setDefaultDimensions` method * in the decorator If you'd like to remove them at some point, you can use `clearDefaultDimensions` method. From ef6a58e2a344276e8dabb5cdbdc25ba2a182d3d9 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:06:03 +0100 Subject: [PATCH 07/29] Update docs/core/metrics.md Co-authored-by: ijemmy --- 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 a463bd279d..50c6d70717 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -138,7 +138,7 @@ See examples below: } ``` -=== "logMetrics middleware" +=== "Middy middleware" ```typescript hl_lines="5" import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; From f52e19821630aefe39dc854b549c9f641c906865 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:06:54 +0100 Subject: [PATCH 08/29] Update docs/core/metrics.md Co-authored-by: ijemmy --- 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 50c6d70717..bebe71c08a 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -209,7 +209,7 @@ The middleware and the decorator also **validate**, **serialize**, and **flush** * Metric units must be [supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) -#### Using a middleware +#### Using Middy middleware See below an example of how to automatically flush metrics with the Middy-compatible `logMetrics` middleware. From d503075e2143935320779fbf544858131aacc866 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:07:15 +0100 Subject: [PATCH 09/29] Update docs/core/metrics.md Co-authored-by: ijemmy --- 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 bebe71c08a..d247956380 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -309,7 +309,7 @@ export class MyFunction { #### Manually -If you wish to do so, you can manually flush the metrics with `purgeStoredMetrics` and clear metrics as follows: +You can manually flush the metrics with `purgeStoredMetrics` as follows: !!! warning Metrics, dimensions and namespace validation still applies. From 419c8d237fea5586b9870da3a6e0f2fa9ad8dad5 Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 13:47:27 +0100 Subject: [PATCH 10/29] improv(metrics): improve docs, examples, naming --- docs/core/metrics.md | 38 ++++++++++--------- ...atic-access.ts => manual-metrics-print.ts} | 0 packages/metrics/examples/empty-metrics.ts | 3 +- ...atic-access.ts => manual-metrics-print.ts} | 0 packages/metrics/package.json | 4 +- 5 files changed, 24 insertions(+), 21 deletions(-) rename packages/metrics/examples/decorator/{programatic-access.ts => manual-metrics-print.ts} (100%) rename packages/metrics/examples/{programmatic-access.ts => manual-metrics-print.ts} (100%) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 1d277e5043..1577a42d19 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -171,27 +171,25 @@ See examples below: === "with logMetrics decorator" - ```typescript hl_lines="6 12" + ```typescript hl_lines="9" import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - import { Context, Callback } from 'aws-lambda'; - import middy from '@middy/core'; - + import { Context, Callback } from 'aws-lambda'; const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); const DEFAULT_DIMENSIONS = {"environment": "prod", "another": "one"}; - export class MyFunction { - @metrics.logMetrics({defaultDimensions: DEFAULT_DIMENSIONS}) - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + @metrics.logMetrics({defaultDimensions: DEFAULT_DIMENSIONS}) + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + } } ``` ### Flushing metrics -As you finish adding all your metrics, you need to serialize and flush them to standard output. +As you finish adding all your metrics, you need to serialize and "flush them" (= print them to standard output). You can flush metrics automatically using one of the following methods: @@ -199,7 +197,9 @@ You can flush metrics automatically using one of the following methods: * class decorator * manually -The middleware and the decorator also **validate**, **serialize**, and **flush** all your metrics. During metrics validation, if no metrics are provided then a warning will be logged, but no exception will be raised. +Using the Middy middleware or decorator will **automatically validate, serialize, and flush** all your metrics. During metrics validation, if no metrics are provided then a warning will be logged, but no exception will be raised. +If you do not the middleware or decorator, you have to flush your metrics manually. + !!! warning "Metric validation" If metrics are provided, and any of the following criteria are not met, a **`RangeError`** exception will be raised: @@ -257,10 +257,13 @@ See below an example of how to automatically flush metrics with the Middy-compat } ``` -#### Using the class decorator +#### Using a class decorator -Decorators can only be attached to a class declaration, method, accessor, property, or parameter. Therefore, if prefer to write your handler as a standard function, check the middleware or manual methods instead. -See the [official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/decorators.html) for more details. +!!! info + Decorators can only be attached to a class declaration, method, accessor, property, or parameter. Therefore, if you prefer to write your handler as a standard function rather than a Class method, check the [middleware](#using-a-middleware) or [manual](#manually) method sections instead. + See the [official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/decorators.html) for more details. + +The `logMetrics` decorator of the metrics utility can be used when your Lambda handler function is implemented as method of a Class. ```typescript hl_lines="8" @@ -309,7 +312,7 @@ export class MyFunction { #### Manually -If you wish to do so, you can manually flush the metrics with `purgeStoredMetrics` and clear metrics as follows: +If you wish to do so, you can manually flush metrics with `purgeStoredMetrics` and clear metrics as follows: !!! warning Metrics, dimensions and namespace validation still applies. @@ -392,7 +395,7 @@ If it's a cold start invocation, this feature will: This has the advantage of keeping cold start metric separate from your application metrics, where you might have unrelated dimensions. -!!! info "We do not emit 0 as a value for the ColdStart metric for cost-efficiency reasons. [Let us know](https://github.com/awslabs/aws-lambda-powertools-typecsript/issues/new?assignees=&labels=feature-request%2C+triage&template=feature_request.md&title=) if you'd prefer a flag to override it." +!!! info "We do not emit 0 as a value for the ColdStart metric for cost-efficiency reasons. [Let us know](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/new?assignees=&labels=feature-request%2C+triage&template=feature_request.md&title=) if you'd prefer a flag to override it." ## Advanced @@ -459,10 +462,9 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `singleMetr === "logMetrics middleware" - ```typescript hl_lines="8 10 12" + ```typescript hl_lines="7 9 11 15" import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; import { Context } from 'aws-lambda'; - import middy from '@middy/core'; const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); @@ -480,7 +482,7 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `singleMetr === "logMetrics decorator" - ```typescript hl_lines="11 13 15" + ```typescript hl_lines="9 11 13 15" import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; import { Context, Callback } from 'aws-lambda'; import middy from '@middy/core'; diff --git a/packages/metrics/examples/decorator/programatic-access.ts b/packages/metrics/examples/decorator/manual-metrics-print.ts similarity index 100% rename from packages/metrics/examples/decorator/programatic-access.ts rename to packages/metrics/examples/decorator/manual-metrics-print.ts diff --git a/packages/metrics/examples/empty-metrics.ts b/packages/metrics/examples/empty-metrics.ts index 01aaefccfd..00c6e60870 100644 --- a/packages/metrics/examples/empty-metrics.ts +++ b/packages/metrics/examples/empty-metrics.ts @@ -13,7 +13,8 @@ process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; const metrics = new Metrics(); const lambdaHandler = async (): Promise => { - // No metric added + // Notice that no metrics are added + // Since the raiseOnEmptyMetrics parameter is set to true, the Powertool throw an Error }; const handlerWithMiddleware = middy(lambdaHandler) diff --git a/packages/metrics/examples/programmatic-access.ts b/packages/metrics/examples/manual-metrics-print.ts similarity index 100% rename from packages/metrics/examples/programmatic-access.ts rename to packages/metrics/examples/manual-metrics-print.ts diff --git a/packages/metrics/package.json b/packages/metrics/package.json index f1d9fcaf0b..a0764fc966 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -26,7 +26,7 @@ "example:empty-metrics": "ts-node examples/empty-metrics.ts", "example:single-metric": "ts-node examples/single-metric.ts", "example:cold-start": "ts-node examples/cold-start.ts", - "example:programatic-access": "ts-node examples/programmatic-access.ts", + "example:manual-metrics-print": "ts-node examples/manual-metrics-print.ts", "example:constructor-options": "ts-node examples/constructor-options.ts", "example:default-dimensions-constructor": "ts-node examples/default-dimensions-constructor.ts", "example:hello-world-decorator": "ts-node examples/decorator/hello-world.ts", @@ -36,7 +36,7 @@ "example:empty-metrics-decorator": "ts-node examples/decorator/empty-metrics.ts", "example:single-metric-decorator": "ts-node examples/decorator/single-metric.ts", "example:cold-start-decorator": "ts-node examples/decorator/cold-start.ts", - "example:programatic-access-decorator": "ts-node examples/decorator/programatic-access.ts", + "example:manual-metrics-print-decorator": "ts-node examples/decorator/manual-metrics-print.ts", "example:constructor-options-decorator": "ts-node examples/decorator/constructor-options.ts", "example:default-dimensions-constructor-decorator": "ts-node examples/decorator/default-dimensions-constructor.ts" }, From ac08ffe5f0f443c7932ec9a3b0e829c15a4b8351 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 13:51:42 +0100 Subject: [PATCH 11/29] Update packages/metrics/examples/decorator/default-dimensions.ts Co-authored-by: ijemmy --- packages/metrics/examples/decorator/default-dimensions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/metrics/examples/decorator/default-dimensions.ts b/packages/metrics/examples/decorator/default-dimensions.ts index 9c8a06ddcb..6ef2233262 100644 --- a/packages/metrics/examples/decorator/default-dimensions.ts +++ b/packages/metrics/examples/decorator/default-dimensions.ts @@ -21,6 +21,7 @@ class Lambda implements LambdaInterface { metrics.addDimension('environment', 'dev'); metrics.addDimension('application', 'hello-world-dev'); metrics.addMetric('test-metric', MetricUnits.Count, 10); + // You can override the default dimensions by clearing the existing metrics first. Note that the cleared metric will be dropped, it will NOT be published to CloudWatch const metricsObject = metrics.serializeMetrics(); metrics.clearMetrics(); metrics.clearDimensions(); From a42b05006444197eb3b8e73efe1a80b2de15a4cc Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 13:54:42 +0100 Subject: [PATCH 12/29] Update packages/metrics/examples/decorator/empty-metrics.ts Co-authored-by: ijemmy --- packages/metrics/examples/decorator/empty-metrics.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/metrics/examples/decorator/empty-metrics.ts b/packages/metrics/examples/decorator/empty-metrics.ts index 3c9a4ac63d..e1822d73df 100644 --- a/packages/metrics/examples/decorator/empty-metrics.ts +++ b/packages/metrics/examples/decorator/empty-metrics.ts @@ -15,7 +15,8 @@ const metrics = new Metrics(); class Lambda implements LambdaInterface { - @metrics.logMetrics({ raiseOnEmptyMetrics: true }) + // Be default, we will not throw any error if there is no metrics. Use this property to override and throw an exception + @metrics.logMetrics({ raiseOnEmptyMetrics: true }) public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { } From af2ec16cf4ac2c72457b56eb71b7922a5b34e403 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:20:35 +0100 Subject: [PATCH 13/29] Update docs/core/metrics.md Co-authored-by: Florian Chazal --- 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 e96ed6bd09..4e593ac533 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -145,7 +145,7 @@ See examples below: import { Context } from 'aws-lambda'; import middy from '@middy/core'; - const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + const metrics = new Metrics({ namespace: 'serverlessAirline', service: 'orders' }); const lambdaHandler = async (event: any, context: Context) => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); From 3a9cd0dba7b4a8a7d4cef1f186498aa8ba74f08a Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:26:25 +0100 Subject: [PATCH 14/29] Update docs/core/metrics.md Co-authored-by: Florian Chazal --- 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 4e593ac533..61df856366 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -205,7 +205,7 @@ If you do not the middleware or decorator, you have to flush your metrics manual If metrics are provided, and any of the following criteria are not met, a **`RangeError`** exception will be raised: * Maximum of 9 dimensions - * Namespace is set, and no more than one + * Namespace is set only once (or none) * Metric units must be [supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) From 7ea58a85d593582edd048b8a0d6117844ede5c18 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:27:54 +0100 Subject: [PATCH 15/29] Update docs/core/metrics.md Co-authored-by: Florian Chazal --- 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 61df856366..6d492a66b4 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -219,7 +219,7 @@ See below an example of how to automatically flush metrics with the Middy-compat import { Context } from 'aws-lambda'; import middy from '@middy/core'; - const metrics = new Metrics({namespace:"exampleApplication", service:"exampleService"}); + const metrics = new Metrics({ namespace: 'exampleApplication' , service: 'exampleService' }); const lambdaHandler = async (event: any, context: Context) => { metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1); From 625b79d36435531e1fa48cbd5ddeacc03dfb0707 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:28:55 +0100 Subject: [PATCH 16/29] Update docs/core/metrics.md Co-authored-by: Florian Chazal --- 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 6d492a66b4..95035db3a4 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -360,7 +360,7 @@ You can optionally capture cold start metrics with the `logMetrics` middleware o import { Context } from 'aws-lambda'; import middy from '@middy/core'; - const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + const metrics = new Metrics({namespace: 'serverlessAirline', service: 'orders' }); const lambdaHandler = async (event: any, context: Context) => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); From 0d5c161764591f9d154268ce97ca8545a090b624 Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:29:30 +0100 Subject: [PATCH 17/29] Update docs/core/metrics.md Co-authored-by: Florian Chazal --- 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 95035db3a4..dfff930ffa 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -377,7 +377,7 @@ You can optionally capture cold start metrics with the `logMetrics` middleware o import { Context, Callback } from 'aws-lambda'; import middy from '@middy/core'; - const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); + const metrics = new Metrics({namespace: 'serverlessAirline', service: 'orders' }); export class MyFunction { From d75c40f44d0d6e147a29df3e68a96f8e41e9b62a Mon Sep 17 00:00:00 2001 From: Sara Gerion <47529391+saragerion@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:30:31 +0100 Subject: [PATCH 18/29] Update packages/metrics/examples/decorator/manual-flushing.ts Co-authored-by: ijemmy --- packages/metrics/examples/decorator/manual-flushing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/metrics/examples/decorator/manual-flushing.ts b/packages/metrics/examples/decorator/manual-flushing.ts index 3e4b0621dc..8bc38515cf 100644 --- a/packages/metrics/examples/decorator/manual-flushing.ts +++ b/packages/metrics/examples/decorator/manual-flushing.ts @@ -16,7 +16,7 @@ const lambdaHandler: Handler = async () => { metrics.addMetric('test-metric', MetricUnits.Count, 10); metrics.purgeStoredMetrics(); - //Metrics will be logged and cleared + //Metrics will be published and cleared return { foo: 'bar' From 3d423e5a4078e1548d6cb7a174d4d28ebbe4cc45 Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 14:58:50 +0100 Subject: [PATCH 19/29] improv(metrics): logMetricsAfterOrError middleware, examples update --- .../metrics/examples/decorator/single-metric.ts | 13 ++++++++----- packages/metrics/examples/single-metric.ts | 11 +++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/metrics/examples/decorator/single-metric.ts b/packages/metrics/examples/decorator/single-metric.ts index c19ba4aa5e..795a10c2da 100644 --- a/packages/metrics/examples/decorator/single-metric.ts +++ b/packages/metrics/examples/decorator/single-metric.ts @@ -3,7 +3,7 @@ import * as dummyEvent from '../../../tests/resources/events/custom/hello-world. import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; import { LambdaInterface } from './utils/lambda/LambdaInterface'; import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics, MetricUnits } from '../src'; +import { Metrics, MetricUnits } from '../../src'; // Populate runtime populateEnvironmentVariables(); @@ -16,11 +16,14 @@ class Lambda implements LambdaInterface { @metrics.logMetrics() public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addDimension('metricUnit', 'milliseconds'); + // This metric will have the "metricUnit" dimension, and no "metricType" dimension: + metrics.addMetric('latency', MetricUnits.Milliseconds, 56); + const singleMetric = metrics.singleMetric(); - metrics.addDimension('OuterDimension', 'true'); - singleMetric.addDimension('InnerDimension', 'true'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); - singleMetric.addMetric('single-metric', MetricUnits.Percent, 50); + // This metric will have the "metricType" dimension, and no "metricUnit" dimension: + singleMetric.addDimension('metricType', 'business'); + singleMetric.addMetric('videoClicked', MetricUnits.Count, 1); } } diff --git a/packages/metrics/examples/single-metric.ts b/packages/metrics/examples/single-metric.ts index aec3d36bfb..78f9d399d3 100644 --- a/packages/metrics/examples/single-metric.ts +++ b/packages/metrics/examples/single-metric.ts @@ -13,11 +13,14 @@ process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; const metrics = new Metrics(); const lambdaHandler = async (): Promise => { + metrics.addDimension('metricUnit', 'milliseconds'); + // This metric will have the "metricUnit" dimension, and no "metricType" dimension: + metrics.addMetric('latency', MetricUnits.Milliseconds, 56); + const singleMetric = metrics.singleMetric(); - metrics.addDimension('OuterDimension', 'true'); - singleMetric.addDimension('InnerDimension', 'true'); - metrics.addMetric('test-metric', MetricUnits.Count, 10); - singleMetric.addMetric('single-metric', MetricUnits.Percent, 50); + // This metric will have the "metricType" dimension, and no "metricUnit" dimension: + singleMetric.addDimension('metricType', 'business'); + singleMetric.addMetric('videoClicked', MetricUnits.Count, 1); }; const handlerWithMiddleware = middy(lambdaHandler) From 2b23c86d01dd0830b4e6f6944fd73ab01a31130e Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 14:59:47 +0100 Subject: [PATCH 20/29] improv(metrics): logMetricsAfterOrError middleware, examples update --- .../examples/decorator/empty-metrics.ts | 3 +- packages/metrics/src/middleware/middy.ts | 5 +- .../tests/unit/middleware/middy.test.ts | 73 +++++-------------- 3 files changed, 24 insertions(+), 57 deletions(-) diff --git a/packages/metrics/examples/decorator/empty-metrics.ts b/packages/metrics/examples/decorator/empty-metrics.ts index 3c9a4ac63d..4ef951418a 100644 --- a/packages/metrics/examples/decorator/empty-metrics.ts +++ b/packages/metrics/examples/decorator/empty-metrics.ts @@ -17,7 +17,8 @@ class Lambda implements LambdaInterface { @metrics.logMetrics({ raiseOnEmptyMetrics: true }) public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - + // Notice that no metrics are added + // Since the raiseOnEmptyMetrics parameter is set to true, the Powertool throw an Error } } diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index c699e2a3c6..6c9a077d3b 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -22,7 +22,7 @@ const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): mi }; - const logMetricsAfter = async (): Promise => { + const logMetricsAfterOrError = async (): Promise => { metricsInstances.forEach((metrics: Metrics) => { metrics.purgeStoredMetrics(); }); @@ -30,7 +30,8 @@ const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): mi return { before: logMetricsBefore, - after: logMetricsAfter + after: logMetricsAfterOrError, + onError: logMetricsAfterOrError }; }; diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index 4e37c15c63..14c65063fe 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -17,6 +17,25 @@ describe('Middy middleware', () => { describe('logMetrics', () => { + const event = { foo: 'bar' }; + const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); + const awsRequestId = getRandomInt().toString(); + + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', + awsRequestId: awsRequestId, + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + test('when a metrics instance is passed WITH custom options, it prints the metrics in the stdout', async () => { // Prepare @@ -32,24 +51,6 @@ describe('Middy middleware', () => { captureColdStartMetric: true }; const handler = middy(lambdaHandler).use(logMetrics(metrics, metricsOptions)); - const event = { foo: 'bar' }; - const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); - - const awsRequestId = getRandomInt().toString(); - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', - awsRequestId: awsRequestId, - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; // Act await handler(event, context, () => console.log('Lambda invoked!')); @@ -102,24 +103,6 @@ describe('Middy middleware', () => { }; const handler = middy(lambdaHandler).use(logMetrics(metrics)); - const event = { foo: 'bar' }; - const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); - - const awsRequestId = getRandomInt().toString(); - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', - awsRequestId: awsRequestId, - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; // Act await handler(event, context, () => console.log('Lambda invoked!')); @@ -155,24 +138,6 @@ describe('Middy middleware', () => { raiseOnEmptyMetrics: true }; const handler = middy(lambdaHandler).use(logMetrics([metrics], metricsOptions)); - const event = { foo: 'bar' }; - const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); - - const awsRequestId = getRandomInt().toString(); - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-central-1:123456789012:function:foo-bar-function', - awsRequestId: awsRequestId, - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; // Act await handler(event, context, () => console.log('Lambda invoked!')); From 9b295ed3f5b088cfef82050ac9e00516470e3adc Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 15:00:02 +0100 Subject: [PATCH 21/29] improv(metrics): logMetricsAfterOrError middleware, examples update --- docs/core/metrics.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index e96ed6bd09..29571f3013 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -462,18 +462,22 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `singleMetr === "logMetrics middleware" - ```typescript hl_lines="7 9 11 15" + ```typescript hl_lines="12 14 15" import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics'; import { Context } from 'aws-lambda'; + import middy from '@middy/core'; const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); const lambdaHandler = async (event: any, context: Context) => { - const singleMetric = metrics.singleMetric(); - metrics.addDimension('outerDimension', 'true'); - singleMetric.addDimension('innerDimension', 'true'); - metrics.addMetric('testMetric', MetricUnits.Count, 10); - singleMetric.addMetric('singleMetric', MetricUnits.Percent, 50); + metrics.addDimension('metricUnit', 'milliseconds'); + // This metric will have the "metricUnit" dimension, and no "metricType" dimension: + metrics.addMetric('latency', MetricUnits.Milliseconds, 56); + + const singleMetric = metrics.singleMetric(); + // This metric will have the "metricType" dimension, and no "metricUnit" dimension: + singleMetric.addDimension('metricType', 'business'); + singleMetric.addMetric('orderSubmitted', MetricUnits.Count, 1); } export const handler = middy(lambdaHandler) @@ -482,10 +486,9 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `singleMetr === "logMetrics decorator" - ```typescript hl_lines="9 11 13 15" + ```typescript hl_lines="14 16 17" import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - import { Context, Callback } from 'aws-lambda'; - import middy from '@middy/core'; + import { Context, Callback } from 'aws-lambda'; const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"}); @@ -493,11 +496,14 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `singleMetr @metrics.logMetrics() public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addDimension('metricUnit', 'milliseconds'); + // This metric will have the "metricUnit" dimension, and no "metricType" dimension: + metrics.addMetric('latency', MetricUnits.Milliseconds, 56); + const singleMetric = metrics.singleMetric(); - metrics.addDimension('outerDimension', 'true'); - singleMetric.addDimension('innerDimension', 'true'); - metrics.addMetric('testMetric', MetricUnits.Count, 10); - singleMetric.addMetric('singleMetric', MetricUnits.Percent, 50); + // This metric will have the "metricType" dimension, and no "metricUnit" dimension: + singleMetric.addDimension('metricType', 'business'); + singleMetric.addMetric('orderSubmitted', MetricUnits.Count, 1); } } ``` From 29d1c0556d02ed9419808ab00ace1b9847786b3a Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 16:07:05 +0100 Subject: [PATCH 22/29] tests(metrics): fix handlers type --- packages/metrics/jest.config.js | 1 + packages/metrics/tests/unit/Metrics.test.ts | 17 +++++++++++++++-- .../metrics/tests/unit/middleware/middy.test.ts | 10 +++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/metrics/jest.config.js b/packages/metrics/jest.config.js index 6fbaae512f..118de05065 100644 --- a/packages/metrics/jest.config.js +++ b/packages/metrics/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + 'runner': 'groups', 'preset': 'ts-jest', 'transform': { '^.+\\.ts?$': 'ts-jest', diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 8144b5ecc5..1f3f70d1f7 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -16,6 +16,19 @@ interface LooseObject { [key: string]: string } +type DummyEvent = { + key1: string + key2: string + key3: string +}; + +/** + * Metrics tests + * + * @group metrics + * @group unit/Metrics + */ + describe('Class: Metrics', () => { const originalEnvironmentVariables = process.env; @@ -229,7 +242,7 @@ describe('Class: Metrics', () => { test('Cold start metric should only be written out once and flushed automatically', async () => { const metrics = new Metrics({ namespace: 'test' }); - const handler = async (_event: unknown, _context: unknown): Promise => { + const handler = async (_event: DummyEvent, _context: Context): Promise => { // Should generate only one log metrics.captureColdStartMetric(); }; @@ -363,7 +376,7 @@ describe('Class: Metrics', () => { expect.assertions(1); const metrics = new Metrics({ namespace: 'test' }); - const handler = async (_event: unknown, _context: unknown): Promise => { + const handler = async (_event: DummyEvent, _context: Context): Promise => { metrics.raiseOnEmptyMetrics(); // Logic goes here metrics.purgeStoredMetrics(); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index 14c65063fe..8527546d5c 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -1,12 +1,21 @@ import { logMetrics } from '../../../../metrics/src/middleware'; import { Metrics, MetricUnits } from '../../../../metrics/src'; import middy from '@middy/core'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import * as event from '../../../../../tests/resources/events/custom/hello-world.json'; import { ExtraOptions } from '../../../src/types'; const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string); +/** + * Metrics tests + * + * @group metrics + * @group unit/middleware + */ describe('Middy middleware', () => { beforeEach(() => { @@ -17,7 +26,6 @@ describe('Middy middleware', () => { describe('logMetrics', () => { - const event = { foo: 'bar' }; const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); const awsRequestId = getRandomInt().toString(); From d23bbc534c6cccc1379c4b1209e56a12f2df3aa8 Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 16:11:05 +0100 Subject: [PATCH 23/29] fix(metrics): examples file path --- packages/metrics/examples/decorator/cold-start.ts | 10 +++++----- .../metrics/examples/decorator/constructor-options.ts | 10 +++++----- .../decorator/default-dimensions-constructor.ts | 10 +++++----- .../metrics/examples/decorator/default-dimensions.ts | 2 +- packages/metrics/examples/decorator/dimensions.ts | 2 +- packages/metrics/examples/decorator/empty-metrics.ts | 6 +++--- packages/metrics/examples/decorator/hello-world.ts | 2 +- packages/metrics/examples/decorator/manual-flushing.ts | 2 +- .../metrics/examples/decorator/manual-metrics-print.ts | 2 +- packages/metrics/examples/decorator/single-metric.ts | 8 ++++---- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/metrics/examples/decorator/cold-start.ts b/packages/metrics/examples/decorator/cold-start.ts index 693e8d5bba..de005cf9a0 100644 --- a/packages/metrics/examples/decorator/cold-start.ts +++ b/packages/metrics/examples/decorator/cold-start.ts @@ -1,9 +1,9 @@ -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; -import { populateEnvironmentVariables } from '../tests/helpers'; +import * as dummyEvent from '../../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './../utils/lambda/LambdaInterface'; +import { populateEnvironmentVariables } from '../../tests/helpers'; import { Callback, Context } from 'aws-lambda/handler'; -import { Metrics, MetricUnits } from '../src'; +import { Metrics, MetricUnits } from '../../src'; // Populate runtime populateEnvironmentVariables(); diff --git a/packages/metrics/examples/decorator/constructor-options.ts b/packages/metrics/examples/decorator/constructor-options.ts index 67be0ace59..b95ea09b48 100644 --- a/packages/metrics/examples/decorator/constructor-options.ts +++ b/packages/metrics/examples/decorator/constructor-options.ts @@ -1,8 +1,8 @@ -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { Metrics, MetricUnits } from '../src'; -import { LambdaInterface } from "./utils/lambda"; -import { Callback, Context } from "aws-lambda/handler"; +import * as dummyEvent from '../../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../../tests/resources/contexts/hello-world'; +import { Metrics, MetricUnits } from '../../src'; +import { LambdaInterface } from './../utils/lambda'; +import { Callback, Context } from 'aws-lambda/handler'; const metrics = new Metrics({ namespace: 'hello-world-constructor', diff --git a/packages/metrics/examples/decorator/default-dimensions-constructor.ts b/packages/metrics/examples/decorator/default-dimensions-constructor.ts index cb01c59c61..bbd8993e4a 100644 --- a/packages/metrics/examples/decorator/default-dimensions-constructor.ts +++ b/packages/metrics/examples/decorator/default-dimensions-constructor.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); @@ -16,11 +16,11 @@ const metrics = new Metrics({ defaultDimensions:{ 'application': 'hello-world' } class Lambda implements LambdaInterface { - @metrics.logMetrics() - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - metrics.addMetric('test-metric', MetricUnits.Count, 10); + @metrics.logMetrics() + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + metrics.addMetric('test-metric', MetricUnits.Count, 10); - } + } } diff --git a/packages/metrics/examples/decorator/default-dimensions.ts b/packages/metrics/examples/decorator/default-dimensions.ts index 6ef2233262..b08b4fbae9 100644 --- a/packages/metrics/examples/decorator/default-dimensions.ts +++ b/packages/metrics/examples/decorator/default-dimensions.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); diff --git a/packages/metrics/examples/decorator/dimensions.ts b/packages/metrics/examples/decorator/dimensions.ts index 0058344517..6c2289237c 100644 --- a/packages/metrics/examples/decorator/dimensions.ts +++ b/packages/metrics/examples/decorator/dimensions.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); diff --git a/packages/metrics/examples/decorator/empty-metrics.ts b/packages/metrics/examples/decorator/empty-metrics.ts index bbb34183d6..91be760dc6 100644 --- a/packages/metrics/examples/decorator/empty-metrics.ts +++ b/packages/metrics/examples/decorator/empty-metrics.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); @@ -15,8 +15,8 @@ const metrics = new Metrics(); class Lambda implements LambdaInterface { - // Be default, we will not throw any error if there is no metrics. Use this property to override and throw an exception - @metrics.logMetrics({ raiseOnEmptyMetrics: true }) + // Be default, we will not throw any error if there is no metrics. Use this property to override and throw an exception + @metrics.logMetrics({ raiseOnEmptyMetrics: true }) public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { // Notice that no metrics are added // Since the raiseOnEmptyMetrics parameter is set to true, the Powertool throw an Error diff --git a/packages/metrics/examples/decorator/hello-world.ts b/packages/metrics/examples/decorator/hello-world.ts index 088cb0a0c4..f522180e30 100644 --- a/packages/metrics/examples/decorator/hello-world.ts +++ b/packages/metrics/examples/decorator/hello-world.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); diff --git a/packages/metrics/examples/decorator/manual-flushing.ts b/packages/metrics/examples/decorator/manual-flushing.ts index 8bc38515cf..a0a9c8f26f 100644 --- a/packages/metrics/examples/decorator/manual-flushing.ts +++ b/packages/metrics/examples/decorator/manual-flushing.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); diff --git a/packages/metrics/examples/decorator/manual-metrics-print.ts b/packages/metrics/examples/decorator/manual-metrics-print.ts index aaea1e1925..0fbae05516 100644 --- a/packages/metrics/examples/decorator/manual-metrics-print.ts +++ b/packages/metrics/examples/decorator/manual-metrics-print.ts @@ -1,4 +1,4 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; +import { populateEnvironmentVariables } from '../../tests/helpers'; // Populate runtime populateEnvironmentVariables(); diff --git a/packages/metrics/examples/decorator/single-metric.ts b/packages/metrics/examples/decorator/single-metric.ts index 795a10c2da..0e0c84c6c8 100644 --- a/packages/metrics/examples/decorator/single-metric.ts +++ b/packages/metrics/examples/decorator/single-metric.ts @@ -1,7 +1,7 @@ -import { populateEnvironmentVariables } from '../tests/helpers'; -import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json'; -import { context as dummyContext } from '../../../tests/resources/contexts/hello-world'; -import { LambdaInterface } from './utils/lambda/LambdaInterface'; +import { populateEnvironmentVariables } from '../../tests/helpers'; +import * as dummyEvent from '../../../../tests/resources/events/custom/hello-world.json'; +import { context as dummyContext } from '../../../../tests/resources/contexts/hello-world'; +import { LambdaInterface } from './../utils/lambda/LambdaInterface'; import { Callback, Context } from 'aws-lambda/handler'; import { Metrics, MetricUnits } from '../../src'; From 79411198462abe661b9a8dfdf81a8f9b26f20955 Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 16:17:52 +0100 Subject: [PATCH 24/29] revert(metrics): jest groups --- packages/metrics/jest.config.js | 1 - packages/metrics/tests/unit/Metrics.test.ts | 7 ------- packages/metrics/tests/unit/middleware/middy.test.ts | 6 ------ 3 files changed, 14 deletions(-) diff --git a/packages/metrics/jest.config.js b/packages/metrics/jest.config.js index 118de05065..6fbaae512f 100644 --- a/packages/metrics/jest.config.js +++ b/packages/metrics/jest.config.js @@ -1,5 +1,4 @@ module.exports = { - 'runner': 'groups', 'preset': 'ts-jest', 'transform': { '^.+\\.ts?$': 'ts-jest', diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 1f3f70d1f7..5fafbdb43b 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -22,13 +22,6 @@ type DummyEvent = { key3: string }; -/** - * Metrics tests - * - * @group metrics - * @group unit/Metrics - */ - describe('Class: Metrics', () => { const originalEnvironmentVariables = process.env; diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index 8527546d5c..046a6ac207 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -10,12 +10,6 @@ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string); -/** - * Metrics tests - * - * @group metrics - * @group unit/middleware - */ describe('Middy middleware', () => { beforeEach(() => { From 3b6b053c9bba86d267e333eb58b7971529e6a52b Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 16:20:56 +0100 Subject: [PATCH 25/29] chore(metrics): rename examples script, prepending 'decorator' --- packages/metrics/package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/metrics/package.json b/packages/metrics/package.json index a0764fc966..cd2515f035 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -29,16 +29,16 @@ "example:manual-metrics-print": "ts-node examples/manual-metrics-print.ts", "example:constructor-options": "ts-node examples/constructor-options.ts", "example:default-dimensions-constructor": "ts-node examples/default-dimensions-constructor.ts", - "example:hello-world-decorator": "ts-node examples/decorator/hello-world.ts", - "example:manual-flushing-decorator": "ts-node examples/decorator/manual-flushing.ts", - "example:dimensions-decorator": "ts-node examples/decorator/dimensions.ts", - "example:default-dimensions-decorator": "ts-node examples/decorator/default-dimensions.ts", - "example:empty-metrics-decorator": "ts-node examples/decorator/empty-metrics.ts", - "example:single-metric-decorator": "ts-node examples/decorator/single-metric.ts", - "example:cold-start-decorator": "ts-node examples/decorator/cold-start.ts", - "example:manual-metrics-print-decorator": "ts-node examples/decorator/manual-metrics-print.ts", - "example:constructor-options-decorator": "ts-node examples/decorator/constructor-options.ts", - "example:default-dimensions-constructor-decorator": "ts-node examples/decorator/default-dimensions-constructor.ts" + "example:decorator-hello-world": "ts-node examples/decorator/hello-world.ts", + "example:decorator-manual-flushing": "ts-node examples/decorator/manual-flushing.ts", + "example:decorator-dimensions": "ts-node examples/decorator/dimensions.ts", + "example:decorator-default-dimensions": "ts-node examples/decorator/default-dimensions.ts", + "example:decorator-empty-metrics": "ts-node examples/decorator/empty-metrics.ts", + "example:decorator-single-metric": "ts-node examples/decorator/single-metric.ts", + "example:decorator-cold-start": "ts-node examples/decorator/cold-start.ts", + "example:decorator-manual-metrics-print": "ts-node examples/decorator/manual-metrics-print.ts", + "example:decorator-constructor-options": "ts-node examples/decorator/constructor-options.ts", + "example:decorator-default-dimensions-constructor": "ts-node examples/decorator/default-dimensions-constructor.ts" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/metrics#readme", "license": "MIT-0", From 0ab77dae573058e07f3fa1b6ebc3baceb26c862b Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 20:53:33 +0100 Subject: [PATCH 26/29] fix(metrics): removed empty lint file --- packages/metrics/.eslintrc.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/metrics/.eslintrc.json diff --git a/packages/metrics/.eslintrc.json b/packages/metrics/.eslintrc.json deleted file mode 100644 index e69de29bb2..0000000000 From c90915458f1947e328247eaedae00377f8476fcf Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 20:57:10 +0100 Subject: [PATCH 27/29] ci(all): exclude examples CDK for linting --- .eslintignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 281bb335cd..936d1f2c1b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ coverage lib node_modules -tests/resources/* \ No newline at end of file +tests/resources/* +examples/cdk/* \ No newline at end of file From a95f08cf43b5afa47a28cf40e6742b3c7448e464 Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 21:04:26 +0100 Subject: [PATCH 28/29] chore(all): lint fix for CDK examples --- .eslintignore | 3 +-- .../metrics/tests/e2e/standardFunctions.test.MyFunction.ts | 3 ++- packages/metrics/tests/e2e/standardFunctions.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.eslintignore b/.eslintignore index 936d1f2c1b..281bb335cd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,4 @@ coverage lib node_modules -tests/resources/* -examples/cdk/* \ No newline at end of file +tests/resources/* \ No newline at end of file diff --git a/packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts b/packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts index 519e49cacd..a848e71416 100644 --- a/packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts +++ b/packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts @@ -1,4 +1,5 @@ import { Metrics, MetricUnits } from '../../src'; +import { Context } from 'aws-lambda'; const namespace = process.env.EXPECTED_NAMESPACE ?? 'CDKExample'; const serviceName = process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandardHandler'; @@ -14,7 +15,7 @@ const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? 2; const metrics = new Metrics({ namespace: namespace, service: serviceName }); -export const handler = async (event: any, context: any) => { +export const handler = async (_event: unknown, _context: Context): Promise => { metrics.captureColdStartMetric(); metrics.raiseOnEmptyMetrics(); metrics.setDefaultDimensions(JSON.parse(defaultDimensions)); diff --git a/packages/metrics/tests/e2e/standardFunctions.test.ts b/packages/metrics/tests/e2e/standardFunctions.test.ts index a4e6a72e36..76b738501d 100644 --- a/packages/metrics/tests/e2e/standardFunctions.test.ts +++ b/packages/metrics/tests/e2e/standardFunctions.test.ts @@ -10,7 +10,7 @@ import { randomUUID } from 'crypto'; import { Tracing } from '@aws-cdk/aws-lambda'; import * as lambda from '@aws-cdk/aws-lambda-nodejs'; -import { App, Stack, CfnOutput } from '@aws-cdk/core'; +import { App, Stack } from '@aws-cdk/core'; import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; import * as AWS from 'aws-sdk'; @@ -38,7 +38,7 @@ describe('coldstart', () => { const expectedSingleMetricUnit = MetricUnits.Percent; const expectedSingleMetricValue = '2'; const functionName = 'MyFunctionWithStandardHandler'; - const myFunctionWithStandardFunctions = new lambda.NodejsFunction(stack, 'MyFunction', { + new lambda.NodejsFunction(stack, 'MyFunction', { functionName: functionName, tracing: Tracing.ACTIVE, environment: { From 28b9df1ed0daec104d63407b6b206eb54d22ec2b Mon Sep 17 00:00:00 2001 From: Sara Gerion Date: Mon, 27 Dec 2021 21:19:02 +0100 Subject: [PATCH 29/29] test(metrics): added metrics middleware jest group --- packages/metrics/tests/unit/Metrics.test.ts | 4 ++-- packages/metrics/tests/unit/middleware/middy.test.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 34ef2aec30..2e348f39f5 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -1,7 +1,7 @@ /** - * Test metrics decorator + * Test Metrics class * - * @group unit/metrics/all + * @group unit/metrics/class */ import { ContextExamples as dummyContext, LambdaInterface } from '@aws-lambda-powertools/commons'; diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index 046a6ac207..8a48c3b0e9 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -1,3 +1,9 @@ +/** + * Test metrics middleware + * + * @group unit/metrics/middleware + */ + import { logMetrics } from '../../../../metrics/src/middleware'; import { Metrics, MetricUnits } from '../../../../metrics/src'; import middy from '@middy/core';