diff --git a/docs/core/tracer.md b/docs/core/tracer.md index a63095fbcb..ec90e4977a 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -75,7 +75,7 @@ The `Tracer` utility is instantiated outside of the Lambda handler. In doing thi // You can also pass the parameter in the constructor // const tracer = new Tracer({ - // serviceName: "serverlessAirline" + // serviceName: 'serverlessAirline' // }); ``` @@ -510,7 +510,7 @@ Tracer exposes a `getRootXrayTraceId()` method that allows you to retrieve the [ return { statusCode: 500, body: `Internal Error - Please contact support and quote the following id: ${rootTraceId}`, - headers: { "_X_AMZN_TRACE_ID": rootTraceId }, + headers: { '_X_AMZN_TRACE_ID': rootTraceId }, }; } }; diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index be9ee86253..92f0d9ee07 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -39,28 +39,12 @@ import type { * * @example * ```typescript - * import { Logger } from "@aws-lambda-powertools/logger"; + * import { Logger } from '@aws-lambda-powertools/logger'; * * // Logger parameters fetched from the environment variables: * const logger = new Logger(); * ``` - * - * ### Functions usage with manual instrumentation - * - * If you prefer to manually instrument your Lambda handler you can use the methods in the Logger class directly. - * - * @example - * ```typescript - * import { Logger } from "@aws-lambda-powertools/logger"; - * - * const logger = new Logger(); - * - * export const handler = async (_event, context) => { - * logger.addContext(context); - * logger.info("This is an INFO log with some context"); - * }; - * ``` - * + * * ### Functions usage with middleware * * If you use function-based Lambda handlers you can use the [injectLambdaContext()](#injectLambdaContext) @@ -68,13 +52,13 @@ import type { * * @example * ```typescript - * import { Logger, injectLambdaContext } from "@aws-lambda-powertools/logger"; + * import { Logger, injectLambdaContext } from '@aws-lambda-powertools/logger'; * import middy from '@middy/core'; * * const logger = new Logger(); * * const lambdaHandler = async (_event: any, _context: any) => { - * logger.info("This is an INFO log with some context"); + * logger.info('This is an INFO log with some context'); * }; * * export const handler = middy(lambdaHandler).use(injectLambdaContext(logger)); @@ -86,22 +70,41 @@ import type { * * @example * ```typescript - * import { Logger } from "@aws-lambda-powertools/logger"; + * import { Logger } from '@aws-lambda-powertools/logger'; * import { LambdaInterface } from '@aws-lambda-powertools/commons'; * * const logger = new Logger(); * * class Lambda implements LambdaInterface { + * + * // FYI: Decorator might not render properly in VSCode mouse over due to https://github.com/microsoft/TypeScript/issues/47679 and might show as *@logger* instead of `@logger.injectLambdaContext` + * * // Decorate your handler class method * @logger.injectLambdaContext() * public async handler(_event: any, _context: any): Promise { - * logger.info("This is an INFO log with some context"); + * logger.info('This is an INFO log with some context'); * } * } * * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` + * + * ### Functions usage with manual instrumentation + * + * If you prefer to manually instrument your Lambda handler you can use the methods in the Logger class directly. + * + * @example + * ```typescript + * import { Logger } from '@aws-lambda-powertools/logger'; + * + * const logger = new Logger(); + * + * export const handler = async (_event, context) => { + * logger.addContext(context); + * logger.info('This is an INFO log with some context'); + * }; + * ``` * * @class * @implements {ClassThatLogs} @@ -269,9 +272,32 @@ class Logger extends Utility implements ClassThatLogs { /** * Method decorator that adds the current Lambda function context as extra * information in all log items. + * * The decorator can be used only when attached to a Lambda function handler which * is written as method of a class, and should be declared just before the handler declaration. * + * Note: Currently TypeScript only supports decorators on classes and methods. If you are using the + * function syntax, you should use the middleware instead. + * + * @example + * ```typescript + * import { Logger } from '@aws-lambda-powertools/logger'; + * import { LambdaInterface } from '@aws-lambda-powertools/commons'; + * + * const logger = new Logger(); + * + * class Lambda implements LambdaInterface { + * // Decorate your handler class method + * @logger.injectLambdaContext() + * public async handler(_event: any, _context: any): Promise { + * logger.info('This is an INFO log with some context'); + * } + * } + * + * const handlerClass = new Lambda(); + * export const handler = handlerClass.handler.bind(handlerClass); + * ``` + * * @see https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators * @returns {HandlerMethodDecorator} */ diff --git a/packages/logger/src/middleware/middy.ts b/packages/logger/src/middleware/middy.ts index 85466c5de1..01f5409fc2 100644 --- a/packages/logger/src/middleware/middy.ts +++ b/packages/logger/src/middleware/middy.ts @@ -3,29 +3,28 @@ import type middy from '@middy/core'; import { HandlerOptions, LogAttributes } from '../types'; /** - * A middy middleware that adds the current Lambda invocation's context inside all log items. - * - * ## Usage + * A middy middleware that helps emitting CloudWatch EMF metrics in your logs. * + * Using this middleware on your handler function will automatically add context information to logs, as well as optionally log the event and clear attributes set during the invocation. + * * @example * ```typescript - * import { Logger, injectLambdaContext } from "@aws-lambda-powertools/logger"; - * + * import { Logger, injectLambdaContext } from '@aws-lambda-powertools/logger'; * import middy from '@middy/core'; * * * const logger = new Logger(); * * const lambdaHandler = async (_event: any, _context: any) => { - * logger.info("This is an INFO log with some context"); + * logger.info('This is an INFO log with some context'); * }; * - * * export const handler = middy(lambdaHandler).use(injectLambdaContext(logger)); * ``` * - * @param {Logger|Logger[]} target - The Tracer instance to use for tracing - * @returns {middy.MiddlewareObj} - The middy middleware object + * @param target - The Logger instance(s) to use for logging + * @param options - (_optional_) Options for the middleware + * @returns - The middy middleware object */ const injectLambdaContext = (target: Logger | Logger[], options?: HandlerOptions): middy.MiddlewareObj => { diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 48ba88cd78..f8e2571fe3 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -30,11 +30,33 @@ const DEFAULT_NAMESPACE = 'default_namespace'; * * 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)); + * ``` * * ### Object oriented way with decorator * * If you are used to TypeScript Class usage to encapsulate your Lambda handler you can leverage the [@metrics.logMetrics()](./_aws_lambda_powertools_metrics.Metrics.html#logMetrics) decorator to automatically: - * * create cold start metric + * * capture a `ColdStart` metric * * flush buffered metrics * * throw on empty metrics * @@ -42,23 +64,23 @@ const DEFAULT_NAMESPACE = 'default_namespace'; * * ```typescript * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; - * import { Callback, Context } from 'aws-lambda'; + * import { LambdaInterface } from '@aws-lambda-powertools/commons'; * - * const metrics = new Metrics({ namespace:'MyService', serviceName:'withDecorator' }); + * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); * - * export class MyFunctionWithDecorator { + * class Lambda implements LambdaInterface { * - * // FYI: Decorator might not render properly in VSCode mouse over due to https://github.com/microsoft/TypeScript/issues/39371 and might show as *@metrics* instead of `@metrics.logMetrics` + * // FYI: Decorator might not render properly in VSCode mouse over due to https://github.com/microsoft/TypeScript/issues/47679 and might show as *@metrics* instead of `@metrics.logMetrics` * * @metrics.logMetrics({ captureColdStartMetric: true, throwOnEmptyMetrics: true }) - * public handler(_event: any, _context: Context, _callback: Callback): void | Promise { - * // ... - * metrics.addMetric('test-metric', MetricUnits.Count, 10); - * // ... + * public handler(_event: any, _context: any): Promise { + * // ... + * metrics.addMetric('test-metric', MetricUnits.Count, 10); + * // ... * } * } * - * const handlerClass = new MyFunctionWithDecorator(); + * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` * @@ -71,7 +93,7 @@ const DEFAULT_NAMESPACE = 'default_namespace'; * ```typescript * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; * - * const metrics = new Metrics({ namespace: 'MyService', serviceName: 'MyFunction' }); + * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); * * export const handler = async (_event: any, _context: any): Promise => { * metrics.captureColdStartMetric(); @@ -164,11 +186,10 @@ class Metrics extends Utility implements MetricsInterface { * * ```typescript * import { Metrics } from '@aws-lambda-powertools/metrics'; - * import { Context } from 'aws-lambda'; * - * const metrics = new Metrics({ namespace:'serverlessAirline', serviceName:'orders' }); + * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); * - * export const handler = async (event: any, context: Context): Promise => { + * export const handler = async (event: any, _context: any): Promise => { * metrics.captureColdStartMetric(); * }; * ``` @@ -209,19 +230,19 @@ class Metrics extends Utility implements MetricsInterface { * * ```typescript * import { Metrics } from '@aws-lambda-powertools/metrics'; - * import { Callback, Context } from 'aws-lambda'; + * import { LambdaInterface } from '@aws-lambda-powertools/commons'; * - * const metrics = new Metrics({ namespace:'CdkExample', serviceName:'withDecorator' }); + * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); * - * export class MyFunctionWithDecorator { + * class Lambda implements LambdaInterface { * * @metrics.logMetrics({ captureColdStartMetric: true }) - * public handler(_event: any, _context: Context, _callback: Callback): void | Promise { + * public handler(_event: any, _context: any): Promise { * // ... * } * } * - * const handlerClass = new MyFunctionWithDecorator(); + * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` * @@ -276,7 +297,7 @@ class Metrics extends Utility implements MetricsInterface { * ```typescript * import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; * - * const metrics = new Metrics({ namespace: 'CdkExample', serviceName: 'MyFunction' }); // Sets metric namespace, and service as a metric dimension + * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); // Sets metric namespace, and service as a metric dimension * * export const handler = async (_event: any, _context: any): Promise => { * metrics.addMetric('test-metric', MetricUnits.Count, 10); @@ -383,11 +404,10 @@ class Metrics extends Utility implements MetricsInterface { * * ```typescript * import { Metrics } from '@aws-lambda-powertools/metrics'; - * import { Context } from 'aws-lambda'; * - * const metrics = new Metrics({ namespace:'serverlessAirline', serviceName:'orders' }); + * const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName:'orders' }); * - * export const handler = async (event: any, context: Context): Promise => { + * export const handler = async (_event: any, _context: any): Promise => { * metrics.throwOnEmptyMetrics(); * metrics.publishStoredMetrics(); // will throw since no metrics added. * }; diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index 2b1bafb1e3..8f48824552 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -2,6 +2,33 @@ import type { Metrics } from '../Metrics'; import type middy from '@middy/core'; import type { ExtraOptions } from '../types'; +/** + * 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 = {}): middy.MiddlewareObj => { const metricsInstances = target instanceof Array ? target : [target]; diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 09ac5e3314..072e237e4c 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -38,9 +38,11 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * - * export const handler = middy(async (_event: any, _context: any) => { +* const lambdaHandler = async (_event: any, _context: any) => { * ... - * }).use(captureLambdaHandler(tracer)); + * }; + * + * export const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); * ``` * * ### Object oriented usage with decorators @@ -54,12 +56,13 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * @example * ```typescript * import { Tracer } from '@aws-lambda-powertools/tracer'; + * import { LambdaInterface } from '@aws-lambda-powertools/commons'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * - * // FYI: Decorator might not render properly in VSCode mouse over due to https://github.com/microsoft/TypeScript/issues/39371 and might show as *@tracer* instead of `@tracer.captureLambdaHandler` + * // FYI: Decorator might not render properly in VSCode mouse over due to https://github.com/microsoft/TypeScript/issues/47679 and might show as *@tracer* instead of `@tracer.captureLambdaHandler` * - * class Lambda { + * class Lambda implements LambdaInterface { * @tracer.captureLambdaHandler() * public handler(event: any, context: any) { * ... @@ -77,7 +80,6 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * @example * ```typescript * import { Tracer } from '@aws-lambda-powertools/tracer'; - * import { Segment } from 'aws-xray-sdk-core'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * @@ -93,7 +95,7 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * * let res; * try { - * res = ... + * // ... your own logic goes here * // Add the response as metadata * tracer.addResponseAsMetadata(res, process.env._HANDLER); * } catch (err) { @@ -245,11 +247,11 @@ class Tracer extends Utility implements TracerInterface { * * @example * ```typescript - * import { S3 } from "aws-sdk"; + * import { S3 } from 'aws-sdk'; * import { Tracer } from '@aws-lambda-powertools/tracer'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); - * const s3 = tracer.captureAWSClient(new S3({ apiVersion: "2006-03-01" })); + * const s3 = tracer.captureAWSClient(new S3({ apiVersion: '2006-03-01' })); * * export const handler = async (_event: any, _context: any) => { * ... @@ -287,7 +289,7 @@ class Tracer extends Utility implements TracerInterface { * * @example * ```typescript - * import { S3Client } from "@aws-sdk/client-s3"; + * import { S3Client } from '@aws-sdk/client-s3'; * import { Tracer } from '@aws-lambda-powertools/tracer'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); @@ -323,10 +325,11 @@ class Tracer extends Utility implements TracerInterface { * @example * ```typescript * import { Tracer } from '@aws-lambda-powertools/tracer'; + * import { LambdaInterface } from '@aws-lambda-powertools/commons'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * - * class Lambda { + * class Lambda implements LambdaInterface { * @tracer.captureLambdaHandler() * public handler(event: any, context: any) { * ... @@ -400,10 +403,11 @@ class Tracer extends Utility implements TracerInterface { * @example * ```typescript * import { Tracer } from '@aws-lambda-powertools/tracer'; + * import { LambdaInterface } from '@aws-lambda-powertools/commons'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * - * class Lambda { + * class Lambda implements LambdaInterface { * @tracer.captureMethod() * public myMethod(param: any) { * ... @@ -477,7 +481,6 @@ class Tracer extends Utility implements TracerInterface { * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * * export const handler = async () => { - * * try { * ... * } catch (err) { @@ -489,7 +492,7 @@ class Tracer extends Utility implements TracerInterface { * // Include the rootTraceId in the response so we can show a "contact support" button that * // takes the customer to a customer service form with the trace as additional context. * body: `Internal Error - Please contact support and quote the following id: ${rootTraceId}`, - * headers: { "_X_AMZN_TRACE_ID": rootTraceId }, + * headers: { '_X_AMZN_TRACE_ID': rootTraceId }, * }; * } * } @@ -623,7 +626,7 @@ class Tracer extends Utility implements TracerInterface { * @example * ```typescript * import { Tracer } from '@aws-lambda-powertools/tracer'; - * import { Segment } from 'aws-xray-sdk-core'; + * import { Subsegment } from 'aws-xray-sdk-core'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index b6fc75b708..7d4bcffb77 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -19,14 +19,16 @@ import type { CaptureLambdaHandlerOptions } from '../types'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * - * export const handler = middy(async (_event: any, _context: any) => { + * const lambdaHandler = async (_event: any, _context: any) => { * ... - * }).use(captureLambdaHandler(tracer)); + * }; + * + * export const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); * ``` * * @param target - The Tracer instance to use for tracing * @param options - (_optional_) Options for the middleware - * @returns middleware object - The middy middleware object + * @returns middleware - The middy middleware object */ const captureLambdaHandler = (target: Tracer, options?: CaptureLambdaHandlerOptions): middy.MiddlewareObj => { let lambdaSegment: Subsegment | Segment;