From 0814610eb5558c5d34478a0aa735e91749ef2354 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 11 May 2023 20:03:39 +0200 Subject: [PATCH] style(metrics): apply standardized formatting --- packages/metrics/.eslintrc.js | 72 + packages/metrics/jest.config.js | 53 +- packages/metrics/package.json | 9 +- packages/metrics/src/Metrics.ts | 139 +- packages/metrics/src/MetricsInterface.ts | 37 +- .../src/config/ConfigServiceInterface.ts | 12 +- .../src/config/EnvironmentVariablesService.ts | 11 +- packages/metrics/src/config/index.ts | 2 +- packages/metrics/src/constants.ts | 2 +- packages/metrics/src/index.ts | 2 +- packages/metrics/src/middleware/index.ts | 2 +- packages/metrics/src/middleware/middy.ts | 31 +- .../metrics/src/types/MetricResolution.ts | 5 +- packages/metrics/src/types/MetricUnit.ts | 57 +- packages/metrics/src/types/Metrics.ts | 67 +- packages/metrics/src/types/index.ts | 2 +- ...sicFeatures.decorator.test.functionCode.ts | 42 +- .../e2e/basicFeatures.decorators.test.ts | 232 +- .../basicFeatures.manual.test.functionCode.ts | 38 +- .../tests/e2e/basicFeatures.manual.test.ts | 218 +- packages/metrics/tests/e2e/constants.ts | 2 +- .../metrics/tests/helpers/metricsUtils.ts | 23 +- .../helpers/populateEnvironmentVariables.ts | 10 +- packages/metrics/tests/unit/Metrics.test.ts | 2015 +++++++++-------- .../tests/unit/middleware/middy.test.ts | 235 +- 25 files changed, 1871 insertions(+), 1447 deletions(-) create mode 100644 packages/metrics/.eslintrc.js diff --git a/packages/metrics/.eslintrc.js b/packages/metrics/.eslintrc.js new file mode 100644 index 0000000000..2e4ca18fb2 --- /dev/null +++ b/packages/metrics/.eslintrc.js @@ -0,0 +1,72 @@ +module.exports = { + env: { + browser: false, + es2020: true, + jest: true, + node: true, + }, + ignorePatterns: ['coverage', 'lib'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'prettier'], + settings: { + 'import/resolver': { + node: {}, + typescript: { + project: './tsconfig.json', + alwaysTryTypes: true, + }, + }, + }, + rules: { + '@typescript-eslint/explicit-function-return-type': [ + 'error', + { allowExpressions: true }, + ], // Enforce return type definitions for functions + '@typescript-eslint/explicit-member-accessibility': 'error', // Enforce explicit accessibility modifiers on class properties and methods (public, private, protected) + '@typescript-eslint/member-ordering': [ + // Standardize the order of class members + 'error', + { + default: { + memberTypes: [ + 'signature', + 'public-field', + 'protected-field', + 'private-field', + 'constructor', + 'public-method', + 'protected-method', + 'private-method', + ], + order: 'alphabetically', + }, + }, + ], + '@typescript-eslint/no-explicit-any': 'error', // Disallow usage of the any type + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Disallow unused variables, except for variables starting with an underscore + '@typescript-eslint/no-use-before-define': ['off'], // Check if this rule is needed + 'no-unused-vars': 'off', // Disable eslint core rule, since it's replaced by @typescript-eslint/no-unused-vars + // Rules from eslint core https://eslint.org/docs/latest/rules/ + 'array-bracket-spacing': ['error', 'never'], // Disallow spaces inside of array brackets + 'computed-property-spacing': ['error', 'never'], // Disallow spaces inside of computed properties + 'func-style': ['warn', 'expression'], // Enforce function expressions instead of function declarations + 'keyword-spacing': 'error', // Enforce spaces after keywords and before parenthesis, e.g. if (condition) instead of if(condition) + 'padding-line-between-statements': [ + // Require an empty line before return statements + 'error', + { blankLine: 'always', prev: '*', next: 'return' }, + ], + 'no-console': 0, // Allow console.log statements + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], // Disallow multiple spaces except for comments + 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], // Enforce no empty line at the beginning & end of files and max 1 empty line between consecutive statements + 'no-throw-literal': 'error', // Disallow throwing literals as exceptions, e.g. throw 'error' instead of throw new Error('error') + 'object-curly-spacing': ['error', 'always'], // Enforce spaces inside of curly braces in objects + 'prefer-arrow-callback': 'error', // Enforce arrow functions instead of anonymous functions for callbacks + quotes: ['error', 'single', { allowTemplateLiterals: true }], // Enforce single quotes except for template strings + semi: ['error', 'always'], // Require semicolons instead of ASI (automatic semicolon insertion) at the end of statements + }, +}; diff --git a/packages/metrics/jest.config.js b/packages/metrics/jest.config.js index 11773c6994..a187c129ed 100644 --- a/packages/metrics/jest.config.js +++ b/packages/metrics/jest.config.js @@ -3,41 +3,26 @@ module.exports = { name: 'AWS Lambda Powertools utility: METRICS', color: 'green', }, - 'runner': 'groups', - 'preset': 'ts-jest', - 'transform': { + runner: 'groups', + preset: 'ts-jest', + transform: { '^.+\\.ts?$': 'ts-jest', }, - moduleFileExtensions: [ 'js', 'ts' ], - 'collectCoverageFrom': [ - '**/src/**/*.ts', - '!**/node_modules/**', - ], - 'testMatch': ['**/?(*.)+(spec|test).ts'], - 'roots': [ - '/src', - '/tests', - ], - 'testPathIgnorePatterns': [ - '/node_modules/', - ], - 'testEnvironment': 'node', - 'coveragePathIgnorePatterns': [ - '/node_modules/', - ], - 'coverageThreshold': { - 'global': { - 'statements': 100, - 'branches': 100, - 'functions': 100, - 'lines': 100, + moduleFileExtensions: ['js', 'ts'], + collectCoverageFrom: ['**/src/**/*.ts', '!**/node_modules/**'], + testMatch: ['**/?(*.)+(spec|test).ts'], + roots: ['/src', '/tests'], + testPathIgnorePatterns: ['/node_modules/'], + testEnvironment: 'node', + coveragePathIgnorePatterns: ['/node_modules/'], + coverageThreshold: { + global: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, }, }, - 'coverageReporters': [ - 'json-summary', - 'text', - 'lcov' ], - 'setupFiles': [ - '/tests/helpers/populateEnvironmentVariables.ts' - ] -}; \ No newline at end of file + coverageReporters: ['json-summary', 'text', 'lcov'], + setupFiles: ['/tests/helpers/populateEnvironmentVariables.ts'], +}; diff --git a/packages/metrics/package.json b/packages/metrics/package.json index dea1265b6c..5801621181 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -19,14 +19,15 @@ "test:e2e": "jest --group=e2e", "watch": "jest --group=unit --watch ", "build": "tsc", - "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", - "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts,.js --no-error-on-unmatched-pattern .", + "lint-fix": "eslint --fix --ext .ts,.js --no-error-on-unmatched-pattern .", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh metrics-bundle ./dist", "prepare": "npm run build" }, "lint-staged": { - "*.ts": "npm run lint-fix" + "*.ts": "npm run lint-fix", + "*.js": "npm run lint-fix" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/metrics#readme", "license": "MIT-0", @@ -59,4 +60,4 @@ "serverless", "nodejs" ] -} +} \ No newline at end of file diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index b4bc9bc03e..168fa255c3 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -34,26 +34,26 @@ import { * * Context manager to create a one off metric with a different dimension * * ## Usage - * + * * ### Functions usage with middleware - * + * * Using this middleware on your handler function will automatically flush metrics after the function returns or throws an error. * Additionally, you can configure the middleware to easily: * * ensure that at least one metric is emitted before you flush them * * capture a `ColdStart` a metric * * set default dimensions for all your metrics - * + * * @example * ```typescript * import { Metrics, logMetrics } from '@aws-lambda-powertools/metrics'; * import middy from '@middy/core'; - * + * * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); - * + * * const lambdaHandler = async (_event: any, _context: any) => { * ... * }; - * + * * export const handler = middy(lambdaHandler).use(logMetrics(metrics)); * ``` * @@ -112,10 +112,10 @@ class Metrics extends Utility implements MetricsInterface { private dimensions: Dimensions = {}; private envVarsService?: EnvironmentVariablesService; private functionName?: string; - private isSingleMetric: boolean = false; + private isSingleMetric = false; private metadata: Record = {}; private namespace?: string; - private shouldThrowOnEmptyMetrics: boolean = false; + private shouldThrowOnEmptyMetrics = false; private storedMetrics: StoredMetrics = {}; public constructor(options: MetricsOptions = {}) { @@ -136,7 +136,9 @@ class Metrics extends Utility implements MetricsInterface { */ public addDimension(name: string, value: string): void { if (MAX_DIMENSION_COUNT <= this.getCurrentDimensionsCount()) { - throw new RangeError(`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`); + throw new RangeError( + `The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}` + ); } this.dimensions[name] = value; } @@ -157,7 +159,7 @@ class Metrics extends Utility implements MetricsInterface { throw new RangeError( `Unable to add ${ Object.keys(dimensions).length - } dimensions: the number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`, + } dimensions: the number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}` ); } this.dimensions = newDimensions; @@ -187,9 +189,9 @@ class Metrics extends Utility implements MetricsInterface { * @example * ```typescript * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - * + * * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); - * + * * metrics.addMetric('successfulBooking', MetricUnits.Count, 1); * ``` * @@ -200,25 +202,30 @@ class Metrics extends Utility implements MetricsInterface { * @example * ```typescript * import { Metrics, MetricUnits, MetricResolution } from '@aws-lambda-powertools/metrics'; - * + * * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); - * + * * metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High); * ``` * - * @param name - The metric name + * @param name - The metric name * @param unit - The metric unit * @param value - The metric value * @param resolution - The metric resolution */ - public addMetric(name: string, unit: MetricUnit, value: number, resolution: MetricResolution = MetricResolution.Standard): void { + public addMetric( + name: string, + unit: MetricUnit, + value: number, + resolution: MetricResolution = MetricResolution.Standard + ): void { this.storeMetric(name, unit, value, resolution); if (this.isSingleMetric) this.publishStoredMetrics(); } /** * Create a singleMetric to capture cold start. - * + * * If it's a cold start invocation, this feature will: * * Create a separate EMF blob that contains a single metric named ColdStart * * Add function_name and service dimensions @@ -242,7 +249,9 @@ class Metrics extends Utility implements MetricsInterface { const singleMetric = this.singleMetric(); if (this.defaultDimensions.service) { - singleMetric.setDefaultDimensions({ service: this.defaultDimensions.service }); + singleMetric.setDefaultDimensions({ + service: this.defaultDimensions.service, + }); } if (this.functionName != null) { singleMetric.addDimension('function_name', this.functionName); @@ -304,7 +313,8 @@ class Metrics extends Utility implements MetricsInterface { * @decorator Class */ public logMetrics(options: ExtraOptions = {}): HandlerMethodDecorator { - const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; + const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = + options; if (throwOnEmptyMetrics) { this.throwOnEmptyMetrics(); } @@ -315,7 +325,7 @@ class Metrics extends Utility implements MetricsInterface { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. - */ + */ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const originalMethod = descriptor.value!; @@ -323,21 +333,26 @@ class Metrics extends Utility implements MetricsInterface { const metricsRef = this; // Use a function() {} instead of an () => {} arrow function so that we can // access `myClass` as `this` in a decorated `myClass.myMethod()`. - descriptor.value = ( async function(this: Handler, event: unknown, context: Context, callback: Callback): Promise { + descriptor.value = async function ( + this: Handler, + event: unknown, + context: Context, + callback: Callback + ): Promise { metricsRef.functionName = context.functionName; if (captureColdStartMetric) metricsRef.captureColdStartMetric(); - + let result: unknown; try { - result = await originalMethod.apply(this, [ event, context, callback ]); + result = await originalMethod.apply(this, [event, context, callback]); } catch (error) { throw error; } finally { metricsRef.publishStoredMetrics(); } - + return result; - }); + }; return descriptor; }; @@ -361,10 +376,13 @@ class Metrics extends Utility implements MetricsInterface { * ``` */ public publishStoredMetrics(): void { - if (!this.shouldThrowOnEmptyMetrics && Object.keys(this.storedMetrics).length === 0) { + if ( + !this.shouldThrowOnEmptyMetrics && + Object.keys(this.storedMetrics).length === 0 + ) { console.warn( 'No application metrics to publish. The cold-start metric may be published if enabled. ' + - 'If application metrics should never be empty, consider using \'throwOnEmptyMetrics\'', + 'If application metrics should never be empty, consider using `throwOnEmptyMetrics`' ); } const target = this.serializeMetrics(); @@ -377,10 +395,10 @@ class Metrics extends Utility implements MetricsInterface { /** * Function to create a new metric object compliant with the EMF (Embedded Metric Format) schema which * includes the metric name, unit, and optionally storage resolution. - * + * * The function will create a new EMF blob and log it to standard output to be then ingested by Cloudwatch * logs and processed automatically for metrics creation. - * + * * @returns metrics as JSON object compliant EMF Schema Specification * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html for more details */ @@ -444,7 +462,7 @@ class Metrics extends Utility implements MetricsInterface { /** * Sets default dimensions that will be added to all metrics. - * + * * @param dimensions The default dimensions to be added to all metrics. */ public setDefaultDimensions(dimensions: Dimensions | undefined): void { @@ -513,11 +531,14 @@ class Metrics extends Utility implements MetricsInterface { /** * Gets the current number of dimensions stored. - * + * * @returns the number of dimensions currently stored */ private getCurrentDimensionsCount(): number { - return Object.keys(this.dimensions).length + Object.keys(this.defaultDimensions).length; + return ( + Object.keys(this.dimensions).length + + Object.keys(this.defaultDimensions).length + ); } /** @@ -535,7 +556,7 @@ class Metrics extends Utility implements MetricsInterface { * @returns the environment variables service */ private getEnvVarsService(): EnvironmentVariablesService { - return this.envVarsService; + return this.envVarsService as EnvironmentVariablesService; } /** @@ -552,12 +573,14 @@ class Metrics extends Utility implements MetricsInterface { * @returns true if the metric is new, false if another metric with the same name already exists */ private isNewMetric(name: string, unit: MetricUnit): boolean { - if (this.storedMetrics[name]){ + if (this.storedMetrics[name]) { if (this.storedMetrics[name].unit !== unit) { const currentUnit = this.storedMetrics[name].unit; - throw new Error(`Metric "${name}" has already been added with unit "${currentUnit}", but we received unit "${unit}". Did you mean to use metric unit "${currentUnit}"?`); + throw new Error( + `Metric "${name}" has already been added with unit "${currentUnit}", but we received unit "${unit}". Did you mean to use metric unit "${currentUnit}"?` + ); } - + return false; } else { return true; @@ -569,8 +592,12 @@ class Metrics extends Utility implements MetricsInterface { * * @param customConfigService The custom config service to be used */ - private setCustomConfigService(customConfigService?: ConfigServiceInterface): void { - this.customConfigService = customConfigService ? customConfigService : undefined; + private setCustomConfigService( + customConfigService?: ConfigServiceInterface + ): void { + this.customConfigService = customConfigService + ? customConfigService + : undefined; } /** @@ -593,14 +620,20 @@ class Metrics extends Utility implements MetricsInterface { /** * Sets the options to be used by the Metrics instance. - * + * * This method is used during the initialization of the Metrics instance. * * @param options The options to be used * @returns the Metrics instance */ private setOptions(options: MetricsOptions): Metrics { - const { customConfigService, namespace, serviceName, singleMetric, defaultDimensions } = options; + const { + customConfigService, + namespace, + serviceName, + singleMetric, + defaultDimensions, + } = options; this.setEnvVarsService(); this.setCustomConfigService(customConfigService); @@ -618,9 +651,11 @@ class Metrics extends Utility implements MetricsInterface { * @param service The service to be used */ private setService(service: string | undefined): void { - const targetService = (service || - this.getCustomConfigService()?.getServiceName() || - this.getEnvVarsService().getServiceName()) as string || this.getDefaultServiceName(); + const targetService = + ((service || + this.getCustomConfigService()?.getServiceName() || + this.getEnvVarsService().getServiceName()) as string) || + this.getDefaultServiceName(); if (targetService.length > 0) { this.setDefaultDimensions({ service: targetService }); } @@ -628,7 +663,7 @@ class Metrics extends Utility implements MetricsInterface { /** * Stores a metric in the buffer - * + * * @param name The name of the metric to store * @param unit The unit of the metric to store * @param value The value of the metric to store @@ -638,8 +673,8 @@ class Metrics extends Utility implements MetricsInterface { name: string, unit: MetricUnit, value: number, - resolution: MetricResolution, - ): void { + resolution: MetricResolution + ): void { if (Object.keys(this.storedMetrics).length >= MAX_METRICS_SIZE) { this.publishStoredMetrics(); } @@ -648,10 +683,9 @@ class Metrics extends Utility implements MetricsInterface { this.storedMetrics[name] = { unit, value, - name, - resolution + name, + resolution, }; - } else { const storedMetric = this.storedMetrics[name]; if (!Array.isArray(storedMetric.value)) { @@ -660,11 +694,6 @@ class Metrics extends Utility implements MetricsInterface { storedMetric.value.push(value); } } - } -export { - Metrics, - MetricUnits, - MetricResolution, -}; \ No newline at end of file +export { Metrics, MetricUnits, MetricResolution }; diff --git a/packages/metrics/src/MetricsInterface.ts b/packages/metrics/src/MetricsInterface.ts index 5d294722c6..ce11fd7aa1 100644 --- a/packages/metrics/src/MetricsInterface.ts +++ b/packages/metrics/src/MetricsInterface.ts @@ -5,25 +5,28 @@ import { EmfOutput, HandlerMethodDecorator, Dimensions, - MetricsOptions + 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, resolution?: MetricResolution): void - clearDimensions(): void - clearMetadata(): void - clearMetrics(): void - clearDefaultDimensions(): void - logMetrics(options?: MetricsOptions): HandlerMethodDecorator - publishStoredMetrics(): 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, + resolution?: MetricResolution + ): void; + clearDimensions(): void; + clearMetadata(): void; + clearMetrics(): void; + clearDefaultDimensions(): void; + logMetrics(options?: MetricsOptions): HandlerMethodDecorator; + publishStoredMetrics(): void; + serializeMetrics(): EmfOutput; + setDefaultDimensions(dimensions: Dimensions | undefined): void; + singleMetric(): Metrics; } -export { - MetricsInterface, -}; \ No newline at end of file +export { MetricsInterface }; diff --git a/packages/metrics/src/config/ConfigServiceInterface.ts b/packages/metrics/src/config/ConfigServiceInterface.ts index 0c009b3e79..ea1c127db6 100644 --- a/packages/metrics/src/config/ConfigServiceInterface.ts +++ b/packages/metrics/src/config/ConfigServiceInterface.ts @@ -1,11 +1,7 @@ interface ConfigServiceInterface { - - get?(name: string): string - getNamespace(): string - getServiceName(): string - + get?(name: string): string; + getNamespace(): string; + getServiceName(): string; } -export { - ConfigServiceInterface, -}; \ No newline at end of file +export { ConfigServiceInterface }; diff --git a/packages/metrics/src/config/EnvironmentVariablesService.ts b/packages/metrics/src/config/EnvironmentVariablesService.ts index 5bb4eb712b..6105759e4d 100644 --- a/packages/metrics/src/config/EnvironmentVariablesService.ts +++ b/packages/metrics/src/config/EnvironmentVariablesService.ts @@ -1,16 +1,15 @@ import { ConfigServiceInterface } from './ConfigServiceInterface'; import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons'; -class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface { - +class EnvironmentVariablesService + extends CommonEnvironmentVariablesService + implements ConfigServiceInterface +{ private namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE'; public getNamespace(): string { return this.get(this.namespaceVariable); } - } -export { - EnvironmentVariablesService, -}; \ No newline at end of file +export { EnvironmentVariablesService }; diff --git a/packages/metrics/src/config/index.ts b/packages/metrics/src/config/index.ts index 0f036d810e..11fd37677e 100644 --- a/packages/metrics/src/config/index.ts +++ b/packages/metrics/src/config/index.ts @@ -1,2 +1,2 @@ export * from './ConfigServiceInterface'; -export * from './EnvironmentVariablesService'; \ No newline at end of file +export * from './EnvironmentVariablesService'; diff --git a/packages/metrics/src/constants.ts b/packages/metrics/src/constants.ts index f8cf76c567..0dcf9ff255 100644 --- a/packages/metrics/src/constants.ts +++ b/packages/metrics/src/constants.ts @@ -1,4 +1,4 @@ export const COLD_START_METRIC = 'ColdStart'; export const DEFAULT_NAMESPACE = 'default_namespace'; export const MAX_METRICS_SIZE = 100; -export const MAX_DIMENSION_COUNT = 29; \ No newline at end of file +export const MAX_DIMENSION_COUNT = 29; diff --git a/packages/metrics/src/index.ts b/packages/metrics/src/index.ts index ffa141c1e6..7af054bdae 100644 --- a/packages/metrics/src/index.ts +++ b/packages/metrics/src/index.ts @@ -1,3 +1,3 @@ export * from './Metrics'; export * from './MetricsInterface'; -export * from './middleware'; \ No newline at end of file +export * from './middleware'; diff --git a/packages/metrics/src/middleware/index.ts b/packages/metrics/src/middleware/index.ts index fcc794fc08..cfe9900b37 100644 --- a/packages/metrics/src/middleware/index.ts +++ b/packages/metrics/src/middleware/index.ts @@ -1 +1 @@ -export * from './middy'; \ No newline at end of file +export * from './middy'; diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index 693c8a9094..b1a0313214 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -2,43 +2,47 @@ import type { Metrics } from '../Metrics'; import type { ExtraOptions } from '../types'; import type { MiddlewareLikeObj, - MiddyLikeRequest + MiddyLikeRequest, } from '@aws-lambda-powertools/commons'; /** * A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. - * + * * Using this middleware on your handler function will automatically flush metrics after the function returns or throws an error. * Additionally, you can configure the middleware to easily: * * ensure that at least one metric is emitted before you flush them * * capture a `ColdStart` a metric * * set default dimensions for all your metrics - * + * * @example * ```typescript * import { Metrics, logMetrics } from '@aws-lambda-powertools/metrics'; * import middy from '@middy/core'; - * + * * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); - * + * * const lambdaHandler = async (_event: any, _context: any) => { * ... * }; - * + * * export const handler = middy(lambdaHandler).use(logMetrics(metrics)); * ``` - * + * * @param target - The Metrics instance to use for emitting metrics * @param options - (_optional_) Options for the middleware * @returns middleware - The middy middleware object */ -const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): MiddlewareLikeObj => { +const logMetrics = ( + target: Metrics | Metrics[], + options: ExtraOptions = {} +): MiddlewareLikeObj => { const metricsInstances = target instanceof Array ? target : [target]; const logMetricsBefore = async (request: MiddyLikeRequest): Promise => { metricsInstances.forEach((metrics: Metrics) => { metrics.setFunctionName(request.context.functionName); - const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; + const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = + options; if (throwOnEmptyMetrics) { metrics.throwOnEmptyMetrics(); } @@ -49,7 +53,6 @@ const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): Mi metrics.captureColdStartMetric(); } }); - }; const logMetricsAfterOrError = async (): Promise => { @@ -57,14 +60,12 @@ const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): Mi metrics.publishStoredMetrics(); }); }; - + return { before: logMetricsBefore, after: logMetricsAfterOrError, - onError: logMetricsAfterOrError + onError: logMetricsAfterOrError, }; }; -export { - logMetrics, -}; \ No newline at end of file +export { logMetrics }; diff --git a/packages/metrics/src/types/MetricResolution.ts b/packages/metrics/src/types/MetricResolution.ts index 76065be623..297025f3c2 100644 --- a/packages/metrics/src/types/MetricResolution.ts +++ b/packages/metrics/src/types/MetricResolution.ts @@ -3,6 +3,7 @@ const MetricResolution = { High: 1, } as const; -type MetricResolution = typeof MetricResolution[keyof typeof MetricResolution]; +type MetricResolution = + (typeof MetricResolution)[keyof typeof MetricResolution]; -export { MetricResolution }; \ No newline at end of file +export { MetricResolution }; diff --git a/packages/metrics/src/types/MetricUnit.ts b/packages/metrics/src/types/MetricUnit.ts index a04b013c58..ccbffac645 100644 --- a/packages/metrics/src/types/MetricUnit.ts +++ b/packages/metrics/src/types/MetricUnit.ts @@ -55,34 +55,31 @@ type MetricUnitTerabitsPerSecond = MetricUnits.TerabitsPerSecond; type MetricUnitCountPerSecond = MetricUnits.CountPerSecond; type MetricUnit = - MetricUnitSeconds - | MetricUnitMicroseconds - | MetricUnitMilliseconds - | MetricUnitBytes - | MetricUnitKilobytes - | MetricUnitMegabytes - | MetricUnitGigabytes - | MetricUnitTerabytes - | MetricUnitBits - | MetricUnitKilobits - | MetricUnitMegabits - | MetricUnitGigabits - | MetricUnitTerabits - | MetricUnitPercent - | MetricUnitCount - | MetricUnitBitsPerSecond - | MetricUnitBytesPerSecond - | MetricUnitKilobytesPerSecond - | MetricUnitMegabytesPerSecond - | MetricUnitGigabytesPerSecond - | MetricUnitTerabytesPerSecond - | MetricUnitKilobitsPerSecond - | MetricUnitMegabitsPerSecond - | MetricUnitGigabitsPerSecond - | MetricUnitTerabitsPerSecond - | MetricUnitCountPerSecond; + | MetricUnitSeconds + | MetricUnitMicroseconds + | MetricUnitMilliseconds + | MetricUnitBytes + | MetricUnitKilobytes + | MetricUnitMegabytes + | MetricUnitGigabytes + | MetricUnitTerabytes + | MetricUnitBits + | MetricUnitKilobits + | MetricUnitMegabits + | MetricUnitGigabits + | MetricUnitTerabits + | MetricUnitPercent + | MetricUnitCount + | MetricUnitBitsPerSecond + | MetricUnitBytesPerSecond + | MetricUnitKilobytesPerSecond + | MetricUnitMegabytesPerSecond + | MetricUnitGigabytesPerSecond + | MetricUnitTerabytesPerSecond + | MetricUnitKilobitsPerSecond + | MetricUnitMegabitsPerSecond + | MetricUnitGigabitsPerSecond + | MetricUnitTerabitsPerSecond + | MetricUnitCountPerSecond; -export { - MetricUnit, - MetricUnits, -}; \ No newline at end of file +export { MetricUnit, MetricUnits }; diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index ca1da5935c..afdda1d9ef 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -1,5 +1,9 @@ import { Handler } from 'aws-lambda'; -import { LambdaInterface, AsyncHandler, SyncHandler } from '@aws-lambda-powertools/commons'; +import { + LambdaInterface, + AsyncHandler, + SyncHandler, +} from '@aws-lambda-powertools/commons'; import { ConfigServiceInterface } from '../config'; import { MetricUnit } from './MetricUnit'; import { MetricResolution } from './MetricResolution'; @@ -7,29 +11,31 @@ import { MetricResolution } from './MetricResolution'; type Dimensions = Record; type MetricsOptions = { - customConfigService?: ConfigServiceInterface - namespace?: string - serviceName?: string - singleMetric?: boolean - defaultDimensions?: Dimensions + customConfigService?: ConfigServiceInterface; + namespace?: string; + serviceName?: string; + singleMetric?: boolean; + defaultDimensions?: Dimensions; }; type EmfOutput = Readonly<{ - [key: string]: string | number | object + [key: string]: string | number | object; _aws: { - Timestamp: number + Timestamp: number; CloudWatchMetrics: { - Namespace: string - Dimensions: [string[]] - Metrics: MetricDefinition[] - }[] - } + Namespace: string; + Dimensions: [string[]]; + Metrics: MetricDefinition[]; + }[]; + }; }>; type HandlerMethodDecorator = ( target: LambdaInterface, propertyKey: string | symbol, - descriptor: TypedPropertyDescriptor> | TypedPropertyDescriptor> + descriptor: + | TypedPropertyDescriptor> + | TypedPropertyDescriptor> ) => void; /** @@ -52,24 +58,33 @@ type HandlerMethodDecorator = ( * ``` */ type ExtraOptions = { - throwOnEmptyMetrics?: boolean - defaultDimensions?: Dimensions - captureColdStartMetric?: boolean + throwOnEmptyMetrics?: boolean; + defaultDimensions?: Dimensions; + captureColdStartMetric?: boolean; }; type StoredMetric = { - name: string - unit: MetricUnit - value: number | number[] - resolution: MetricResolution + name: string; + unit: MetricUnit; + value: number | number[]; + resolution: MetricResolution; }; type StoredMetrics = Record; type MetricDefinition = { - Name: string - Unit: MetricUnit - StorageResolution?: MetricResolution -}; + Name: string; + Unit: MetricUnit; + StorageResolution?: MetricResolution; +}; -export { MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, ExtraOptions, StoredMetrics, StoredMetric, MetricDefinition }; +export { + MetricsOptions, + Dimensions, + EmfOutput, + HandlerMethodDecorator, + ExtraOptions, + StoredMetrics, + StoredMetric, + MetricDefinition, +}; diff --git a/packages/metrics/src/types/index.ts b/packages/metrics/src/types/index.ts index 14416fbd33..344dac9821 100644 --- a/packages/metrics/src/types/index.ts +++ b/packages/metrics/src/types/index.ts @@ -1,3 +1,3 @@ export * from './Metrics'; export * from './MetricUnit'; -export * from './MetricResolution'; \ No newline at end of file +export * from './MetricResolution'; diff --git a/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts b/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts index 066d8d4140..5a8f104843 100644 --- a/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts @@ -3,29 +3,41 @@ import { Context } from 'aws-lambda'; import { LambdaInterface } from '@aws-lambda-powertools/commons'; const namespace = process.env.EXPECTED_NAMESPACE ?? 'CdkExample'; -const serviceName = process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandardHandler'; +const serviceName = + process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandardHandler'; const metricName = process.env.EXPECTED_METRIC_NAME ?? 'MyMetric'; -const metricUnit = (process.env.EXPECTED_METRIC_UNIT as MetricUnits) ?? MetricUnits.Count; +const metricUnit = + (process.env.EXPECTED_METRIC_UNIT as MetricUnits) ?? MetricUnits.Count; const metricValue = process.env.EXPECTED_METRIC_VALUE ?? '1'; -const defaultDimensions = process.env.EXPECTED_DEFAULT_DIMENSIONS ?? '{"MyDimension":"MyValue"}'; -const extraDimension = process.env.EXPECTED_EXTRA_DIMENSION ?? '{"MyExtraDimension":"MyExtraValue"}'; -const singleMetricDimension = process.env.EXPECTED_SINGLE_METRIC_DIMENSION ?? '{"MySingleMetricDim":"MySingleValue"}'; -const singleMetricName = process.env.EXPECTED_SINGLE_METRIC_NAME ?? 'MySingleMetric'; -const singleMetricUnit = (process.env.EXPECTED_SINGLE_METRIC_UNIT as MetricUnits) ?? MetricUnits.Percent; +const defaultDimensions = + process.env.EXPECTED_DEFAULT_DIMENSIONS ?? '{"MyDimension":"MyValue"}'; +const extraDimension = + process.env.EXPECTED_EXTRA_DIMENSION ?? '{"MyExtraDimension":"MyExtraValue"}'; +const singleMetricDimension = + process.env.EXPECTED_SINGLE_METRIC_DIMENSION ?? + '{"MySingleMetricDim":"MySingleValue"}'; +const singleMetricName = + process.env.EXPECTED_SINGLE_METRIC_NAME ?? 'MySingleMetric'; +const singleMetricUnit = + (process.env.EXPECTED_SINGLE_METRIC_UNIT as MetricUnits) ?? + MetricUnits.Percent; const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? '2'; const metrics = new Metrics({ namespace: namespace, serviceName: serviceName }); class Lambda implements LambdaInterface { - - @metrics.logMetrics({ captureColdStartMetric: true, defaultDimensions: JSON.parse(defaultDimensions), throwOnEmptyMetrics: true }) + @metrics.logMetrics({ + captureColdStartMetric: true, + defaultDimensions: JSON.parse(defaultDimensions), + throwOnEmptyMetrics: true, + }) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore public async handler(_event: unknown, _context: Context): Promise { metrics.addMetric(metricName, metricUnit, parseInt(metricValue)); metrics.addDimension( Object.entries(JSON.parse(extraDimension))[0][0], - Object.entries(JSON.parse(extraDimension))[0][1] as string, + Object.entries(JSON.parse(extraDimension))[0][1] as string ); this.dummyMethod(); @@ -35,12 +47,16 @@ class Lambda implements LambdaInterface { const metricWithItsOwnDimensions = metrics.singleMetric(); metricWithItsOwnDimensions.addDimension( Object.entries(JSON.parse(singleMetricDimension))[0][0], - Object.entries(JSON.parse(singleMetricDimension))[0][1] as string, + Object.entries(JSON.parse(singleMetricDimension))[0][1] as string ); - metricWithItsOwnDimensions.addMetric(singleMetricName, singleMetricUnit, parseInt(singleMetricValue)); + metricWithItsOwnDimensions.addMetric( + singleMetricName, + singleMetricUnit, + parseInt(singleMetricValue) + ); } } const handlerClass = new Lambda(); -export const handler = handlerClass.handler.bind(handlerClass); \ No newline at end of file +export const handler = handlerClass.handler.bind(handlerClass); diff --git a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts index 6675ba08d8..297e81ecc4 100644 --- a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts @@ -8,23 +8,26 @@ import { Tracing } from 'aws-cdk-lib/aws-lambda'; import { App, Stack } from 'aws-cdk-lib'; import { CloudWatchClient, - GetMetricStatisticsCommand + GetMetricStatisticsCommand, } from '@aws-sdk/client-cloudwatch'; import { v4 } from 'uuid'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, } from '../../../commons/tests/utils/e2eUtils'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { + deployStack, + destroyStack, +} from '../../../commons/tests/utils/cdk-cli'; import { MetricUnits } from '../../src'; -import { - ONE_MINUTE, - RESOURCE_NAME_PREFIX, - SETUP_TIMEOUT, - TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT +import { + ONE_MINUTE, + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT, } from './constants'; import { getMetrics } from '../helpers/metricsUtils'; @@ -35,8 +38,18 @@ if (!isValidRuntimeKey(runtime)) { } const uuid = v4(); -const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); +const stackName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'decorator' +); +const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'decorator' +); const lambdaFunctionCodeFile = 'basicFeatures.decorator.test.functionCode.ts'; const cloudwatchClient = new CloudWatchClient({}); @@ -81,7 +94,9 @@ describe(`metrics E2E tests (decorator) for runtime: ${runtime}`, () => { EXPECTED_METRIC_VALUE: expectedMetricValue, EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), - EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), + EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify( + expectedSingleMetricDimension + ), EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, @@ -93,79 +108,128 @@ describe(`metrics E2E tests (decorator) for runtime: ${runtime}`, () => { // and invoked await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - }, SETUP_TIMEOUT); describe('ColdStart metrics', () => { - it('should capture ColdStart Metric', async () => { - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: 'function_name', Value: functionName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - ]; - // Check coldstart metric dimensions - const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - - expect(coldStartMetrics.Metrics?.length).toBe(1); - const coldStartMetric = coldStartMetrics.Metrics?.[0]; - expect(coldStartMetric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); - const coldStartMetricStat = await cloudwatchClient.send( - new GetMetricStatisticsCommand({ - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: 'ColdStart', - Statistics: ['Sum'], - }) - ); - - // Despite lambda has been called twice, coldstart metric sum should only be 1 - const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; - expect(singleDataPoint?.Sum).toBe(1); - }, TEST_CASE_TIMEOUT); + it( + 'should capture ColdStart Metric', + async () => { + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: 'function_name', Value: functionName }, + { + Name: Object.keys(expectedDefaultDimensions)[0], + Value: expectedDefaultDimensions.MyDimension, + }, + ]; + // Check coldstart metric dimensions + const coldStartMetrics = await getMetrics( + cloudwatchClient, + expectedNamespace, + 'ColdStart', + 1 + ); + + expect(coldStartMetrics.Metrics?.length).toBe(1); + const coldStartMetric = coldStartMetrics.Metrics?.[0]; + expect(coldStartMetric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + console.log( + `Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor( + adjustedStartTime.getTime() / 1000 + )} --end-time ${Math.floor( + endTime.getTime() / 1000 + )} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify( + expectedDimensions + )}'` + ); + const coldStartMetricStat = await cloudwatchClient.send( + new GetMetricStatisticsCommand({ + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: 'ColdStart', + Statistics: ['Sum'], + }) + ); + + // Despite lambda has been called twice, coldstart metric sum should only be 1 + const singleDataPoint = coldStartMetricStat.Datapoints + ? coldStartMetricStat.Datapoints[0] + : {}; + expect(singleDataPoint?.Sum).toBe(1); + }, + TEST_CASE_TIMEOUT + ); }); describe('Default and extra dimensions', () => { - - it('should produce a Metric with the default and extra one dimensions', async () => { - // Check metric dimensions - const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); - - expect(metrics.Metrics?.length).toBe(1); - const metric = metrics.Metrics?.[0]; - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, - ]; - expect(metric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); - const metricStat = await cloudwatchClient.send( - new GetMetricStatisticsCommand({ - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: expectedMetricName, - Statistics: ['Sum'], - }) - ); - - // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount - const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; - expect(singleDataPoint?.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); - }, TEST_CASE_TIMEOUT); + it( + 'should produce a Metric with the default and extra one dimensions', + async () => { + // Check metric dimensions + const metrics = await getMetrics( + cloudwatchClient, + expectedNamespace, + expectedMetricName, + 1 + ); + + expect(metrics.Metrics?.length).toBe(1); + const metric = metrics.Metrics?.[0]; + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { + Name: Object.keys(expectedDefaultDimensions)[0], + Value: expectedDefaultDimensions.MyDimension, + }, + { + Name: Object.keys(expectedExtraDimension)[0], + Value: expectedExtraDimension.MyExtraDimension, + }, + ]; + expect(metric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date( + startTime.getTime() - 3 * ONE_MINUTE + ); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + console.log( + `Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor( + adjustedStartTime.getTime() / 1000 + )} --end-time ${Math.floor( + endTime.getTime() / 1000 + )} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify( + expectedDimensions + )}'` + ); + const metricStat = await cloudwatchClient.send( + new GetMetricStatisticsCommand({ + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: expectedMetricName, + Statistics: ['Sum'], + }) + ); + + // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount + const singleDataPoint = metricStat.Datapoints + ? metricStat.Datapoints[0] + : {}; + expect(singleDataPoint?.Sum).toBeGreaterThanOrEqual( + parseInt(expectedMetricValue) * invocationCount + ); + }, + TEST_CASE_TIMEOUT + ); }); afterAll(async () => { if (!process.env.DISABLE_TEARDOWN) { diff --git a/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts index 997b5775d9..eae2c1a842 100644 --- a/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts @@ -2,35 +2,51 @@ 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'; +const serviceName = + process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandardHandler'; const metricName = process.env.EXPECTED_METRIC_NAME ?? 'MyMetric'; -const metricUnit = (process.env.EXPECTED_METRIC_UNIT as MetricUnits) ?? MetricUnits.Count; +const metricUnit = + (process.env.EXPECTED_METRIC_UNIT as MetricUnits) ?? MetricUnits.Count; const metricValue = process.env.EXPECTED_METRIC_VALUE ?? '1'; -const defaultDimensions = process.env.EXPECTED_DEFAULT_DIMENSIONS ?? '{"MyDimension":"MyValue"}'; -const extraDimension = process.env.EXPECTED_EXTRA_DIMENSION ?? '{"MyExtraDimension":"MyExtraValue"}'; -const singleMetricDimension = process.env.EXPECTED_SINGLE_METRIC_DIMENSION ?? '{"MySingleMetricDim":"MySingleValue"}'; -const singleMetricName = process.env.EXPECTED_SINGLE_METRIC_NAME ?? 'MySingleMetric'; -const singleMetricUnit = (process.env.EXPECTED_SINGLE_METRIC_UNIT as MetricUnits) ?? MetricUnits.Percent; +const defaultDimensions = + process.env.EXPECTED_DEFAULT_DIMENSIONS ?? '{"MyDimension":"MyValue"}'; +const extraDimension = + process.env.EXPECTED_EXTRA_DIMENSION ?? '{"MyExtraDimension":"MyExtraValue"}'; +const singleMetricDimension = + process.env.EXPECTED_SINGLE_METRIC_DIMENSION ?? + '{"MySingleMetricDim":"MySingleValue"}'; +const singleMetricName = + process.env.EXPECTED_SINGLE_METRIC_NAME ?? 'MySingleMetric'; +const singleMetricUnit = + (process.env.EXPECTED_SINGLE_METRIC_UNIT as MetricUnits) ?? + MetricUnits.Percent; const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? '2'; const metrics = new Metrics({ namespace: namespace, serviceName: serviceName }); -export const handler = async (_event: unknown, _context: Context): Promise => { +export const handler = async ( + _event: unknown, + _context: Context +): Promise => { metrics.captureColdStartMetric(); metrics.throwOnEmptyMetrics(); metrics.setDefaultDimensions(JSON.parse(defaultDimensions)); metrics.addMetric(metricName, metricUnit, parseInt(metricValue)); metrics.addDimension( Object.entries(JSON.parse(extraDimension))[0][0], - Object.entries(JSON.parse(extraDimension))[0][1] as string, + Object.entries(JSON.parse(extraDimension))[0][1] as string ); const metricWithItsOwnDimensions = metrics.singleMetric(); metricWithItsOwnDimensions.addDimension( Object.entries(JSON.parse(singleMetricDimension))[0][0], - Object.entries(JSON.parse(singleMetricDimension))[0][1] as string, + Object.entries(JSON.parse(singleMetricDimension))[0][1] as string + ); + metricWithItsOwnDimensions.addMetric( + singleMetricName, + singleMetricUnit, + parseInt(singleMetricValue) ); - metricWithItsOwnDimensions.addMetric(singleMetricName, singleMetricUnit, parseInt(singleMetricValue)); metrics.publishStoredMetrics(); }; diff --git a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts index f9fedf1bea..585c2e9abf 100644 --- a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts @@ -9,23 +9,26 @@ import { Tracing } from 'aws-cdk-lib/aws-lambda'; import { App, Stack } from 'aws-cdk-lib'; import { CloudWatchClient, - GetMetricStatisticsCommand + GetMetricStatisticsCommand, } from '@aws-sdk/client-cloudwatch'; import { v4 } from 'uuid'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, } from '../../../commons/tests/utils/e2eUtils'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { + deployStack, + destroyStack, +} from '../../../commons/tests/utils/cdk-cli'; import { MetricUnits } from '../../src'; -import { - ONE_MINUTE, - RESOURCE_NAME_PREFIX, - SETUP_TIMEOUT, - TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT +import { + ONE_MINUTE, + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT, } from './constants'; import { getMetrics } from '../helpers/metricsUtils'; @@ -36,8 +39,18 @@ if (!isValidRuntimeKey(runtime)) { } const uuid = v4(); -const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); +const stackName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'manual' +); +const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'manual' +); const lambdaFunctionCodeFile = 'basicFeatures.manual.test.functionCode.ts'; const cloudwatchClient = new CloudWatchClient({}); @@ -82,7 +95,9 @@ describe(`metrics E2E tests (manual) for runtime: ${runtime}`, () => { EXPECTED_METRIC_VALUE: expectedMetricValue, EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), - EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), + EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify( + expectedSingleMetricDimension + ), EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, @@ -93,73 +108,122 @@ describe(`metrics E2E tests (manual) for runtime: ${runtime}`, () => { // and invoked await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - }, SETUP_TIMEOUT); describe('ColdStart metrics', () => { - it('should capture ColdStart Metric', async () => { - // Check coldstart metric dimensions - const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - expect(coldStartMetrics.Metrics?.length).toBe(1); - const coldStartMetric = coldStartMetrics.Metrics?.[0]; - expect(coldStartMetric?.Dimensions).toStrictEqual([{ Name: 'service', Value: expectedServiceName }]); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); - const endTime = new Date(new Date().getTime() + 60 * 1000); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify([{ Name: 'service', Value: expectedServiceName }])}'`); - const coldStartMetricStat = await cloudwatchClient.send( - new GetMetricStatisticsCommand({ - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: [{ Name: 'service', Value: expectedServiceName }], - EndTime: endTime, - Period: 60, - MetricName: 'ColdStart', - Statistics: ['Sum'], - }) - ); - - // Despite lambda has been called twice, coldstart metric sum should only be 1 - const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; - expect(singleDataPoint?.Sum).toBe(1); - }, TEST_CASE_TIMEOUT); + it( + 'should capture ColdStart Metric', + async () => { + // Check coldstart metric dimensions + const coldStartMetrics = await getMetrics( + cloudwatchClient, + expectedNamespace, + 'ColdStart', + 1 + ); + expect(coldStartMetrics.Metrics?.length).toBe(1); + const coldStartMetric = coldStartMetrics.Metrics?.[0]; + expect(coldStartMetric?.Dimensions).toStrictEqual([ + { Name: 'service', Value: expectedServiceName }, + ]); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); + const endTime = new Date(new Date().getTime() + 60 * 1000); + console.log( + `Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor( + adjustedStartTime.getTime() / 1000 + )} --end-time ${Math.floor( + endTime.getTime() / 1000 + )} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify([ + { Name: 'service', Value: expectedServiceName }, + ])}'` + ); + const coldStartMetricStat = await cloudwatchClient.send( + new GetMetricStatisticsCommand({ + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: [{ Name: 'service', Value: expectedServiceName }], + EndTime: endTime, + Period: 60, + MetricName: 'ColdStart', + Statistics: ['Sum'], + }) + ); + + // Despite lambda has been called twice, coldstart metric sum should only be 1 + const singleDataPoint = coldStartMetricStat.Datapoints + ? coldStartMetricStat.Datapoints[0] + : {}; + expect(singleDataPoint?.Sum).toBe(1); + }, + TEST_CASE_TIMEOUT + ); }); describe('Default and extra dimensions', () => { - it('should produce a Metric with the default and extra one dimensions', async () => { - // Check metric dimensions - const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); - - expect(metrics.Metrics?.length).toBe(1); - const metric = metrics.Metrics?.[0]; - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, - ]; - expect(metric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); - const metricStat = await cloudwatchClient.send( - new GetMetricStatisticsCommand({ - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: expectedMetricName, - Statistics: ['Sum'], - }) - ); - - // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount - const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; - expect(singleDataPoint.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); - }, TEST_CASE_TIMEOUT); + it( + 'should produce a Metric with the default and extra one dimensions', + async () => { + // Check metric dimensions + const metrics = await getMetrics( + cloudwatchClient, + expectedNamespace, + expectedMetricName, + 1 + ); + + expect(metrics.Metrics?.length).toBe(1); + const metric = metrics.Metrics?.[0]; + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { + Name: Object.keys(expectedDefaultDimensions)[0], + Value: expectedDefaultDimensions.MyDimension, + }, + { + Name: Object.keys(expectedExtraDimension)[0], + Value: expectedExtraDimension.MyExtraDimension, + }, + ]; + expect(metric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date( + startTime.getTime() - 3 * ONE_MINUTE + ); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + console.log( + `Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor( + adjustedStartTime.getTime() / 1000 + )} --end-time ${Math.floor( + endTime.getTime() / 1000 + )} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify( + expectedDimensions + )}'` + ); + const metricStat = await cloudwatchClient.send( + new GetMetricStatisticsCommand({ + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: expectedMetricName, + Statistics: ['Sum'], + }) + ); + + // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount + const singleDataPoint = metricStat.Datapoints + ? metricStat.Datapoints[0] + : {}; + expect(singleDataPoint.Sum).toBeGreaterThanOrEqual( + parseInt(expectedMetricValue) * invocationCount + ); + }, + TEST_CASE_TIMEOUT + ); }); afterAll(async () => { diff --git a/packages/metrics/tests/e2e/constants.ts b/packages/metrics/tests/e2e/constants.ts index a950bb7f0d..9eddc39ebe 100644 --- a/packages/metrics/tests/e2e/constants.ts +++ b/packages/metrics/tests/e2e/constants.ts @@ -2,4 +2,4 @@ export const RESOURCE_NAME_PREFIX = 'Metrics-E2E'; export const ONE_MINUTE = 60 * 1000; export const TEST_CASE_TIMEOUT = 3 * ONE_MINUTE; export const SETUP_TIMEOUT = 5 * ONE_MINUTE; -export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; \ No newline at end of file +export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; diff --git a/packages/metrics/tests/helpers/metricsUtils.ts b/packages/metrics/tests/helpers/metricsUtils.ts index bf29a176db..5254b474cb 100644 --- a/packages/metrics/tests/helpers/metricsUtils.ts +++ b/packages/metrics/tests/helpers/metricsUtils.ts @@ -42,27 +42,28 @@ const getMetrics = async ( }, retryOptions); }; -const setupDecoratorLambdaHandler = (metrics: Metrics, options: ExtraOptions = {}): Handler => { - +const setupDecoratorLambdaHandler = ( + metrics: Metrics, + options: ExtraOptions = {} +): Handler => { class LambdaFunction implements LambdaInterface { @metrics.logMetrics(options) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - public async handler(_event: TEvent, _context: Context): Promise { + public async handler( + _event: TEvent, + _context: Context + ): Promise { metrics.addMetric('decorator-lambda-test-metric', MetricUnits.Count, 1); - + return 'Lambda invoked!'; } } - + const handlerClass = new LambdaFunction(); const handler = handlerClass.handler.bind(handlerClass); - - return handler; -}; -export { - getMetrics, - setupDecoratorLambdaHandler + return handler; }; +export { getMetrics, setupDecoratorLambdaHandler }; diff --git a/packages/metrics/tests/helpers/populateEnvironmentVariables.ts b/packages/metrics/tests/helpers/populateEnvironmentVariables.ts index 37e985813c..ce4d3e4bc4 100644 --- a/packages/metrics/tests/helpers/populateEnvironmentVariables.ts +++ b/packages/metrics/tests/helpers/populateEnvironmentVariables.ts @@ -1,10 +1,14 @@ // Reserved variables -process.env._X_AMZN_TRACE_ID = 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; +process.env._X_AMZN_TRACE_ID = + 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128'; -if (process.env.AWS_REGION === undefined && process.env.CDK_DEFAULT_REGION === undefined) { +if ( + process.env.AWS_REGION === undefined && + process.env.CDK_DEFAULT_REGION === undefined +) { process.env.AWS_REGION = 'eu-west-1'; } // Powertools variables -process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; \ No newline at end of file +process.env.POWERTOOLS_METRICS_NAMESPACE = 'hello-world'; diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 724d88ed99..b0f41073bf 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -6,14 +6,22 @@ import { LambdaInterface, ContextExamples as dummyContext, - Events as dummyEvent + Events as dummyEvent, } from '@aws-lambda-powertools/commons'; import { MetricResolution, MetricUnits, Metrics } from '../../src/'; import { Context, Handler } from 'aws-lambda'; import { Dimensions, EmfOutput, MetricsOptions } from '../../src/types'; -import { COLD_START_METRIC, DEFAULT_NAMESPACE, MAX_DIMENSION_COUNT, MAX_METRICS_SIZE } from '../../src/constants'; +import { + COLD_START_METRIC, + DEFAULT_NAMESPACE, + MAX_DIMENSION_COUNT, + MAX_METRICS_SIZE, +} from '../../src/constants'; import { setupDecoratorLambdaHandler } from '../helpers/metricsUtils'; -import { ConfigServiceInterface, EnvironmentVariablesService } from '../../src/config'; +import { + ConfigServiceInterface, + EnvironmentVariablesService, +} from '../../src/config'; const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); @@ -21,11 +29,10 @@ jest.spyOn(console, 'log').mockImplementation(); jest.spyOn(console, 'warn').mockImplementation(); interface LooseObject { - [key: string]: string + [key: string]: string; } describe('Class: Metrics', () => { - const ENVIRONMENT_VARIABLES = process.env; const TEST_NAMESPACE = 'test'; const context = dummyContext.helloworldContext; @@ -39,9 +46,7 @@ describe('Class: Metrics', () => { }); describe('Method: constructor', () => { - test('when no constructor parameters are set, creates instance with the options set in the environment variables', () => { - // Prepare const metricsOptions = undefined; @@ -49,26 +54,26 @@ describe('Class: Metrics', () => { const metrics: Metrics = new Metrics(metricsOptions); // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: undefined, - defaultDimensions: { - service: 'service_undefined', - }, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: false, - metadata: {}, - namespace: 'hello-world', - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - - }); - - test('when no constructor parameters and no environment variables are set, creates instance with the default properties', () => { + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: undefined, + defaultDimensions: { + service: 'service_undefined', + }, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: false, + metadata: {}, + namespace: 'hello-world', + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); + }); + test('when no constructor parameters and no environment variables are set, creates instance with the default properties', () => { // Prepare const metricsOptions = undefined; process.env = {}; @@ -77,26 +82,26 @@ describe('Class: Metrics', () => { const metrics: Metrics = new Metrics(metricsOptions); // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: undefined, - defaultDimensions: { - service: 'service_undefined', - }, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: false, - metadata: {}, - namespace: '', - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - - }); - - test('when constructor parameters are set, creates instance with the options set in the constructor parameters', () => { + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: undefined, + defaultDimensions: { + service: 'service_undefined', + }, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: false, + metadata: {}, + namespace: '', + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); + }); + test('when constructor parameters are set, creates instance with the options set in the constructor parameters', () => { // Prepare const metricsOptions: MetricsOptions = { customConfigService: new EnvironmentVariablesService(), @@ -112,271 +117,283 @@ describe('Class: Metrics', () => { const metrics: Metrics = new Metrics(metricsOptions); // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: expect.any(EnvironmentVariablesService), - defaultDimensions: metricsOptions.defaultDimensions, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: true, - metadata: {}, - namespace: metricsOptions.namespace, - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - }); - + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: expect.any(EnvironmentVariablesService), + defaultDimensions: metricsOptions.defaultDimensions, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: true, + metadata: {}, + namespace: metricsOptions.namespace, + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); + }); + test('when custom namespace is passed, creates instance with the correct properties', () => { - // Prepare const metricsOptions: MetricsOptions = { namespace: TEST_NAMESPACE, }; - + // Act const metrics: Metrics = new Metrics(metricsOptions); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: undefined, - defaultDimensions: { - service: 'service_undefined', - }, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: false, - metadata: {}, - namespace: metricsOptions.namespace, - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: undefined, + defaultDimensions: { + service: 'service_undefined', + }, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: false, + metadata: {}, + namespace: metricsOptions.namespace, + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); }); test('when custom defaultDimensions is passed, creates instance with the correct properties', () => { - // Prepare const metricsOptions: MetricsOptions = { defaultDimensions: { service: 'order', }, }; - + // Act const metrics: Metrics = new Metrics(metricsOptions); - - // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: undefined, - defaultDimensions: metricsOptions.defaultDimensions, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: false, - metadata: {}, - namespace: 'hello-world', - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - + + // Assess + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: undefined, + defaultDimensions: metricsOptions.defaultDimensions, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: false, + metadata: {}, + namespace: 'hello-world', + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); }); test('when singleMetric is passed, creates instance with the correct properties', () => { - // Prepare const metricsOptions: MetricsOptions = { singleMetric: true, }; - + // Act const metrics: Metrics = new Metrics(metricsOptions); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: undefined, - defaultDimensions: { - service: 'service_undefined', - }, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: true, - metadata: {}, - namespace: 'hello-world', - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: undefined, + defaultDimensions: { + service: 'service_undefined', + }, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: true, + metadata: {}, + namespace: 'hello-world', + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); }); test('when custom customConfigService is passed, creates instance with the correct properties', () => { - // Prepare const configService: ConfigServiceInterface = { get(name: string): string { return `a-string-from-${name}`; }, - getNamespace(): string{ + getNamespace(): string { return TEST_NAMESPACE; }, - getServiceName(): string{ + getServiceName(): string { return 'test-service'; - } + }, }; const metricsOptions: MetricsOptions = { customConfigService: configService, }; - + // Act const metrics: Metrics = new Metrics(metricsOptions); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: configService, - defaultDimensions: { - service: 'test-service' - }, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: false, - metadata: {}, - namespace: TEST_NAMESPACE, - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: configService, + defaultDimensions: { + service: 'test-service', + }, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: false, + metadata: {}, + namespace: TEST_NAMESPACE, + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); }); test('when custom serviceName is passed, creates instance with the correct properties', () => { - // Prepare const metricsOptions: MetricsOptions = { serviceName: 'test-service', }; - + // Act const metrics: Metrics = new Metrics(metricsOptions); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - coldStart: true, - customConfigService: undefined, - defaultDimensions: { - service: 'test-service' - }, - defaultServiceName: 'service_undefined', - dimensions: {}, - envVarsService: expect.any(EnvironmentVariablesService), - isSingleMetric: false, - metadata: {}, - namespace: 'hello-world', - shouldThrowOnEmptyMetrics: false, - storedMetrics: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + coldStart: true, + customConfigService: undefined, + defaultDimensions: { + service: 'test-service', + }, + defaultServiceName: 'service_undefined', + dimensions: {}, + envVarsService: expect.any(EnvironmentVariablesService), + isSingleMetric: false, + metadata: {}, + namespace: 'hello-world', + shouldThrowOnEmptyMetrics: false, + storedMetrics: {}, + }) + ); }); - }); describe('Method: addDimension', () => { - test('when called, it should store dimensions', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const dimensionName = 'test-dimension'; - const dimensionValue= 'test-value'; - + const dimensionValue = 'test-value'; + // Act metrics.addDimension(dimensionName, dimensionValue); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - dimensions: { - [dimensionName]: dimensionValue - }, - })); - + expect(metrics).toEqual( + expect.objectContaining({ + dimensions: { + [dimensionName]: dimensionValue, + }, + }) + ); }); test('it should update existing dimension value if same dimension is added again', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const dimensionName = 'test-dimension'; - + // Act metrics.addDimension(dimensionName, 'test-value-1'); metrics.addDimension(dimensionName, 'test-value-2'); // Assess - expect(metrics).toEqual(expect.objectContaining({ - dimensions: { - [dimensionName]: 'test-value-2' - } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + dimensions: { + [dimensionName]: 'test-value-2', + }, + }) + ); }); test('it should throw error if the number of dimensions exceeds the maximum allowed', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const dimensionName = 'test-dimension'; const dimensionValue = 'test-value'; - + // Act & Assess // Starts from 1 because the service dimension is already added by default expect(() => { for (let i = 1; i < MAX_DIMENSION_COUNT; i++) { - metrics.addDimension(`${dimensionName}-${i}`, `${dimensionValue}-${i}`); + metrics.addDimension( + `${dimensionName}-${i}`, + `${dimensionValue}-${i}` + ); } }).not.toThrowError(); expect(Object.keys(metrics['defaultDimensions']).length).toBe(1); - expect(Object.keys(metrics['dimensions']).length).toBe(MAX_DIMENSION_COUNT - 1); - expect(() => metrics.addDimension('another-dimension', 'another-dimension-value')) - .toThrowError(`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`); - + expect(Object.keys(metrics['dimensions']).length).toBe( + MAX_DIMENSION_COUNT - 1 + ); + expect(() => + metrics.addDimension('another-dimension', 'another-dimension-value') + ).toThrowError( + `The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}` + ); }); test('it should take consideration of defaultDimensions while throwing error if number of dimensions exceeds the maximum allowed', () => { - // Prepare const defaultDimensions: LooseObject = { - 'environment': 'dev', - 'foo': 'bar' + environment: 'dev', + foo: 'bar', }; - const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, defaultDimensions }); + const metrics: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + defaultDimensions, + }); const dimensionName = 'test-dimension'; const dimensionValue = 'test-value'; - + // Act & Assess // Starts from 3 because three default dimensions are already set (service, environment, foo) expect(() => { for (let i = 3; i < MAX_DIMENSION_COUNT; i++) { - metrics.addDimension(`${dimensionName}-${i}`, `${dimensionValue}-${i}`); + metrics.addDimension( + `${dimensionName}-${i}`, + `${dimensionValue}-${i}` + ); } }).not.toThrowError(); expect(Object.keys(metrics['defaultDimensions']).length).toBe(3); - expect(Object.keys(metrics['dimensions']).length).toBe(MAX_DIMENSION_COUNT - 3); - expect(() => metrics.addDimension('another-dimension', 'another-dimension-value')) - .toThrowError(`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`); - + expect(Object.keys(metrics['dimensions']).length).toBe( + MAX_DIMENSION_COUNT - 3 + ); + expect(() => + metrics.addDimension('another-dimension', 'another-dimension-value') + ).toThrowError( + `The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}` + ); }); - }); describe('Method: addDimensions', () => { - test('it should add multiple dimensions', () => { - // Prepare const dimensionsToBeAdded: LooseObject = { 'test-dimension-1': 'test-value-1', @@ -388,14 +405,14 @@ describe('Class: Metrics', () => { metrics.addDimensions(dimensionsToBeAdded); // Assess - expect(metrics).toEqual(expect.objectContaining({ - dimensions: dimensionsToBeAdded - })); - + expect(metrics).toEqual( + expect.objectContaining({ + dimensions: dimensionsToBeAdded, + }) + ); }); test('it should update existing dimension value if same dimension is added again', () => { - // Prepare const dimensionsToBeAdded: LooseObject = { 'test-dimension-1': 'test-value-1', @@ -408,17 +425,17 @@ describe('Class: Metrics', () => { metrics.addDimensions({ 'test-dimension-1': 'test-value-3' }); // Assess - expect(metrics).toEqual(expect.objectContaining({ - dimensions: { - 'test-dimension-1': 'test-value-3', - 'test-dimension-2': 'test-value-2', - } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + dimensions: { + 'test-dimension-1': 'test-value-3', + 'test-dimension-2': 'test-value-2', + }, + }) + ); }); test('it should successfully add up to maximum allowed dimensions without throwing error', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const dimensionName = 'test-dimension'; @@ -427,15 +444,17 @@ describe('Class: Metrics', () => { for (let i = 0; i < MAX_DIMENSION_COUNT; i++) { dimensionsToBeAdded[`${dimensionName}-${i}`] = `${dimensionValue}-${i}`; } - + // Act & Assess - expect(() => metrics.addDimensions(dimensionsToBeAdded)).not.toThrowError(); - expect(Object.keys(metrics['dimensions']).length).toBe(MAX_DIMENSION_COUNT); - + expect(() => + metrics.addDimensions(dimensionsToBeAdded) + ).not.toThrowError(); + expect(Object.keys(metrics['dimensions']).length).toBe( + MAX_DIMENSION_COUNT + ); }); test('it should throw error if number of dimensions exceeds the maximum allowed', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const dimensionName = 'test-dimension'; @@ -444,115 +463,138 @@ describe('Class: Metrics', () => { for (let i = 0; i < MAX_DIMENSION_COUNT; i++) { dimensionsToBeAdded[`${dimensionName}-${i}`] = `${dimensionValue}-${i}`; } - + // Act & Assess - expect(() => metrics.addDimensions(dimensionsToBeAdded)).not.toThrowError(); - expect(Object.keys(metrics['dimensions']).length).toBe(MAX_DIMENSION_COUNT); - expect(() => - metrics.addDimensions({ 'another-dimension': 'another-dimension-value' }) - ).toThrowError(`Unable to add 1 dimensions: the number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`); - - }); - + expect(() => + metrics.addDimensions(dimensionsToBeAdded) + ).not.toThrowError(); + expect(Object.keys(metrics['dimensions']).length).toBe( + MAX_DIMENSION_COUNT + ); + expect(() => + metrics.addDimensions({ + 'another-dimension': 'another-dimension-value', + }) + ).toThrowError( + `Unable to add 1 dimensions: the number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}` + ); + }); }); describe('Method: addMetadata', () => { - test('it should add metadata', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetadata('foo', 'bar'); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - metadata: { 'foo': 'bar' } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + metadata: { foo: 'bar' }, + }) + ); }); test('it should update existing metadata value if same metadata is added again', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetadata('foo', 'bar'); metrics.addMetadata('foo', 'baz'); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - metadata: { 'foo': 'baz' } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + metadata: { foo: 'baz' }, + }) + ); }); }); describe('Method: addMetric', () => { - test('it should store metrics when called', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const metricName = 'test-metric'; // Act - metrics.addMetric(metricName, MetricUnits.Count, 1, MetricResolution.High); + metrics.addMetric( + metricName, + MetricUnits.Count, + 1, + MetricResolution.High + ); // Assess - expect(metrics).toEqual(expect.objectContaining({ - storedMetrics: { - [metricName]: { - name: metricName, - resolution: MetricResolution.High, - unit: MetricUnits.Count, - value: 1 - } - }, - })); - + expect(metrics).toEqual( + expect.objectContaining({ + storedMetrics: { + [metricName]: { + name: metricName, + resolution: MetricResolution.High, + unit: MetricUnits.Count, + value: 1, + }, + }, + }) + ); }); test('it should store multiple metrics when called with multiple metric name', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); // Act - metrics.addMetric('test-metric-1', MetricUnits.Count, 1, MetricResolution.High); - metrics.addMetric('test-metric-2', MetricUnits.Count, 3, MetricResolution.High); - metrics.addMetric('test-metric-3', MetricUnits.Count, 6, MetricResolution.High); + metrics.addMetric( + 'test-metric-1', + MetricUnits.Count, + 1, + MetricResolution.High + ); + metrics.addMetric( + 'test-metric-2', + MetricUnits.Count, + 3, + MetricResolution.High + ); + metrics.addMetric( + 'test-metric-3', + MetricUnits.Count, + 6, + MetricResolution.High + ); // Assess - expect(metrics).toEqual(expect.objectContaining({ - storedMetrics: { - 'test-metric-1': { - name: 'test-metric-1', - resolution: MetricResolution.High, - unit: MetricUnits.Count, - value: 1 - }, - 'test-metric-2': { - name: 'test-metric-2', - resolution: MetricResolution.High, - unit: MetricUnits.Count, - value: 3 + expect(metrics).toEqual( + expect.objectContaining({ + storedMetrics: { + 'test-metric-1': { + name: 'test-metric-1', + resolution: MetricResolution.High, + unit: MetricUnits.Count, + value: 1, + }, + 'test-metric-2': { + name: 'test-metric-2', + resolution: MetricResolution.High, + unit: MetricUnits.Count, + value: 3, + }, + 'test-metric-3': { + name: 'test-metric-3', + resolution: MetricResolution.High, + unit: MetricUnits.Count, + value: 6, + }, }, - 'test-metric-3': { - name: 'test-metric-3', - resolution: MetricResolution.High, - unit: MetricUnits.Count, - value: 6 - } - }, - })); - + }) + ); }); test('it should store metrics with standard resolution when called without resolution', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); @@ -561,27 +603,27 @@ describe('Class: Metrics', () => { metrics.addMetric('test-metric-2', MetricUnits.Seconds, 3); // Assess - expect(metrics).toEqual(expect.objectContaining({ - storedMetrics: { - 'test-metric-1': { - name: 'test-metric-1', - resolution: MetricResolution.Standard, - unit: MetricUnits.Count, - value: 1 + expect(metrics).toEqual( + expect.objectContaining({ + storedMetrics: { + 'test-metric-1': { + name: 'test-metric-1', + resolution: MetricResolution.Standard, + unit: MetricUnits.Count, + value: 1, + }, + 'test-metric-2': { + name: 'test-metric-2', + resolution: MetricResolution.Standard, + unit: MetricUnits.Seconds, + value: 3, + }, }, - 'test-metric-2': { - name: 'test-metric-2', - resolution: MetricResolution.Standard, - unit: MetricUnits.Seconds, - value: 3 - } - }, - })); - + }) + ); }); test('it should group the metric values together in an array when trying to add same metric with different values', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const metricName = 'test-metric'; @@ -591,23 +633,23 @@ describe('Class: Metrics', () => { metrics.addMetric(metricName, MetricUnits.Count, 5); metrics.addMetric(metricName, MetricUnits.Count, 1); metrics.addMetric(metricName, MetricUnits.Count, 4); - - // Assess - expect(metrics).toEqual(expect.objectContaining({ - storedMetrics: { - [metricName]: { - name: metricName, - resolution: MetricResolution.Standard, - unit: MetricUnits.Count, - value: [ 1, 5, 1, 4 ] - } - }, - })); + // Assess + expect(metrics).toEqual( + expect.objectContaining({ + storedMetrics: { + [metricName]: { + name: metricName, + resolution: MetricResolution.Standard, + unit: MetricUnits.Count, + value: [1, 5, 1, 4], + }, + }, + }) + ); }); test('it should throw an error when trying to add same metric with different unit', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const metricName = 'test-metric'; @@ -616,352 +658,404 @@ describe('Class: Metrics', () => { expect(() => { metrics.addMetric(metricName, MetricUnits.Count, 1); metrics.addMetric(metricName, MetricUnits.Kilobits, 5); - }).toThrowError(`Metric "${metricName}" has already been added with unit "${MetricUnits.Count}", but we received unit "${MetricUnits.Kilobits}". Did you mean to use metric unit "${MetricUnits.Count}"?`); - + }).toThrowError( + `Metric "${metricName}" has already been added with unit "${MetricUnits.Count}", but we received unit "${MetricUnits.Kilobits}". Did you mean to use metric unit "${MetricUnits.Count}"?` + ); }); test('it should publish metrics if stored metrics count has already reached max metric size threshold & then store remaining metric', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - const publishStoredMetricsSpy = jest.spyOn(metrics, 'publishStoredMetrics'); + const publishStoredMetricsSpy = jest.spyOn( + metrics, + 'publishStoredMetrics' + ); const metricName = 'test-metric'; - + // Act & Assess expect(() => { for (let i = 0; i < MAX_METRICS_SIZE; i++) { metrics.addMetric(`${metricName}-${i}`, MetricUnits.Count, i); } }).not.toThrowError(); - expect(Object.keys(metrics['storedMetrics']).length).toEqual(MAX_METRICS_SIZE); - metrics.addMetric('another-metric', MetricUnits.Count, MAX_METRICS_SIZE + 1); + expect(Object.keys(metrics['storedMetrics']).length).toEqual( + MAX_METRICS_SIZE + ); + metrics.addMetric( + 'another-metric', + MetricUnits.Count, + MAX_METRICS_SIZE + 1 + ); expect(publishStoredMetricsSpy).toHaveBeenCalledTimes(1); - expect(metrics).toEqual(expect.objectContaining({ - storedMetrics: { - 'another-metric': { - name: 'another-metric', - resolution: MetricResolution.Standard, - unit: MetricUnits.Count, - value: MAX_METRICS_SIZE + 1 - } - }, - })); - + expect(metrics).toEqual( + expect.objectContaining({ + storedMetrics: { + 'another-metric': { + name: 'another-metric', + resolution: MetricResolution.Standard, + unit: MetricUnits.Count, + value: MAX_METRICS_SIZE + 1, + }, + }, + }) + ); }); test('it should not publish metrics if stored metrics count has not reached max metric size threshold', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - const publishStoredMetricsSpy = jest.spyOn(metrics, 'publishStoredMetrics'); + const publishStoredMetricsSpy = jest.spyOn( + metrics, + 'publishStoredMetrics' + ); const metricName = 'test-metric'; - + // Act & Assess expect(() => { for (let i = 0; i < MAX_METRICS_SIZE - 1; i++) { metrics.addMetric(`${metricName}-${i}`, MetricUnits.Count, i); } }).not.toThrowError(); - expect(Object.keys(metrics['storedMetrics']).length).toEqual(MAX_METRICS_SIZE - 1); + expect(Object.keys(metrics['storedMetrics']).length).toEqual( + MAX_METRICS_SIZE - 1 + ); metrics.addMetric('another-metric', MetricUnits.Count, MAX_METRICS_SIZE); expect(publishStoredMetricsSpy).toHaveBeenCalledTimes(0); - expect(Object.keys(metrics['storedMetrics']).length).toEqual(MAX_METRICS_SIZE); - + expect(Object.keys(metrics['storedMetrics']).length).toEqual( + MAX_METRICS_SIZE + ); }); test('it should publish metrics on every call if singleMetric is set to true', () => { - // Prepare - const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const publishStoredMetricsSpy = jest.spyOn(metrics, 'publishStoredMetrics'); - + const metrics: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const publishStoredMetricsSpy = jest.spyOn( + metrics, + 'publishStoredMetrics' + ); + // Act metrics.addMetric('test-metric-1', MetricUnits.Count, 1); metrics.addMetric('test-metric-2', MetricUnits.Bits, 100); - + // Assess expect(publishStoredMetricsSpy).toHaveBeenCalledTimes(2); - }); test('it should not publish metrics on every call if singleMetric is set to false', () => { - // Prepare - const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: false }); - const publishStoredMetricsSpy = jest.spyOn(metrics, 'publishStoredMetrics'); - + const metrics: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: false, + }); + const publishStoredMetricsSpy = jest.spyOn( + metrics, + 'publishStoredMetrics' + ); + // Act metrics.addMetric('test-metric-1', MetricUnits.Count, 1); metrics.addMetric('test-metric-2', MetricUnits.Bits, 100); - + // Assess expect(publishStoredMetricsSpy).toHaveBeenCalledTimes(0); - }); test('it should not publish metrics on every call if singleMetric is not provided', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - const publishStoredMetricsSpy = jest.spyOn(metrics, 'publishStoredMetrics'); - + const publishStoredMetricsSpy = jest.spyOn( + metrics, + 'publishStoredMetrics' + ); + // Act metrics.addMetric('test-metric-1', MetricUnits.Count, 1); metrics.addMetric('test-metric-2', MetricUnits.Bits, 100); - + // Assess expect(publishStoredMetricsSpy).toHaveBeenCalledTimes(0); - }); - }); describe('Methods: captureColdStartMetric', () => { - test('it should call addMetric with correct parameters', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - const singleMetricMock: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const singleMetricSpy = jest.spyOn(metrics, 'singleMetric').mockImplementation(() => singleMetricMock); + const singleMetricMock: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const singleMetricSpy = jest + .spyOn(metrics, 'singleMetric') + .mockImplementation(() => singleMetricMock); const addMetricSpy = jest.spyOn(singleMetricMock, 'addMetric'); - - // Act + + // Act metrics.captureColdStartMetric(); - + // Assess expect(singleMetricSpy).toBeCalledTimes(1); expect(addMetricSpy).toBeCalledTimes(1); - expect(addMetricSpy).toBeCalledWith(COLD_START_METRIC, MetricUnits.Count, 1); - + expect(addMetricSpy).toBeCalledWith( + COLD_START_METRIC, + MetricUnits.Count, + 1 + ); }); test('it should call setDefaultDimensions with correct parameters', () => { - // Prepare const defaultDimensions: Dimensions = { - 'foo': 'bar', - 'service': 'order' + foo: 'bar', + service: 'order', }; - const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, defaultDimensions }); - const singleMetricMock: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const singleMetricSpy = jest.spyOn(metrics, 'singleMetric').mockImplementation(() => singleMetricMock); - const setDefaultDimensionsSpy = jest.spyOn(singleMetricMock, 'setDefaultDimensions'); - - // Act + const metrics: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + defaultDimensions, + }); + const singleMetricMock: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const singleMetricSpy = jest + .spyOn(metrics, 'singleMetric') + .mockImplementation(() => singleMetricMock); + const setDefaultDimensionsSpy = jest.spyOn( + singleMetricMock, + 'setDefaultDimensions' + ); + + // Act metrics.captureColdStartMetric(); - + // Assess expect(singleMetricSpy).toBeCalledTimes(1); expect(setDefaultDimensionsSpy).toBeCalledTimes(1); - expect(setDefaultDimensionsSpy).toBeCalledWith({ service: defaultDimensions.service }); - + expect(setDefaultDimensionsSpy).toBeCalledWith({ + service: defaultDimensions.service, + }); }); test('it should call setDefaultDimensions with correct parameters when defaultDimensions are not set', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - const singleMetricMock: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const singleMetricSpy = jest.spyOn(metrics, 'singleMetric').mockImplementation(() => singleMetricMock); - const setDefaultDimensionsSpy = jest.spyOn(singleMetricMock, 'setDefaultDimensions'); - - // Act + const singleMetricMock: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const singleMetricSpy = jest + .spyOn(metrics, 'singleMetric') + .mockImplementation(() => singleMetricMock); + const setDefaultDimensionsSpy = jest.spyOn( + singleMetricMock, + 'setDefaultDimensions' + ); + + // Act metrics.captureColdStartMetric(); - + // Assess expect(singleMetricSpy).toBeCalledTimes(1); expect(setDefaultDimensionsSpy).toBeCalledTimes(1); - expect(setDefaultDimensionsSpy).toBeCalledWith({ service: 'service_undefined' }); - + expect(setDefaultDimensionsSpy).toBeCalledWith({ + service: 'service_undefined', + }); }); test('it should call addDimension, if functionName is set', () => { - // Prepare const functionName = 'coldStart'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.setFunctionName(functionName); - const singleMetricMock: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const singleMetricSpy = jest.spyOn(metrics, 'singleMetric').mockImplementation(() => singleMetricMock); + const singleMetricMock: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const singleMetricSpy = jest + .spyOn(metrics, 'singleMetric') + .mockImplementation(() => singleMetricMock); const addDimensionSpy = jest.spyOn(singleMetricMock, 'addDimension'); - - // Act + + // Act metrics.captureColdStartMetric(); - + // Assess expect(singleMetricSpy).toBeCalledTimes(1); expect(addDimensionSpy).toBeCalledTimes(1); expect(addDimensionSpy).toBeCalledWith('function_name', functionName); - }); test('it should not call addDimension, if functionName is not set', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - const singleMetricMock: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const singleMetricSpy = jest.spyOn(metrics, 'singleMetric').mockImplementation(() => singleMetricMock); + const singleMetricMock: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const singleMetricSpy = jest + .spyOn(metrics, 'singleMetric') + .mockImplementation(() => singleMetricMock); const addDimensionSpy = jest.spyOn(singleMetricMock, 'addDimension'); - - // Act + + // Act metrics.captureColdStartMetric(); - + // Assess expect(singleMetricSpy).toBeCalledTimes(1); expect(addDimensionSpy).toBeCalledTimes(0); - }); test('it should not call any function, if there is no cold start', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); jest.spyOn(metrics, 'isColdStart').mockImplementation(() => false); - const singleMetricMock: Metrics = new Metrics({ namespace: TEST_NAMESPACE, singleMetric: true }); - const singleMetricSpy = jest.spyOn(metrics, 'singleMetric').mockImplementation(() => singleMetricMock); + const singleMetricMock: Metrics = new Metrics({ + namespace: TEST_NAMESPACE, + singleMetric: true, + }); + const singleMetricSpy = jest + .spyOn(metrics, 'singleMetric') + .mockImplementation(() => singleMetricMock); const addMetricSpy = jest.spyOn(singleMetricMock, 'addMetric'); - const setDefaultDimensionsSpy = jest.spyOn(singleMetricMock, 'setDefaultDimensions'); + const setDefaultDimensionsSpy = jest.spyOn( + singleMetricMock, + 'setDefaultDimensions' + ); const addDimensionSpy = jest.spyOn(singleMetricMock, 'addDimension'); - - // Act + + // Act metrics.captureColdStartMetric(); - + // Assess expect(singleMetricSpy).toBeCalledTimes(0); expect(setDefaultDimensionsSpy).toBeCalledTimes(0); expect(addDimensionSpy).toBeCalledTimes(0); expect(addMetricSpy).toBeCalledTimes(0); - }); - }); describe('Method: clearDefaultDimensions', () => { - test('it should clear all default dimensions', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - metrics.setDefaultDimensions({ 'foo': 'bar' }); - + metrics.setDefaultDimensions({ foo: 'bar' }); + // Act metrics.clearDefaultDimensions(); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - defaultDimensions: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + defaultDimensions: {}, + }) + ); }); test('it should only clear default dimensions', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - metrics.setDefaultDimensions({ 'foo': 'bar' }); + metrics.setDefaultDimensions({ foo: 'bar' }); metrics.addDimension('environment', 'dev'); - + // Act metrics.clearDefaultDimensions(); - - // Assess - expect(metrics).toEqual(expect.objectContaining({ - defaultDimensions: {}, - dimensions: { - 'environment': 'dev' - } - })); + // Assess + expect(metrics).toEqual( + expect.objectContaining({ + defaultDimensions: {}, + dimensions: { + environment: 'dev', + }, + }) + ); }); - - }); + }); describe('Method: clearDimensions', () => { - test('it should clear all dimensions', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.addDimension('foo', 'bar'); - + // Act metrics.clearDimensions(); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - dimensions: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + dimensions: {}, + }) + ); }); test('it should only clear dimensions', () => { - // Prepare - const metrics: Metrics = new Metrics({ defaultDimensions: { 'environment': 'dev' } }); + const metrics: Metrics = new Metrics({ + defaultDimensions: { environment: 'dev' }, + }); metrics.addDimension('foo', 'bar'); - + // Act metrics.clearDimensions(); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - dimensions: {}, - defaultDimensions: { - 'environment': 'dev', - 'service': 'service_undefined' - } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + dimensions: {}, + defaultDimensions: { + environment: 'dev', + service: 'service_undefined', + }, + }) + ); }); - }); describe('Method: clearMetadata', () => { - test('it should clear all metadata', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.addMetadata('foo', 'bar'); metrics.addMetadata('test', 'baz'); - + // Act metrics.clearMetadata(); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - metadata: {} - })); - + expect(metrics).toEqual( + expect.objectContaining({ + metadata: {}, + }) + ); }); - }); describe('Method: clearMetrics', () => { - test('it should clear stored metrics', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const metricName = 'test-metric'; - + // Act metrics.addMetric(metricName, MetricUnits.Count, 1); metrics.clearMetrics(); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - storedMetrics: {}, - })); - + expect(metrics).toEqual( + expect.objectContaining({ + storedMetrics: {}, + }) + ); }); - }); - - describe('Method: logMetrics', () => { + describe('Method: logMetrics', () => { let metrics: Metrics; let publishStoredMetricsSpy: jest.SpyInstance; let addMetricSpy: jest.SpyInstance; @@ -969,7 +1063,7 @@ describe('Class: Metrics', () => { let throwOnEmptyMetricsSpy: jest.SpyInstance; let setDefaultDimensionsSpy: jest.SpyInstance; const decoratorLambdaExpectedReturnValue = 'Lambda invoked!'; - const decoratorLambdaMetric= 'decorator-lambda-test-metric'; + const decoratorLambdaMetric = 'decorator-lambda-test-metric'; beforeEach(() => { metrics = new Metrics({ namespace: TEST_NAMESPACE }); @@ -981,218 +1075,229 @@ describe('Class: Metrics', () => { }); test('it should execute lambda function & publish stored metrics', async () => { - // Prepare const handler: Handler = setupDecoratorLambdaHandler(metrics); // Act - const actualResult = await handler(event, context, () => console.log('callback')); + const actualResult = await handler(event, context, () => + console.log('callback') + ); // Assess expect(actualResult).toEqual(decoratorLambdaExpectedReturnValue); - expect(addMetricSpy).toHaveBeenNthCalledWith(1, decoratorLambdaMetric, MetricUnits.Count, 1); + expect(addMetricSpy).toHaveBeenNthCalledWith( + 1, + decoratorLambdaMetric, + MetricUnits.Count, + 1 + ); expect(publishStoredMetricsSpy).toBeCalledTimes(1); expect(captureColdStartMetricSpy).not.toBeCalled(); expect(throwOnEmptyMetricsSpy).not.toBeCalled(); expect(setDefaultDimensionsSpy).not.toBeCalled(); - }); test('it should capture cold start metrics, if passed in the options as true', async () => { - // Prepare - const handler: Handler = setupDecoratorLambdaHandler(metrics, { captureColdStartMetric: true }); + const handler: Handler = setupDecoratorLambdaHandler(metrics, { + captureColdStartMetric: true, + }); // Act - const actualResult = await handler(event, context, () => console.log('callback')); + const actualResult = await handler(event, context, () => + console.log('callback') + ); // Assess expect(actualResult).toEqual(decoratorLambdaExpectedReturnValue); - expect(addMetricSpy).toHaveBeenNthCalledWith(1, decoratorLambdaMetric, MetricUnits.Count, 1); + expect(addMetricSpy).toHaveBeenNthCalledWith( + 1, + decoratorLambdaMetric, + MetricUnits.Count, + 1 + ); expect(captureColdStartMetricSpy).toBeCalledTimes(1); expect(publishStoredMetricsSpy).toBeCalledTimes(1); expect(throwOnEmptyMetricsSpy).not.toBeCalled(); expect(setDefaultDimensionsSpy).not.toBeCalled(); - }); test('it should call throwOnEmptyMetrics, if passed in the options as true', async () => { - // Prepare - const handler: Handler = setupDecoratorLambdaHandler(metrics, { throwOnEmptyMetrics: true }); - + const handler: Handler = setupDecoratorLambdaHandler(metrics, { + throwOnEmptyMetrics: true, + }); + // Act - const actualResult = await handler(event, context, () => console.log('callback')); + const actualResult = await handler(event, context, () => + console.log('callback') + ); // Assess expect(actualResult).toEqual(decoratorLambdaExpectedReturnValue); - expect(addMetricSpy).toHaveBeenNthCalledWith(1, decoratorLambdaMetric, MetricUnits.Count, 1); + expect(addMetricSpy).toHaveBeenNthCalledWith( + 1, + decoratorLambdaMetric, + MetricUnits.Count, + 1 + ); expect(throwOnEmptyMetricsSpy).toBeCalledTimes(1); expect(publishStoredMetricsSpy).toBeCalledTimes(1); expect(captureColdStartMetricSpy).not.toBeCalled(); expect(setDefaultDimensionsSpy).not.toBeCalled(); - }); test('it should set default dimensions if passed in the options as true', async () => { - // Prepare const defaultDimensions = { - 'foo': 'bar', - 'service': 'order' + foo: 'bar', + service: 'order', }; - const handler: Handler = setupDecoratorLambdaHandler(metrics, { defaultDimensions }); - + const handler: Handler = setupDecoratorLambdaHandler(metrics, { + defaultDimensions, + }); + // Act - const actualResult = await handler(event, context, () => console.log('callback')); - + const actualResult = await handler(event, context, () => + console.log('callback') + ); + // Assess expect(actualResult).toEqual(decoratorLambdaExpectedReturnValue); - expect(addMetricSpy).toHaveBeenNthCalledWith(1, decoratorLambdaMetric, MetricUnits.Count, 1); - expect(setDefaultDimensionsSpy).toHaveBeenNthCalledWith(1, defaultDimensions); + expect(addMetricSpy).toHaveBeenNthCalledWith( + 1, + decoratorLambdaMetric, + MetricUnits.Count, + 1 + ); + expect(setDefaultDimensionsSpy).toHaveBeenNthCalledWith( + 1, + defaultDimensions + ); expect(publishStoredMetricsSpy).toBeCalledTimes(1); expect(throwOnEmptyMetricsSpy).not.toBeCalled(); expect(captureColdStartMetricSpy).not.toBeCalled(); - }); test('it should throw error if lambda handler throws any error', async () => { - // Prepare const errorMessage = 'Unexpected error occurred!'; class LambdaFunction implements LambdaInterface { - @metrics.logMetrics() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - public async handler(_event: TEvent, _context: Context): Promise { + public async handler( + _event: TEvent, + _context: Context + ): Promise { throw new Error(errorMessage); } - } const handlerClass = new LambdaFunction(); const handler = handlerClass.handler.bind(handlerClass); - + // Act & Assess await expect(handler(event, context)).rejects.toThrowError(errorMessage); - }); - }); describe('Methods: publishStoredMetrics', () => { - test('it should log warning if no metrics are added & throwOnEmptyMetrics is false', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); - // Act + // Act metrics.publishStoredMetrics(); // Assess expect(consoleWarnSpy).toBeCalledTimes(1); expect(consoleWarnSpy).toBeCalledWith( - 'No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using \'throwOnEmptyMetrics\'', + 'No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using `throwOnEmptyMetrics`' ); - }); test('it should call serializeMetrics && log the stringified return value of serializeMetrics', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.addMetric('test-metric', MetricUnits.Count, 10); const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); const mockData: EmfOutput = { - '_aws': { - 'Timestamp': mockDate.getTime(), - 'CloudWatchMetrics': [ + _aws: { + Timestamp: mockDate.getTime(), + CloudWatchMetrics: [ { - 'Namespace': 'test', - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Namespace: 'test', + Dimensions: [['service']], + Metrics: [ { - 'Name': 'test-metric', - 'Unit': MetricUnits.Count - } - ] - } - ] + Name: 'test-metric', + Unit: MetricUnits.Count, + }, + ], + }, + ], }, - 'service': 'service_undefined', - 'test-metric': 10 + service: 'service_undefined', + 'test-metric': 10, }; - const serializeMetricsSpy = jest.spyOn(metrics, 'serializeMetrics').mockImplementation(() => mockData); - - // Act + const serializeMetricsSpy = jest + .spyOn(metrics, 'serializeMetrics') + .mockImplementation(() => mockData); + + // Act metrics.publishStoredMetrics(); - + // Assess expect(serializeMetricsSpy).toBeCalledTimes(1); expect(consoleLogSpy).toBeCalledTimes(1); expect(consoleLogSpy).toBeCalledWith(JSON.stringify(mockData)); - }); test('it should call clearMetrics function', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.addMetric('test-metric', MetricUnits.Count, 10); const clearMetricsSpy = jest.spyOn(metrics, 'clearMetrics'); - - // Act + + // Act metrics.publishStoredMetrics(); - + // Assess expect(clearMetricsSpy).toBeCalledTimes(1); - }); test('it should call clearDimensions function', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.addMetric('test-metric', MetricUnits.Count, 10); const clearDimensionsSpy = jest.spyOn(metrics, 'clearDimensions'); - - // Act + + // Act metrics.publishStoredMetrics(); - + // Assess expect(clearDimensionsSpy).toBeCalledTimes(1); - }); test('it should call clearMetadata function', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); metrics.addMetric('test-metric', MetricUnits.Count, 10); const clearMetadataSpy = jest.spyOn(metrics, 'clearMetadata'); - - // Act + + // Act metrics.publishStoredMetrics(); - + // Assess expect(clearMetadataSpy).toBeCalledTimes(1); - }); - }); describe('Method: serializeMetrics', () => { - const defaultServiceName = 'service_undefined'; test('it should print warning, if no namespace provided in constructor or environment variable', () => { - // Prepare process.env.POWERTOOLS_METRICS_NAMESPACE = ''; const metrics: Metrics = new Metrics(); @@ -1202,69 +1307,69 @@ describe('Class: Metrics', () => { metrics.serializeMetrics(); // Assess - expect(consoleWarnSpy).toBeCalledWith('Namespace should be defined, default used'); - + expect(consoleWarnSpy).toBeCalledWith( + 'Namespace should be defined, default used' + ); }); test('it should return right object compliant with Cloudwatch EMF', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, serviceName: 'test-service', defaultDimensions: { - 'environment': 'dev' - } + environment: 'dev', + }, }); // Act metrics.addMetric('successfulBooking', MetricUnits.Count, 1); metrics.addMetric('successfulBooking', MetricUnits.Count, 3); - metrics.addMetric('failedBooking', MetricUnits.Count, 1, MetricResolution.High); + metrics.addMetric( + 'failedBooking', + MetricUnits.Count, + 1, + MetricResolution.High + ); const loggedData = metrics.serializeMetrics(); // Assess - expect(loggedData).toEqual( - { - '_aws': { - 'Timestamp': mockDate.getTime(), - 'CloudWatchMetrics': [ - { - 'Namespace': TEST_NAMESPACE, - 'Dimensions': [ - [ - 'service', - 'environment' - ] - ], - 'Metrics': [ - { - 'Name': 'successfulBooking', - 'Unit': MetricUnits.Count - }, - { - 'Name': 'failedBooking', - 'Unit': MetricUnits.Count, - 'StorageResolution': 1 - } - ] - } - ] - }, - 'environment': 'dev', - 'service': 'test-service', - 'successfulBooking': [ 1, 3 ], - 'failedBooking': 1 - } - ); + expect(loggedData).toEqual({ + _aws: { + Timestamp: mockDate.getTime(), + CloudWatchMetrics: [ + { + Namespace: TEST_NAMESPACE, + Dimensions: [['service', 'environment']], + Metrics: [ + { + Name: 'successfulBooking', + Unit: MetricUnits.Count, + }, + { + Name: 'failedBooking', + Unit: MetricUnits.Count, + StorageResolution: 1, + }, + ], + }, + ], + }, + environment: 'dev', + service: 'test-service', + successfulBooking: [1, 3], + failedBooking: 1, + }); }); - test('it should log service dimension correctly when passed', () => { - + test('it should log service dimension correctly when passed', () => { // Prepare const serviceName = 'test-service'; const testMetric = 'test-metric'; - const metrics: Metrics = new Metrics({ serviceName: serviceName, namespace: TEST_NAMESPACE }); + const metrics: Metrics = new Metrics({ + serviceName: serviceName, + namespace: TEST_NAMESPACE, + }); // Act metrics.addMetric(testMetric, MetricUnits.Count, 10); @@ -1273,33 +1378,27 @@ describe('Class: Metrics', () => { // Assess expect(loggedData.service).toEqual(serviceName); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': testMetric, - 'Unit': MetricUnits.Count - } + Name: testMetric, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': serviceName, - [testMetric]: 10 + service: serviceName, + [testMetric]: 10, }); - }); test('it should log service dimension correctly using environment variable when not specified in constructor', () => { - // Prepare const serviceName = 'hello-world-service'; process.env.POWERTOOLS_SERVICE_NAME = serviceName; @@ -1313,629 +1412,586 @@ describe('Class: Metrics', () => { // Assess expect(loggedData.service).toEqual(serviceName); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': testMetric, - 'Unit': MetricUnits.Count - } + Name: testMetric, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': serviceName, - [testMetric]: 10 + service: serviceName, + [testMetric]: 10, }); - }); test('it should log default dimensions correctly', () => { - // Prepare const additionalDimensions = { - 'foo': 'bar', - 'env': 'dev' + foo: 'bar', + env: 'dev', }; const testMetric = 'test-metric'; - const metrics: Metrics = new Metrics({ defaultDimensions: additionalDimensions, namespace: TEST_NAMESPACE }); - + const metrics: Metrics = new Metrics({ + defaultDimensions: additionalDimensions, + namespace: TEST_NAMESPACE, + }); + // Act metrics.addMetric(testMetric, MetricUnits.Count, 10); const loggedData = metrics.serializeMetrics(); - + // Assess - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(3); + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual( + 3 + ); expect(loggedData.service).toEqual(defaultServiceName); expect(loggedData.foo).toEqual(additionalDimensions.foo); expect(loggedData.env).toEqual(additionalDimensions.env); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service', - 'foo', - 'env' - ] - ], - 'Metrics': [ + Dimensions: [['service', 'foo', 'env']], + Metrics: [ { - 'Name': testMetric, - 'Unit': MetricUnits.Count - } + Name: testMetric, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', + service: 'service_undefined', [testMetric]: 10, - 'env': 'dev', - 'foo': 'bar', + env: 'dev', + foo: 'bar', }); - }); test('it should log additional dimensions correctly', () => { - // Prepare const testMetric = 'test-metric'; const additionalDimension = { name: 'metric2', value: 'metric2Value' }; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act - metrics.addMetric('test-metric', MetricUnits.Count, 10, MetricResolution.High); + metrics.addMetric( + 'test-metric', + MetricUnits.Count, + 10, + MetricResolution.High + ); metrics.addDimension(additionalDimension.name, additionalDimension.value); const loggedData = metrics.serializeMetrics(); - + // Assess - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual( + 2 + ); expect(loggedData.service).toEqual(defaultServiceName); - expect(loggedData[additionalDimension.name]).toEqual(additionalDimension.value); + expect(loggedData[additionalDimension.name]).toEqual( + additionalDimension.value + ); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service', - 'metric2' - ] - ], - 'Metrics': [ + Dimensions: [['service', 'metric2']], + Metrics: [ { - 'Name': testMetric, - 'StorageResolution': 1, - 'Unit': MetricUnits.Count - } + Name: testMetric, + StorageResolution: 1, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', + service: 'service_undefined', [testMetric]: 10, - 'metric2': 'metric2Value' + metric2: 'metric2Value', }); - }); test('it should log additional bulk dimensions correctly', () => { - // Prepare const testMetric = 'test-metric'; const additionalDimensions: LooseObject = { metric2: 'metric2Value', - metric3: 'metric3Value' + metric3: 'metric3Value', }; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act - metrics.addMetric(testMetric, MetricUnits.Count, 10, MetricResolution.High); + metrics.addMetric( + testMetric, + MetricUnits.Count, + 10, + MetricResolution.High + ); metrics.addDimensions(additionalDimensions); const loggedData = metrics.serializeMetrics(); - + // Assess - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(3); + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual( + 3 + ); expect(loggedData.service).toEqual(defaultServiceName); Object.keys(additionalDimensions).forEach((key) => { expect(loggedData[key]).toEqual(additionalDimensions[key]); }); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service', - 'metric2', - 'metric3', - ] - ], - 'Metrics': [ + Dimensions: [['service', 'metric2', 'metric3']], + Metrics: [ { - 'Name': testMetric, - 'StorageResolution': 1, - 'Unit': MetricUnits.Count - } + Name: testMetric, + StorageResolution: 1, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', + service: 'service_undefined', [testMetric]: 10, - 'metric2': 'metric2Value', - 'metric3': 'metric3Value' + metric2: 'metric2Value', + metric3: 'metric3Value', }); - }); test('it should log metadata correctly', () => { - // Prepare const testMetric = 'test-metric'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(testMetric, MetricUnits.Count, 10); metrics.addMetadata('foo', 'bar'); const loggedData = metrics.serializeMetrics(); - + // Assess expect(loggedData.foo).toEqual('bar'); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': testMetric, - 'Unit': MetricUnits.Count - } + Name: testMetric, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', + service: 'service_undefined', [testMetric]: 10, - 'foo': 'bar' + foo: 'bar', }); - }); test('it should throw error on empty metrics when throwOnEmptyMetrics is true', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.throwOnEmptyMetrics(); // Assess - expect(() => metrics.serializeMetrics()).toThrow('The number of metrics recorded must be higher than zero'); - + expect(() => metrics.serializeMetrics()).toThrow( + 'The number of metrics recorded must be higher than zero' + ); }); test('it should use the default namespace when no namespace is provided in constructor or found in environment variable', () => { - // Prepare process.env.POWERTOOLS_METRICS_NAMESPACE = ''; const testMetric = 'test-metric'; const metrics: Metrics = new Metrics(); - + // Act metrics.addMetric(testMetric, MetricUnits.Count, 10); const loggedData = metrics.serializeMetrics(); - + // Assess - expect(loggedData._aws.CloudWatchMetrics[0].Namespace).toEqual(DEFAULT_NAMESPACE); + expect(loggedData._aws.CloudWatchMetrics[0].Namespace).toEqual( + DEFAULT_NAMESPACE + ); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': testMetric, - 'Unit': MetricUnits.Count - } + Name: testMetric, + Unit: MetricUnits.Count, + }, ], - 'Namespace': DEFAULT_NAMESPACE - } + Namespace: DEFAULT_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', - [testMetric]: 10 + service: 'service_undefined', + [testMetric]: 10, }); - }); test('it should use namespace provided in constructor', () => { - // Prepare const testMetric = 'test-metric'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(testMetric, MetricUnits.Count, 10); const loggedData = metrics.serializeMetrics(); - + // Assess - expect(loggedData._aws.CloudWatchMetrics[0].Namespace).toEqual(TEST_NAMESPACE); + expect(loggedData._aws.CloudWatchMetrics[0].Namespace).toEqual( + TEST_NAMESPACE + ); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': testMetric, - 'Unit': MetricUnits.Count - } + Name: testMetric, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', - [testMetric]: 10 + service: 'service_undefined', + [testMetric]: 10, }); - }); test('it should contain a metric value if added once', () => { - // Prepare const metricName = 'test-metric'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(metricName, MetricUnits.Count, 10); const loggedData = metrics.serializeMetrics(); - + // Assess expect(loggedData._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); expect(loggedData[metricName]).toEqual(10); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': metricName, - 'Unit': MetricUnits.Count - } + Name: metricName, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', - [metricName]: 10 + service: 'service_undefined', + [metricName]: 10, }); - }); test('it should convert metric value with the same name and unit to array if added multiple times', () => { - // Prepare const metricName = 'test-metric'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(metricName, MetricUnits.Count, 10); metrics.addMetric(metricName, MetricUnits.Count, 20); const loggedData = metrics.serializeMetrics(); - + // Assess expect(loggedData._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); - expect(loggedData[metricName]).toEqual([ 10, 20 ]); + expect(loggedData[metricName]).toEqual([10, 20]); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': metricName, - 'Unit': MetricUnits.Count - } + Name: metricName, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', - [metricName]: [ 10, 20 ] + service: 'service_undefined', + [metricName]: [10, 20], }); - }); test('it should create multiple metric values if added multiple times', () => { - // Prepare const metricName1 = 'test-metric-1'; const metricName2 = 'test-metric-2'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(metricName1, MetricUnits.Count, 10); metrics.addMetric(metricName2, MetricUnits.Seconds, 20); const loggedData = metrics.serializeMetrics(); - + // Assess expect(loggedData._aws.CloudWatchMetrics[0].Metrics.length).toBe(2); expect(loggedData[metricName1]).toEqual(10); expect(loggedData[metricName2]).toEqual(20); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': metricName1, - 'Unit': MetricUnits.Count + Name: metricName1, + Unit: MetricUnits.Count, }, { - 'Name': metricName2, - 'Unit': MetricUnits.Seconds - } + Name: metricName2, + Unit: MetricUnits.Seconds, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', + service: 'service_undefined', [metricName1]: 10, - [metricName2]: 20 + [metricName2]: 20, }); - }); test('it should not contain `StorageResolution` as key for non-high resolution metrics', () => { - // Prepare const metricName = 'test-metric'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(metricName, MetricUnits.Count, 10); const loggedData = metrics.serializeMetrics(); - + // Assess expect(loggedData._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); - expect(loggedData._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBeUndefined(); + expect( + loggedData._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution + ).toBeUndefined(); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': metricName, - 'Unit': MetricUnits.Count - } + Name: metricName, + Unit: MetricUnits.Count, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', - [metricName]: 10 + service: 'service_undefined', + [metricName]: 10, }); - }); test('it should contain `StorageResolution` as key & high metric resolution as value for high resolution metrics', () => { - // Prepare const metricName1 = 'test-metric'; const metricName2 = 'test-metric-2'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.addMetric(metricName1, MetricUnits.Count, 10); - metrics.addMetric(metricName2, MetricUnits.Seconds, 10, MetricResolution.High); + metrics.addMetric( + metricName2, + MetricUnits.Seconds, + 10, + MetricResolution.High + ); const loggedData = metrics.serializeMetrics(); - + // Assess expect(loggedData._aws.CloudWatchMetrics[0].Metrics.length).toBe(2); - expect(loggedData._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBeUndefined(); - expect(loggedData._aws.CloudWatchMetrics[0].Metrics[1].StorageResolution).toEqual(MetricResolution.High); + expect( + loggedData._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution + ).toBeUndefined(); + expect( + loggedData._aws.CloudWatchMetrics[0].Metrics[1].StorageResolution + ).toEqual(MetricResolution.High); expect(loggedData).toEqual({ - '_aws': { - 'CloudWatchMetrics': [ + _aws: { + CloudWatchMetrics: [ { - 'Dimensions': [ - [ - 'service' - ] - ], - 'Metrics': [ + Dimensions: [['service']], + Metrics: [ { - 'Name': metricName1, - 'Unit': MetricUnits.Count + Name: metricName1, + Unit: MetricUnits.Count, }, { - 'Name': metricName2, - 'StorageResolution': 1, - 'Unit': MetricUnits.Seconds - } + Name: metricName2, + StorageResolution: 1, + Unit: MetricUnits.Seconds, + }, ], - 'Namespace': TEST_NAMESPACE - } + Namespace: TEST_NAMESPACE, + }, ], - 'Timestamp': mockDate.getTime() + Timestamp: mockDate.getTime(), }, - 'service': 'service_undefined', + service: 'service_undefined', [metricName1]: 10, - [metricName2]: 10 + [metricName2]: 10, }); - }); - }); describe('Method: setDefaultDimensions', () => { - test('it should set default dimensions correctly when service name is provided', () => { - // Prepare const serviceName = 'test-service'; const metrics: Metrics = new Metrics({ serviceName: serviceName }); const defaultDimensionsToBeAdded = { - 'environment': 'dev', - 'foo': 'bar', + environment: 'dev', + foo: 'bar', }; - + // Act metrics.setDefaultDimensions(defaultDimensionsToBeAdded); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - defaultDimensions: { - ...defaultDimensionsToBeAdded, - service: serviceName - } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + defaultDimensions: { + ...defaultDimensionsToBeAdded, + service: serviceName, + }, + }) + ); }); - + test('it should set default dimensions correctly when service name is not provided', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const defaultDimensionsToBeAdded = { - 'environment': 'dev', - 'foo': 'bar', + environment: 'dev', + foo: 'bar', }; - + // Act metrics.setDefaultDimensions(defaultDimensionsToBeAdded); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - defaultDimensions: { - ...defaultDimensionsToBeAdded, - service: 'service_undefined' - } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + defaultDimensions: { + ...defaultDimensionsToBeAdded, + service: 'service_undefined', + }, + }) + ); }); test('it should add default dimensions', () => { - // Prepare const serviceName = 'test-service'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, serviceName, - defaultDimensions: { 'test-dimension': 'test-dimension-value' } + defaultDimensions: { 'test-dimension': 'test-dimension-value' }, }); const defaultDimensionsToBeAdded = { - 'environment': 'dev', - 'foo': 'bar', + environment: 'dev', + foo: 'bar', }; - + // Act metrics.setDefaultDimensions(defaultDimensionsToBeAdded); - + // Assess - expect(metrics).toEqual(expect.objectContaining({ - defaultDimensions: { - ...defaultDimensionsToBeAdded, - service: serviceName, - 'test-dimension': 'test-dimension-value' - } - })); - + expect(metrics).toEqual( + expect.objectContaining({ + defaultDimensions: { + ...defaultDimensionsToBeAdded, + service: serviceName, + 'test-dimension': 'test-dimension-value', + }, + }) + ); }); test('it should update already added default dimensions values', () => { - // Prepare const serviceName = 'test-service'; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, serviceName, defaultDimensions: { - environment: 'dev' - } + environment: 'dev', + }, }); const defaultDimensionsToBeAdded = { - 'environment': 'prod', - 'foo': 'bar', + environment: 'prod', + foo: 'bar', }; - + // Act metrics.setDefaultDimensions(defaultDimensionsToBeAdded); - - // Assess - expect(metrics).toEqual(expect.objectContaining({ - defaultDimensions: { - foo: 'bar', - service: serviceName, - environment: 'prod' - } - })); + // Assess + expect(metrics).toEqual( + expect.objectContaining({ + defaultDimensions: { + foo: 'bar', + service: serviceName, + environment: 'prod', + }, + }) + ); }); test('it should throw error if number of default dimensions reaches the maximum allowed', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); const dimensionName = 'test-dimension'; @@ -1948,24 +2004,28 @@ describe('Class: Metrics', () => { } // Act & Assess - expect(() => metrics.setDefaultDimensions(defaultDimensions)).not.toThrowError(); - expect(Object.keys(metrics['defaultDimensions']).length).toBe(MAX_DIMENSION_COUNT - 1); + expect(() => + metrics.setDefaultDimensions(defaultDimensions) + ).not.toThrowError(); + expect(Object.keys(metrics['defaultDimensions']).length).toBe( + MAX_DIMENSION_COUNT - 1 + ); expect(() => { - metrics.setDefaultDimensions({ 'another-dimension': 'another-dimension-value' }); + metrics.setDefaultDimensions({ + 'another-dimension': 'another-dimension-value', + }); }).toThrowError('Max dimension count hit'); - }); test('it should consider default dimensions provided in constructor, while throwing error if number of default dimensions reaches the maximum allowed', () => { - // Prepare const initialDefaultDimensions: LooseObject = { 'test-dimension': 'test-value', - 'environment': 'dev' + environment: 'dev', }; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, - defaultDimensions: initialDefaultDimensions + defaultDimensions: initialDefaultDimensions, }); const dimensionName = 'test-dimension'; const dimensionValue = 'test-value'; @@ -1977,81 +2037,78 @@ describe('Class: Metrics', () => { } // Act & Assess - expect(() => metrics.setDefaultDimensions(defaultDimensions)).not.toThrowError(); - expect(Object.keys(metrics['defaultDimensions']).length).toBe(MAX_DIMENSION_COUNT - 1); + expect(() => + metrics.setDefaultDimensions(defaultDimensions) + ).not.toThrowError(); + expect(Object.keys(metrics['defaultDimensions']).length).toBe( + MAX_DIMENSION_COUNT - 1 + ); expect(() => { - metrics.setDefaultDimensions({ 'another-dimension': 'another-dimension-value' }); + metrics.setDefaultDimensions({ + 'another-dimension': 'another-dimension-value', + }); }).toThrowError('Max dimension count hit'); - }); - }); describe('Method: setFunctionName', () => { - test('it should set the function name', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.setFunctionName('test-function'); // Assess - expect(metrics).toEqual(expect.objectContaining({ - functionName: 'test-function' - })); - + expect(metrics).toEqual( + expect.objectContaining({ + functionName: 'test-function', + }) + ); }); - }); describe('Method: singleMetric', () => { - test('it should return a single Metric object', () => { - // Prepare const defaultDimensions = { - 'foo': 'bar', - 'service': 'order' + foo: 'bar', + service: 'order', }; const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE, defaultDimensions, - singleMetric: false + singleMetric: false, }); // Act const singleMetric = metrics.singleMetric(); - - //Asses - expect(singleMetric).toEqual(expect.objectContaining({ - isSingleMetric: true, - namespace: TEST_NAMESPACE, - defaultDimensions - })); + //Asses + expect(singleMetric).toEqual( + expect.objectContaining({ + isSingleMetric: true, + namespace: TEST_NAMESPACE, + defaultDimensions, + }) + ); }); - }); describe('Method: throwOnEmptyMetrics', () => { - test('it should set the throwOnEmptyMetrics flag to true', () => { - // Prepare const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE }); - + // Act metrics.throwOnEmptyMetrics(); // Assess - expect(metrics).toEqual(expect.objectContaining({ - shouldThrowOnEmptyMetrics: true - })); - + expect(metrics).toEqual( + expect.objectContaining({ + shouldThrowOnEmptyMetrics: true, + }) + ); }); - }); - }); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index c0bd34c036..1b450bf102 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -3,13 +3,13 @@ * * @group unit/metrics/middleware */ - import { Metrics, MetricUnits, logMetrics, - MetricResolution -} from '../../../../metrics/src';import middy from '@middy/core'; + MetricResolution, +} from '../../../../metrics/src'; +import middy from '@middy/core'; import { ExtraOptions } from '../../../src/types'; const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); @@ -18,7 +18,6 @@ const mockDate = new Date(1466424490000); jest.spyOn(global, 'Date').mockImplementation(() => mockDate); describe('Middy middleware', () => { - const dummyEvent = { key1: 'value1', key2: 'value2', @@ -31,117 +30,166 @@ describe('Middy middleware', () => { memoryLimitInMB: '128', logGroupName: '/aws/lambda/foo-bar-function-123456abcdef', logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:Example', + invokedFunctionArn: + 'arn:aws:lambda:eu-west-1:123456789012:function:Example', awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', getRemainingTimeInMillis: () => 1234, done: () => console.log('Done!'), fail: () => console.log('Failed!'), succeed: () => console.log('Succeeded!'), }; - + beforeEach(() => { jest.resetModules(); jest.clearAllMocks(); }); describe('throwOnEmptyMetrics', () => { - test('should throw on empty metrics if set to true', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { console.log('do nothing'); }; - const handler = middy(lambdaHandler).use(logMetrics(metrics, { throwOnEmptyMetrics: true })); + const handler = middy(lambdaHandler).use( + logMetrics(metrics, { throwOnEmptyMetrics: true }) + ); try { - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); } catch (e) { - expect((e).message).toBe('The number of metrics recorded must be higher than zero'); + expect((e).message).toBe( + 'The number of metrics recorded must be higher than zero' + ); } }); test('should not throw on empty metrics if set to false', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { console.log('do nothing'); }; - const handler = middy(lambdaHandler).use(logMetrics(metrics, { throwOnEmptyMetrics: false })); + const handler = middy(lambdaHandler).use( + logMetrics(metrics, { throwOnEmptyMetrics: false }) + ); try { - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); } catch (e) { fail(`Should not throw but got the following Error: ${e}`); } }); test('should not throw on empty metrics if not set, but should log a warning', async () => { - // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = async (): Promise => { console.log('do nothing'); }; const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act & Assess - await expect(handler(dummyEvent, dummyContext)).resolves.not.toThrowError(); + await expect( + handler(dummyEvent, dummyContext) + ).resolves.not.toThrowError(); expect(consoleWarnSpy).toBeCalledTimes(1); expect(consoleWarnSpy).toBeCalledWith( - 'No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using \'throwOnEmptyMetrics\'', + 'No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using `throwOnEmptyMetrics`' ); - }); }); describe('captureColdStartMetric', () => { - test('should capture cold start metric if set to true', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { console.log('{"message": "do nothing"}'); }; - const handler = middy(lambdaHandler).use(logMetrics(metrics, { captureColdStartMetric: true })); + const handler = middy(lambdaHandler).use( + logMetrics(metrics, { captureColdStartMetric: true }) + ); - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked! again')); - const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked! again') + ); + const loggedData = [ + JSON.parse(consoleSpy.mock.calls[0][0]), + JSON.parse(consoleSpy.mock.calls[1][0]), + ]; expect(console.log).toBeCalledTimes(5); expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); - expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics[0].Name).toBe('ColdStart'); - expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics[0].Unit).toBe('Count'); + expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics[0].Name).toBe( + 'ColdStart' + ); + expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics[0].Unit).toBe( + 'Count' + ); expect(loggedData[0].ColdStart).toBe(1); }); test('should not capture cold start metrics if set to false', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { console.log('{"message": "do nothing"}'); }; - const handler = middy(lambdaHandler).use(logMetrics(metrics, { captureColdStartMetric: false })); + const handler = middy(lambdaHandler).use( + logMetrics(metrics, { captureColdStartMetric: false }) + ); - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked! again')); - const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked! again') + ); + const loggedData = [ + JSON.parse(consoleSpy.mock.calls[0][0]), + JSON.parse(consoleSpy.mock.calls[1][0]), + ]; expect(loggedData[0]._aws).toBe(undefined); }); test('should not throw on empty metrics if not set', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { console.log('{"message": "do nothing"}'); @@ -149,19 +197,28 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked! again')); - const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked! again') + ); + const loggedData = [ + JSON.parse(consoleSpy.mock.calls[0][0]), + JSON.parse(consoleSpy.mock.calls[1][0]), + ]; expect(loggedData[0]._aws).toBe(undefined); }); }); describe('logMetrics', () => { - test('when a metrics instance receive multiple metrics with the same name, it prints multiple values in an array format', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 2); @@ -171,7 +228,9 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -188,14 +247,17 @@ describe('Middy middleware', () => { ], }, service: 'orders', - successfulBooking: [ 2, 1 ], + successfulBooking: [2, 1], }) ); }); 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', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); @@ -205,10 +267,14 @@ describe('Middy middleware', () => { defaultDimensions: { environment: 'prod', aws_region: 'eu-west-1' }, captureColdStartMetric: true, }; - const handler = middy(lambdaHandler).use(logMetrics(metrics, metricsOptions)); + const handler = middy(lambdaHandler).use( + logMetrics(metrics, metricsOptions) + ); // Act - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -219,7 +285,9 @@ describe('Middy middleware', () => { CloudWatchMetrics: [ { Namespace: 'serverlessAirline', - Dimensions: [[ 'service', 'environment', 'aws_region', 'function_name' ]], + Dimensions: [ + ['service', 'environment', 'aws_region', 'function_name'], + ], Metrics: [{ Name: 'ColdStart', Unit: 'Count' }], }, ], @@ -239,7 +307,7 @@ describe('Middy middleware', () => { CloudWatchMetrics: [ { Namespace: 'serverlessAirline', - Dimensions: [[ 'service', 'environment', 'aws_region' ]], + Dimensions: [['service', 'environment', 'aws_region']], Metrics: [{ Name: 'successfulBooking', Unit: 'Count' }], }, ], @@ -254,7 +322,10 @@ 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', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); @@ -263,7 +334,9 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -287,7 +360,10 @@ 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', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); @@ -295,10 +371,14 @@ describe('Middy middleware', () => { const metricsOptions: ExtraOptions = { throwOnEmptyMetrics: true, }; - const handler = middy(lambdaHandler).use(logMetrics([metrics], metricsOptions)); + const handler = middy(lambdaHandler).use( + logMetrics([metrics], metricsOptions) + ); // Act - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -321,19 +401,28 @@ describe('Middy middleware', () => { }); }); describe('Metrics resolution', () => { - test('serialized metrics in EMF format should not contain `StorageResolution` as key if `60` is set', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { - metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.Standard); + metrics.addMetric( + 'successfulBooking', + MetricUnits.Count, + 1, + MetricResolution.Standard + ); }; const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); // Assess expect(console.log).toHaveBeenCalledWith( @@ -344,10 +433,12 @@ describe('Middy middleware', () => { { Namespace: 'serverlessAirline', Dimensions: [['service']], - Metrics: [{ - Name: 'successfulBooking', - Unit: 'Count', - }], + Metrics: [ + { + Name: 'successfulBooking', + Unit: 'Count', + }, + ], }, ], }, @@ -359,16 +450,26 @@ describe('Middy middleware', () => { test('Should be StorageResolution `1` if MetricResolution is set to `High`', async () => { // Prepare - const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + const metrics = new Metrics({ + namespace: 'serverlessAirline', + serviceName: 'orders', + }); const lambdaHandler = (): void => { - metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High); + metrics.addMetric( + 'successfulBooking', + MetricUnits.Count, + 1, + MetricResolution.High + ); }; const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act - await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => + console.log('Lambda invoked!') + ); // Assess expect(console.log).toHaveBeenCalledWith( @@ -379,11 +480,13 @@ describe('Middy middleware', () => { { Namespace: 'serverlessAirline', Dimensions: [['service']], - Metrics: [{ - Name: 'successfulBooking', - Unit: 'Count', - StorageResolution: 1 - }], + Metrics: [ + { + Name: 'successfulBooking', + Unit: 'Count', + StorageResolution: 1, + }, + ], }, ], },