From 7fd3d6ef63d38a3a4ed070c714c3eaed400dd458 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 8 Dec 2021 16:16:34 +0100 Subject: [PATCH 1/6] Updated Tracer docs --- docs/core/tracer.md | 357 ++++++++++++++------------------------------ 1 file changed, 108 insertions(+), 249 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 424f54caf4..3f62227289 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -38,25 +38,29 @@ Before your use this utility, your AWS Lambda function [must have permissions](h ### Lambda handler -You can quickly start by importing the `Tracer` class, initialize it outside the Lambda handler, and use `capture_lambda_handler` decorator. +You can quickly start by importing the `Tracer` class, initialize it outside the Lambda handler, and use `captureLambdaHanlder` decorator. === "index.ts" - ```typescript hl_lines="1 3 6" - import { Tracer } from '@aws-lambda-powertools/tracer'; + ```typescript hl_lines="1 3 8" + import { Tracer } from '@aws-lambda-powertools/tracer'; + + const tracer = Tracer(); // Sets service via env var + // OR tracer = Tracer({ service: 'example' }); + + class Lambda { - const tracer = Tracer(); // Sets service via env var - // OR tracer = Tracer({ service: 'example' }); + @tracer.captureLambdaHanlder() + public handler(_event: TEvent, _context: Context, _callback: Callback): void { + const chargeId = event.chargeId; + const payment = collectPayment(chargeId); + ... + } - @tracer.capture_lambda_handler - const handler = (event, context) => { - const chargeId = event.chargeId; - const payment = collectPayment(chargeId); - ... - } - ``` + } + ``` -When using this `capture_lambda_handler` decorator, Tracer performs these additional tasks to ease operations: +When using this `captureLambdaHanlder` decorator, Tracer performs these additional tasks to ease operations: * Creates a `ColdStart` annotation to easily filter traces that have had an initialization overhead * Captures any response, or full exceptions generated by the handler, and include as tracing metadata @@ -70,146 +74,122 @@ When using this `capture_lambda_handler` decorator, Tracer performs these additi === "Annotations" You can add annotations using `putAnnotation` method. - ```typescript hl_lines="7" - import { Tracer } from '@aws-lambda-powertools/tracer'; + ```typescript hl_lines="10" + import { Tracer } from '@aws-lambda-powertools/tracer'; - const tracer = Tracer() + const tracer = new Tracer() - @tracer.capture_lambda_handler - const handler = (event, context) => { - ... - tracer.addAnnotation('payment_response', 'SUCCESS'); - } + class Lambda { + + @tracer.captureLambdaHanlder() + public handler(_event: TEvent, _context: Context, _callback: Callback): void { + ... + tracer.putAnnotation('PaymentStatus', "SUCCESS"); + } + + } ``` === "Metadata" - You can add metadata using `putMetadata` method. + You can add metadata using `putMetadata` method. - ```typescript hl_lines="9" + ```typescript hl_lines="11" import { Tracer } from '@aws-lambda-powertools/tracer'; - const tracer = Tracer() - - @tracer.capture_lambda_handler - const handler = (event, context) => { - ... - const res = someLogic(); - tracer.putMetadata('payment_response', res); - } - ``` + const tracer = new Tracer() -[//]:# (START EDITING FROM HERE DOWN) + class Lambda { -### Synchronous functions + @tracer.captureLambdaHanlder() + public handler(_event: TEvent, _context: Context, _callback: Callback): void { + ... + const res = someLogic() + tracer.putAnnotation('PaymentResponse', res); + } -You can trace synchronous functions using the `capture_method` decorator. + } + ``` -!!! warning - **When `capture_response` is enabled, the function response will be read and serialized as json.** +### Methods - The serialization is performed by the aws-xray-sdk which uses the `jsonpickle` module. This can cause - unintended consequences if there are side effects to recursively reading the returned value, for example if the - decorated function response contains a file-like object or a `StreamingBody` for S3 objects. +You can trace other methods using the `captureMethod` decorator. - ```python hl_lines="7 13" - @tracer.capture_method - def collect_payment(charge_id): - ret = requests.post(PAYMENT_ENDPOINT) # logic - tracer.put_annotation("PAYMENT_STATUS", "SUCCESS") # custom annotation - return ret - ``` - -### Asynchronous and generator functions +=== "index.ts" -!!! warning - **We do not support async Lambda handler** - Lambda handler itself must be synchronous + ```typescript hl_lines="8" + import { Tracer } from '@aws-lambda-powertools/tracer'; -You can trace asynchronous functions and generator functions (including context managers) using `capture_method`. + const tracer = new Tracer(); // Sets service via env var + // OR tracer = new Tracer({ service: 'example' }); -=== "Async" + class Lambda { - ```python hl_lines="7" - import asyncio - import contextlib - from aws_lambda_powertools import Tracer + @tracer.captureMethod() + public getChargeId(): string { + ... + return 'foo bar' + } - tracer = Tracer() + @tracer.captureLambdaHanlder() + public handler(_event: TEvent, _context: Context, _callback: Callback): void { + const chargeId = this.getChargeId(); + const payment = collectPayment(chargeId); + ... + } - @tracer.capture_method - async def collect_payment(): - ... + } ``` -=== "Context manager" +## Advanced - ```python hl_lines="7-8" - import asyncio - import contextlib - from aws_lambda_powertools import Tracer +### Patching AWS SDK clients - tracer = Tracer() +Tracer can patch [AWS SDK clients](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-awssdkclients.html) and create traces when your application makes calls to AWS services. - @contextlib.contextmanager - @tracer.capture_method - def collect_payment_ctxman(): - yield result - ... - ``` +!!! info + The following snippet assumes you are using [**AWS SDK v3** for JavaScript](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/) -=== "Generators" +You can patch any AWS SDK clients by calling `captureAWSv3Client` method: - ```python hl_lines="9" - import asyncio - import contextlib - from aws_lambda_powertools import Tracer +=== "index.ts" - tracer = Tracer() + ```typescript hl_lines="8" + import { S3Client } from "@aws-sdk/client-s3"; + import { Tracer } from '@aws-lambda-powertools/tracer'; - @tracer.capture_method - def collect_payment_gen(): - yield result - ... + const tracer = new Tracer(); + const client = new S3Client({}); + tracer.captureAWSv3Client(client); ``` -The decorator will detect whether your function is asynchronous, a generator, or a context manager and adapt its behaviour accordingly. +!!! info + The following two snippets assume you are using [**AWS SDK v2** for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/welcome.html) -=== "app.py" +You can patch all AWS SDK clients by calling `captureAWS` method: - ```python - @tracer.capture_lambda_handler - def handler(evt, ctx): - asyncio.run(collect_payment()) +=== "index.ts" - with collect_payment_ctxman as result: - do_something_with(result) + ```typescript hl_lines="8" + import { Tracer } from '@aws-lambda-powertools/tracer'; - another_result = list(collect_payment_gen()) + const tracer = new Tracer(); + const AWS = tracer.captureAWS(require('aws-sdk')); ``` -## Advanced - -### Patching modules - -Tracer automatically patches all [supported libraries by X-Ray](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-patching.html) during initialization, by default. Underneath, AWS X-Ray SDK checks whether a supported library has been imported before patching. - -If you're looking to shave a few microseconds, or milliseconds depending on your function memory configuration, you can patch specific modules using `patch_modules` param: +If you're looking to shave a few microseconds, or milliseconds depending on your function memory configuration, you can patch specific clients using `captureAWSClient`: -=== "app.py" - - ```python hl_lines="7" - import boto3 - import requests +=== "index.ts" - from aws_lambda_powertools import Tracer + ```typescript hl_lines="8" + import { S3 } from "aws-sdk"; + import { Tracer } from '@aws-lambda-powertools/tracer'; - modules_to_be_patched = ["boto3", "requests"] - tracer = Tracer(patch_modules=modules_to_be_patched) + const tracer = new Tracer(); + const s3 = tracer.captureAWSClient(new S3({ apiVersion: "2006-03-01" })); ``` ### Disabling response auto-capture -> New in 1.9.0 - -Use **`capture_response=False`** parameter in both `capture_lambda_handler` and `capture_method` decorators to instruct Tracer **not** to serialize function responses as metadata. +Use **`POWERTOOLS_TRACER_CAPTURE_RESPONSE=false`** environment variable to instruct Tracer **not** to serialize function responses as metadata. !!! info "This is commonly useful in three scenarios" @@ -217,159 +197,38 @@ Use **`capture_response=False`** parameter in both `capture_lambda_handler` and 2. You might manipulate **streaming objects that can be read only once**; this prevents subsequent calls from being empty 3. You might return **more than 64K** of data _e.g., `message too long` error_ -=== "sensitive_data_scenario.py" - - ```python hl_lines="3 7" - from aws_lambda_powertools import Tracer - - @tracer.capture_method(capture_response=False) - def fetch_sensitive_information(): - return "sensitive_information" - - @tracer.capture_lambda_handler(capture_response=False) - def handler(event, context): - sensitive_information = fetch_sensitive_information() - ``` -=== "streaming_object_scenario.py" - - ```python hl_lines="3" - from aws_lambda_powertools import Tracer - - @tracer.capture_method(capture_response=False) - def get_s3_object(bucket_name, object_key): - s3 = boto3.client("s3") - s3_object = get_object(Bucket=bucket_name, Key=object_key) - return s3_object - ``` - ### Disabling exception auto-capture -> New in 1.10.0 - -Use **`capture_error=False`** parameter in both `capture_lambda_handler` and `capture_method` decorators to instruct Tracer **not** to serialize exceptions as metadata. +Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct Tracer **not** to serialize exceptions as metadata. !!! info "Commonly useful in one scenario" 1. You might **return sensitive** information from exceptions, stack traces you might not control -=== "sensitive_data_exception.py" - - ```python hl_lines="3 5" - from aws_lambda_powertools import Tracer - - @tracer.capture_lambda_handler(capture_error=False) - def handler(event, context): - raise ValueError("some sensitive info in the stack trace...") - ``` - -### Tracing aiohttp requests - -!!! info - This snippet assumes you have **aiohttp** as a dependency - -You can use `aiohttp_trace_config` function to create a valid [aiohttp trace_config object](https://docs.aiohttp.org/en/stable/tracing_reference.html). This is necessary since X-Ray utilizes aiohttp trace hooks to capture requests end-to-end. - -=== "aiohttp_example.py" - - ```python hl_lines="5 10" - import asyncio - import aiohttp - - from aws_lambda_powertools import Tracer - from aws_lambda_powertools.tracing import aiohttp_trace_config - - tracer = Tracer() - - async def aiohttp_task(): - async with aiohttp.ClientSession(trace_configs=[aiohttp_trace_config()]) as session: - async with session.get("https://httpbin.org/json") as resp: - resp = await resp.json() - return resp - ``` - ### Escape hatch mechanism -You can use `tracer.provider` attribute to access all methods provided by AWS X-Ray `xray_recorder` object. - -This is useful when you need a feature available in X-Ray that is not available in the Tracer utility, for example [thread-safe](https://github.com/aws/aws-xray-sdk-python/#user-content-trace-threadpoolexecutor), or [context managers](https://github.com/aws/aws-xray-sdk-python/#user-content-start-a-custom-segmentsubsegment). - -=== "escape_hatch_context_manager_example.py" - - ```python hl_lines="7" - from aws_lambda_powertools import Tracer +You can use `tracer.provider` attribute to access all methods provided by the [AWS X-Ray SDK](https://docs.aws.amazon.com/xray-sdk-for-nodejs/latest/reference/AWSXRay.html). - tracer = Tracer() +This is useful when you need a feature available in X-Ray that is not available in the Tracer utility, for example [SQL queries tracing](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-sqlclients.html), or [a custom logger](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-configuration.html#xray-sdk-nodejs-configuration-logging). - @tracer.capture_lambda_handler - def handler(event, context): - with tracer.provider.in_subsegment('## custom subsegment') as subsegment: - ret = some_work() - subsegment.put_metadata('response', ret) - ``` - -### Concurrent asynchronous functions - -!!! warning - [As of now, X-Ray SDK will raise an exception when async functions are run and traced concurrently](https://github.com/aws/aws-xray-sdk-python/issues/164) - -A safe workaround mechanism is to use `in_subsegment_async` available via Tracer escape hatch (`tracer.provider`). +=== "index.ts" -=== "concurrent_async_workaround.py" + ```typescript hl_lines="6" + import { Logger } from '@aws-lambda-powertools/logger'; + import { Tracer } from '@aws-lambda-powertools/tracer'; - ```python hl_lines="6 7 12 15 17" - import asyncio + const logger = new Logger(); + const tracer = new Tracer() + tracer.provider.setLogger(logger) - from aws_lambda_powertools import Tracer - tracer = Tracer() + class Lambda { - async def another_async_task(): - async with tracer.provider.in_subsegment_async("## another_async_task") as subsegment: - subsegment.put_annotation(key="key", value="value") - subsegment.put_metadata(key="key", value="value", namespace="namespace") + @tracer.captureLambdaHanlder() + public handler(_event: TEvent, _context: Context, _callback: Callback): void { ... + } - async def another_async_task_2(): - ... - - @tracer.capture_method - async def collect_payment(charge_id): - asyncio.gather(another_async_task(), another_async_task_2()) - ... - ``` - -### Reusing Tracer across your code - -Tracer keeps a copy of its configuration after the first initialization. This is useful for scenarios where you want to use Tracer in more than one location across your code base. - -!!! warning - When reusing Tracer in Lambda Layers, or in multiple modules, **do not set `auto_patch=False`**, because import order matters. - - This can result in the first Tracer config being inherited by new instances, and their modules not being patched. - -=== "handler.py" - - ```python hl_lines="2 4 9" - from aws_lambda_powertools import Tracer - from payment import collect_payment - - tracer = Tracer(service="payment") - - @tracer.capture_lambda_handler - def handler(event, context): - charge_id = event.get('charge_id') - payment = collect_payment(charge_id) - ``` -=== "payment.py" - A new instance of Tracer will be created but will reuse the previous Tracer instance configuration, similar to a Singleton. - - ```python hl_lines="3 5" - from aws_lambda_powertools import Tracer - - tracer = Tracer(service="payment") - - @tracer.capture_method - def collect_payment(charge_id: str): - ... + } ``` ## Testing your code @@ -378,6 +237,6 @@ Tracer is disabled by default when not running in the AWS Lambda environment - T ## Tips -* Use annotations on key operations to slice and dice traces, create unique views, and create metrics from it via Trace Groups -* Use a namespace when adding metadata to group data more easily -* Annotations and metadata are added to the current subsegment opened. If you want them in a specific subsegment, use a [context manager](https://github.com/aws/aws-xray-sdk-python/#start-a-custom-segmentsubsegment) via the escape hatch mechanism +- Use annotations on key operations to slice and dice traces, create unique views, and create metrics from it via Trace Groups +- Use a namespace when adding metadata to group data more easily +- Annotations and metadata are added to the current subsegment opened. If you want them in a specific subsegment, [create one](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-subsegments.html#xray-sdk-nodejs-subsegments-lambda) via the escape hatch mechanism From 09ed34969d65fdcd7ec27120918702cdde0c4c2f Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 10 Dec 2021 13:34:07 +0100 Subject: [PATCH 2/6] Fixed SDK name --- docs/core/tracer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 3f62227289..76c7387ff4 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -3,7 +3,7 @@ title: Tracer description: Core utility --- -Tracer is an opinionated thin wrapper for [AWS X-Ray Python SDK](https://github.com/aws/aws-xray-sdk-python/). +Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://github.com/aws/aws-xray-sdk-node). ![Tracer showcase](../media/tracer_utility_showcase.png) From 6f8229ba0b8cfac8ac8e67fb881f3af17aeed466 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 14 Dec 2021 19:30:37 +0100 Subject: [PATCH 3/6] Update config to prep for JSDoc --- packages/tracing/package.json | 4 ++-- packages/tracing/tsconfig.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/tracing/package.json b/packages/tracing/package.json index 70e22f248c..582d1777dd 100644 --- a/packages/tracing/package.json +++ b/packages/tracing/package.json @@ -26,8 +26,8 @@ }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/tracer#readme", "license": "MIT", - "main": "./lib/index.js", - "types": "./lib/index.d.ts", + "main": "./lib/packages/tracing/src/index.js", + "types": "./lib/packages/tracing/src/index.d.ts", "devDependencies": { "@types/aws-lambda": "^8.10.72", "@types/jest": "^27.0.0", diff --git a/packages/tracing/tsconfig.json b/packages/tracing/tsconfig.json index 30559ec1d7..d28abaa6d5 100644 --- a/packages/tracing/tsconfig.json +++ b/packages/tracing/tsconfig.json @@ -7,6 +7,7 @@ "declaration": true, "declarationMap": true, "outDir": "lib", + "removeComments": false, "strict": true, "inlineSourceMap": true, "moduleResolution": "node", From 60d3c27b3d2534c59e2c247b149aba8907a75674 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 14 Dec 2021 19:31:10 +0100 Subject: [PATCH 4/6] WIP: JDoc annotation --- packages/tracing/src/Tracer.ts | 232 +++++++++++++++++++++++++++++++ packages/tracing/types/Tracer.ts | 16 +++ 2 files changed, 248 insertions(+) diff --git a/packages/tracing/src/Tracer.ts b/packages/tracing/src/Tracer.ts index ae1bb434bf..397cf15d5a 100644 --- a/packages/tracing/src/Tracer.ts +++ b/packages/tracing/src/Tracer.ts @@ -27,18 +27,76 @@ class Tracer implements TracerInterface { this.provider = new ProviderService(); } + /** + * Patch all AWS SDK v2 clients and create traces when your application makes calls to AWS services. + * + * If you want to patch a specific client use {@link captureAWSClient} and if you are using AWS SDK v3 use {@link captureAWSv3Client} instead. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-awssdkclients.html + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * const AWS = tracer.captureAWS(require('aws-sdk')); + * ``` + * + * @param aws - AWS SDK v2 import + * @returns AWS - Instrumented AWS SDK + */ public captureAWS(aws: T): T { if (this.tracingEnabled === false) return aws; return this.provider.captureAWS(aws); } + /** + * Patch a specific AWS SDK v2 client and create traces when your application makes calls to that AWS service. + * + * If you want to patch all clients use {@link captureAWS} and if you are using AWS SDK v3 use {@link captureAWSv3Client} instead. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-awssdkclients.html + * + * @example + * ```typescript + * import { S3 } from "aws-sdk"; + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * tracer.captureAWS(require('aws-sdk')); + * const s3 = tracer.captureAWSClient(new S3({ apiVersion: "2006-03-01" })); + * ``` + * + * @param service - AWS SDK v2 client + * @returns service - Instrumented AWS SDK v2 client + */ public captureAWSClient(service: T): T { if (this.tracingEnabled === false) return service; return this.provider.captureAWSClient(service); } + /** + * Patch an AWS SDK v3 client and create traces when your application makes calls to that AWS service. + * + * If you are using AWS SDK v2 use {@link captureAWSClient} instead. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-awssdkclients.html + * + * @example + * ```typescript + * import { S3Client } from "@aws-sdk/client-s3"; + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * const client = new S3Client({}); + * tracer.captureAWSv3Client(client); + * ``` + * + * @param service - AWS SDK v3 client + * @returns service - Instrumented AWS SDK v3 client + */ public captureAWSv3Client(service: T): T { if (this.tracingEnabled === false) return service; @@ -106,6 +164,28 @@ class Tracer implements TracerInterface { }; } + /** + * Get the active segment or subsegment in the current scope. + * + * Usually you won't need to call this method unless you are manipulating segments using the escape hatch pattern. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-segments + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/tracer/#escape-hatch-mechanism + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * export const handler = async (_event: any, _context: any) => { + * const currentSegment = tracer.getSegment(); + * ... // Do something with segment + * } + * ``` + * + * @returns segment - The active segment or subsegment in the current scope. + */ public getSegment(): Segment | Subsegment { const segment = this.provider.getSegment(); if (segment === undefined) { @@ -115,6 +195,17 @@ class Tracer implements TracerInterface { return segment; } + /** + * Retrieve the current value of `ColdStart`. + * + * If Tracer has been initialized outside of the Lambda handler then the same instance + * of Tracer will be reused throghout the lifecycle of that same Lambda execution environment + * and this method will return `false` after the first invocation. + * + * @see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html + * + * @returns boolean - true if is cold start otherwise false + */ public static isColdStart(): boolean { if (Tracer.coldStart === true) { Tracer.coldStart = false; @@ -125,6 +216,25 @@ class Tracer implements TracerInterface { return false; } + /** + * Adds annotation to existing segment or subsegment. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-segment.html#xray-sdk-nodejs-segment-annotations + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * export const handler = async (_event: any, _context: any) => { + * tracer.putAnnotation('PaymentStatus', "SUCCESS"); + * } + * ``` + * + * @param key - Annotation key + * @param value - Value for annotation + */ public putAnnotation(key: string, value: string | number | boolean): void { if (this.tracingEnabled === false) return; @@ -137,6 +247,27 @@ class Tracer implements TracerInterface { document?.addAnnotation(key, value); } + /** + * Adds metadata to existing segment or subsegment. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-segment.html#xray-sdk-nodejs-segment-metadata + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * export const handler = async (_event: any, _context: any) => { + * const res = someLogic() + * tracer.putAnnotation('PaymentResponse', res); + * } + * ``` + * + * @param key - Metadata key + * @param value - Value for metadata + * @param timestamp - Namespace that metadata will lie under, if none is passed it will use the serviceName + */ public putMetadata(key: string, value: unknown, namespace?: string | undefined): void { if (this.tracingEnabled === false) return; @@ -151,10 +282,40 @@ class Tracer implements TracerInterface { document?.addMetadata(key, value, namespace); } + /** + * Sets the passed subsegment as the current active subsegment. + * + * If you are using a middleware or a decorator this is done automatically for you. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-subsegments.html + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * import { Segment } from 'aws-xray-sdk-core'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * export const handler = async (_event: any, _context: any) => { + * const subsegment = new Subsegment('### foo.bar'); + * tracer.setSegment(subsegment); + * } + * ``` + * + * @param segment - Subsegment to set as the current segment + */ public setSegment(segment: Segment | Subsegment): void { return this.provider.setSegment(segment); } + /** + * Add an error to the current segment or subsegment as metadata. + * Used internally by decoratorators and middlewares. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-errors + * + * @param error - Error to serialize as metadata + */ private addErrorAsMetadata(error: Error): void { const subsegment = this.getSegment(); if (this.captureError === false) { @@ -166,6 +327,15 @@ class Tracer implements TracerInterface { subsegment.addError(error, false); } + /** + * Add an data to the current segment or subsegment as metadata. + * Used internally by decoratorators and middlewares. + * + * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-errors + * + * @param data - Data to serialize as metadata + * @param methodName - Name of the method that is being traced + */ private addResponseAsMetadata(data?: unknown, methodName?: string): void { if (data === undefined || this.captureResponse === false || this.tracingEnabled === false) { return; @@ -174,32 +344,62 @@ class Tracer implements TracerInterface { this.putMetadata(`${methodName} response`, data); } + /** + * Add ColdStart annotation to the current segment or subsegment. + * Used internally by decoratorators and middlewares. + */ private annotateColdStart(): void { if (Tracer.isColdStart()) { this.putAnnotation('ColdStart', true); } } + /** + * Getter for `customConfigService`. + * Used internally during initialization. + */ private getCustomConfigService(): ConfigServiceInterface | undefined { return this.customConfigService; } + /** + * Getter for `envVarsService`. + * Used internally during initialization. + */ private getEnvVarsService(): EnvironmentVariablesService { return this.envVarsService; } + /** + * Determine if we are running in a Lambda execution environment. + * Used internally during initialization. + */ private isLambdaExecutionEnv(): boolean { return this.getEnvVarsService()?.getAwsExecutionEnv() !== ''; } + /** + * Determine if we are running inside a SAM CLI process. + * Used internally during initialization. + */ private isLambdaSamCli(): boolean { return this.getEnvVarsService()?.getSamLocal() !== ''; } + /** + * Validate that the service name provided is valid. + * Used internally during initialization. + * + * @param serviceName - Service name to validate + */ private isValidServiceName(serviceName?: string): boolean { return typeof serviceName === 'string' && serviceName.trim().length > 0; } + /** + * Setter for `captureError` based on configuration passed and environment variables. + * Used internally during initialization. + */ private setCaptureError(): void { const customConfigValue = this.getCustomConfigService()?.getTracingCaptureError(); if (customConfigValue !== undefined && customConfigValue.toLowerCase() === 'false') { @@ -216,6 +416,10 @@ class Tracer implements TracerInterface { } } + /** + * Setter for `captureResponse` based on configuration passed and environment variables. + * Used internally during initialization. + */ private setCaptureResponse(): void { const customConfigValue = this.getCustomConfigService()?.getTracingCaptureResponse(); if (customConfigValue !== undefined && customConfigValue.toLowerCase() === 'false') { @@ -232,14 +436,30 @@ class Tracer implements TracerInterface { } } + /** + * Setter for `customConfigService` based on configuration passed. + * Used internally during initialization. + * + * @param customConfigService - Custom configuration service to use + */ private setCustomConfigService(customConfigService?: ConfigServiceInterface): void { this.customConfigService = customConfigService ? customConfigService : undefined; } + /** + * Setter and initializer for `envVarsService`. + * Used internally during initialization. + */ private setEnvVarsService(): void { this.envVarsService = new EnvironmentVariablesService(); } + /** + * Method that reconciles the configuration passed with the environment variables. + * Used internally during initialization. + * + * @param options - Configuration passed to the tracer + */ private setOptions(options: TracerOptions): Tracer { const { enabled, @@ -257,6 +477,12 @@ class Tracer implements TracerInterface { return this; } + /** + * Setter for `customConfigService` based on configurations passed and environment variables. + * Used internally during initialization. + * + * @param serviceName - Name of the service to use + */ private setServiceName(serviceName?: string): void { if (serviceName !== undefined && this.isValidServiceName(serviceName)) { this.serviceName = serviceName; @@ -279,6 +505,12 @@ class Tracer implements TracerInterface { } } + /** + * Setter for `tracingEnabled` based on configurations passed and environment variables. + * Used internally during initialization. + * + * @param enabled - Whether or not tracing is enabled + */ private setTracingEnabled(enabled?: boolean): void { if (enabled !== undefined && enabled === false) { this.tracingEnabled = enabled; diff --git a/packages/tracing/types/Tracer.ts b/packages/tracing/types/Tracer.ts index 7ff38ee1c4..74f966a0f9 100644 --- a/packages/tracing/types/Tracer.ts +++ b/packages/tracing/types/Tracer.ts @@ -2,6 +2,22 @@ import { ConfigServiceInterface } from '../src/config'; import { Handler } from 'aws-lambda'; import { LambdaInterface } from '../examples/utils/lambda'; +/** + * Options for the tracer class to be used during initialization. + * + * Usage: + * @example + * ```typescript + * const customConfigService: ConfigServiceInterface; + * const tracerOptions: TracerOptions = { + * enabled?: true, + * serviceName?: 'my-service', + * customConfigService?: customConfigService, // Only needed for advanced uses + * }; + * + * const tracer = new Tracer(tracerOptions); + * ``` + */ type TracerOptions = { enabled?: boolean serviceName?: string From 081f28d86fdd73d33258a826c2029b60af8ff4a3 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 15 Dec 2021 12:53:47 +0100 Subject: [PATCH 5/6] API docs for Tracer --- packages/tracing/src/Tracer.ts | 158 +++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/packages/tracing/src/Tracer.ts b/packages/tracing/src/Tracer.ts index 397cf15d5a..fbfc56d6cc 100644 --- a/packages/tracing/src/Tracer.ts +++ b/packages/tracing/src/Tracer.ts @@ -5,6 +5,86 @@ import { HandlerMethodDecorator, TracerOptions, MethodDecorator } from '../types import { ProviderService, ProviderServiceInterface } from './provider'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; +/** + * ## Intro + * Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://github.com/aws/aws-xray-sdk-node). + * + * Tracing data can be visualized through AWS X-Ray Console. + * + * ## Key features + * * Auto capture cold start as annotation, and responses or full exceptions as metadata + * * Auto-disable when not running in AWS Lambda environment + * * Support tracing functions via decorators, middleware, and manual instrumentation + * * Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js + * + * ## Usage + * + * ### Functions usage with middlewares + * TBD + * + * ### Object oriented usage with decorators + * + * If you use TypeScript Classes to wrap your Lambda handler you can use the [@tracer.captureLambdaHanlder()](./_aws_lambda_powertools_tracer.Tracer.html#captureLambdaHanlder) decorator to automatically: + * * handle the subsegment lifecycle + * * add the `ColdStart` annotation + * * add the function response as metadata + * * add the function error as metadata (if any) + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * // 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.captureLambdaHanlder` + * + * class Lambda { + * @tracer.captureLambdaHanlder() + * public handler(event: any, context: any) { + * ... + * } + * } + * + * export const handlerClass = new Lambda(); + * export const handler = handlerClass.handler; + * ``` + * + * ### Functions usage with manual instrumentation + * + * If you prefer to manually instrument your Lambda handler you can use the methods in the tracer class directly. + * + * @example + * ```typescript + * import { Tracer } from '@aws-lambda-powertools/tracer'; + * import { Segment } from 'aws-xray-sdk-core'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * const AWS = tracer.captureAWS(require('aws-sdk')); + * + * export const handler = async (_event: any, context: any) => { + * // Create subsegment & set it as active + * const subsegment = new Subsegment(`## ${context.functionName}`); + * tracer.setSegment(subsegment); + * // Add the ColdStart annotation + * this.putAnnotation('ColdStart', tracer.coldStart); + * + * let res; + * try { + * res = await someLogic(); // Do something + * // Add the response as metadata + * tracer.putMetadata(`${context.functionName} response`, data); + * } catch (err) { + * // Add the error as metadata + * subsegment.addError(err, false); + * } + * + * // Close subsegment + * subsegment.close(); + * + * return res; + * } + * ``` + */ class Tracer implements TracerInterface { public static coldStart: boolean = true; @@ -40,6 +120,10 @@ class Tracer implements TracerInterface { * * const tracer = new Tracer({ serviceName: 'my-service' }); * const AWS = tracer.captureAWS(require('aws-sdk')); + * + * export const handler = async (_event: any, _context: any) => { + * ... + * } * ``` * * @param aws - AWS SDK v2 import @@ -66,6 +150,10 @@ class Tracer implements TracerInterface { * const tracer = new Tracer({ serviceName: 'my-service' }); * tracer.captureAWS(require('aws-sdk')); * const s3 = tracer.captureAWSClient(new S3({ apiVersion: "2006-03-01" })); + * + * export const handler = async (_event: any, _context: any) => { + * ... + * } * ``` * * @param service - AWS SDK v2 client @@ -92,6 +180,10 @@ class Tracer implements TracerInterface { * const tracer = new Tracer({ serviceName: 'my-service' }); * const client = new S3Client({}); * tracer.captureAWSv3Client(client); + * + * export const handler = async (_event: any, _context: any) => { + * ... + * } * ``` * * @param service - AWS SDK v3 client @@ -103,6 +195,37 @@ class Tracer implements TracerInterface { return this.provider.captureAWSv3Client(service); } + /** + * A decorator automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. + * + * Using this decorator on your handler function will automatically: + * * handle the subsegment lifecycle + * * add the `ColdStart` annotation + * * add the function response as metadata + * * add the function error as metadata (if any) + * + * 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 { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * class Lambda { + * @tracer.captureLambdaHanlder() + * public handler(event: any, context: any) { + * ... + * } + * } + * + * export const handlerClass = new Lambda(); + * export const handler = handlerClass.handler; + * ``` + * + * @decorator Class + */ public captureLambdaHanlder(): HandlerMethodDecorator { return (target, _propertyKey, descriptor) => { const originalMethod = descriptor.value; @@ -134,6 +257,41 @@ class Tracer implements TracerInterface { }; } + /** + * A decorator automating capture of metadata and annotations on segments or subsegments for an arbitrary function. + * + * Using this decorator on your function will automatically: + * * handle the subsegment lifecycle + * * add the function response as metadata + * * add the function error as metadata (if any) + * + * 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 { Tracer } from '@aws-lambda-powertools/tracer'; + * + * const tracer = new Tracer({ serviceName: 'my-service' }); + * + * class Lambda { + * @tracer.captureMethod() + * public myMethod(param: any) { + * ... + * } + * + * public handler(event: any, context: any) { + * ... + * } + * } + * + * export const handlerClass = new Lambda(); + * export const myMethod = handlerClass.myMethod; + * export const handler = handlerClass.handler; + * ``` + * + * @decorator Class + */ public captureMethod(): MethodDecorator { return (target, _propertyKey, descriptor) => { const originalMethod = descriptor.value; From a9799bde1489a7e08db070ea8996cd6352bcd961 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 15 Dec 2021 13:33:01 +0100 Subject: [PATCH 6/6] Updated docs + typos --- docs/core/tracer.md | 184 +++++++++++++++++++++++---------- packages/tracing/src/Tracer.ts | 5 +- 2 files changed, 134 insertions(+), 55 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 76c7387ff4..21c3bf2cf4 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -11,8 +11,8 @@ Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://gi * Auto capture cold start as annotation, and responses or full exceptions as metadata * Auto-disable when not running in AWS Lambda environment -* Support tracing async methods, generators, and context managers -* Auto patch supported modules by AWS X-Ray +* Support tracing functions via decorators, middleware, and manual instrumentation +* Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js ## Getting started @@ -38,30 +38,79 @@ Before your use this utility, your AWS Lambda function [must have permissions](h ### Lambda handler -You can quickly start by importing the `Tracer` class, initialize it outside the Lambda handler, and use `captureLambdaHanlder` decorator. +You can quickly start by importing the `Tracer` class, initialize it outside the Lambda handler, and instrument your function. -=== "index.ts" +=== "Middleware" - ```typescript hl_lines="1 3 8" + ```typescript hl_lines="1 3 6" import { Tracer } from '@aws-lambda-powertools/tracer'; const tracer = Tracer(); // Sets service via env var // OR tracer = Tracer({ service: 'example' }); - class Lambda { + // TODO: update example once middleware has been implemented. + + export const handler = async (_event: any, _context: any) => { + ... + } + ``` + +=== "Decorator" + + ```typescript hl_lines="1 3 7" + import { Tracer } from '@aws-lambda-powertools/tracer'; + + const tracer = Tracer(); // Sets service via env var + // OR tracer = Tracer({ service: 'example' }); + class Lambda { @tracer.captureLambdaHanlder() - public handler(_event: TEvent, _context: Context, _callback: Callback): void { - const chargeId = event.chargeId; - const payment = collectPayment(chargeId); + public handler(event: any, context: any) { ... } + } + + export const handlerClass = new Lambda(); + export const handler = handlerClass.handler; + ``` + +=== "Manual" + ```typescript hl_lines="1-2 4 8-9 11 17 20 24" + import { Tracer } from '@aws-lambda-powertools/tracer'; + import { Segment } from 'aws-xray-sdk-core'; + + const tracer = Tracer(); // Sets service via env var + // OR tracer = Tracer({ service: 'example' }); + + export const handler = async (_event: any, context: any) => { + // Create subsegment & set it as active + const subsegment = new Subsegment(`## ${context.functionName}`); + tracer.setSegment(subsegment); + // Add the ColdStart annotation + this.putAnnotation('ColdStart', tracer.coldStart); + + let res; + try { + res = await someLogic(); // Do something + // Add the response as metadata + tracer.putMetadata(`${context.functionName} response`, data); + } catch (err) { + // Add the error as metadata + subsegment.addError(err, false); + } + + // Close subsegment + subsegment.close(); + + return res; } ``` -When using this `captureLambdaHanlder` decorator, Tracer performs these additional tasks to ease operations: + +When using thes `captureLambdaHanlder` decorator or the `TBD` middleware, Tracer performs these additional tasks to ease operations: +* Handles the lifecycle of the subsegment * Creates a `ColdStart` annotation to easily filter traces that have had an initialization overhead * Captures any response, or full exceptions generated by the handler, and include as tracing metadata @@ -74,38 +123,26 @@ When using this `captureLambdaHanlder` decorator, Tracer performs these addition === "Annotations" You can add annotations using `putAnnotation` method. - ```typescript hl_lines="10" + ```typescript hl_lines="6" import { Tracer } from '@aws-lambda-powertools/tracer'; - - const tracer = new Tracer() - - class Lambda { - - @tracer.captureLambdaHanlder() - public handler(_event: TEvent, _context: Context, _callback: Callback): void { - ... - tracer.putAnnotation('PaymentStatus', "SUCCESS"); - } - + + const tracer = new Tracer({ serviceName: 'my-service' }); + + export const handler = async (_event: any, _context: any) => { + tracer.putAnnotation('PaymentStatus', "SUCCESS"); } ``` === "Metadata" You can add metadata using `putMetadata` method. - ```typescript hl_lines="11" + ```typescript hl_lines="7" import { Tracer } from '@aws-lambda-powertools/tracer'; - const tracer = new Tracer() - - class Lambda { - - @tracer.captureLambdaHanlder() - public handler(_event: TEvent, _context: Context, _callback: Callback): void { - ... - const res = someLogic() - tracer.putAnnotation('PaymentResponse', res); - } + const tracer = new Tracer({ serviceName: 'my-service' }); + export const handler = async (_event: any, _context: any) => { + const res = someLogic(); + tracer.putMetadata('PaymentResponse', res); } ``` @@ -113,29 +150,81 @@ When using this `captureLambdaHanlder` decorator, Tracer performs these addition You can trace other methods using the `captureMethod` decorator. -=== "index.ts" +=== "Middleware" - ```typescript hl_lines="8" + ```typescript hl_lines="1 3 6" import { Tracer } from '@aws-lambda-powertools/tracer'; - const tracer = new Tracer(); // Sets service via env var - // OR tracer = new Tracer({ service: 'example' }); + const tracer = Tracer(); - class Lambda { + // TODO: update example once middleware has been implemented. + + + + export const handler = async (_event: any, _context: any) => { + ... + } + ``` + +=== "Decorator" + ```typescript hl_lines="6" + import { Tracer } from '@aws-lambda-powertools/tracer'; + + const tracer = Tracer(); + + class Lambda { @tracer.captureMethod() public getChargeId(): string { ... return 'foo bar' } - @tracer.captureLambdaHanlder() - public handler(_event: TEvent, _context: Context, _callback: Callback): void { + public handler(event: any, context: any) { const chargeId = this.getChargeId(); const payment = collectPayment(chargeId); ... } + } + + export const handlerClass = new Lambda(); + export const getChargeId = handlerClass.getChargeId; + export const handler = handlerClass.handler; + ``` + +=== "Manual" + ```typescript hl_lines="2 8-9 15 18 22" + import { Tracer } from '@aws-lambda-powertools/tracer'; + import { Segment } from 'aws-xray-sdk-core'; + + const tracer = new Tracer({ serviceName: 'my-service' }); + + const chargeId = async () => { + // Create subsegment & set it as active + const subsegment = new Subsegment(`### chargeId`); + tracer.setSegment(subsegment); + + let res; + try { + res = await someLogic(); // Do something + // Add the response as metadata + tracer.putMetadata(`chargeId response`, data); + } catch (err) { + // Add the error as metadata + subsegment.addError(err, false); + } + + // Close subsegment + subsegment.close(); + + return res; + } + + export const handler = async (_event: any, _context: any) => { + const chargeId = this.getChargeId(); + const payment = collectPayment(chargeId); + ... } ``` @@ -152,7 +241,7 @@ You can patch any AWS SDK clients by calling `captureAWSv3Client` method: === "index.ts" - ```typescript hl_lines="8" + ```typescript hl_lines="6" import { S3Client } from "@aws-sdk/client-s3"; import { Tracer } from '@aws-lambda-powertools/tracer'; @@ -168,7 +257,7 @@ You can patch all AWS SDK clients by calling `captureAWS` method: === "index.ts" - ```typescript hl_lines="8" + ```typescript hl_lines="4" import { Tracer } from '@aws-lambda-powertools/tracer'; const tracer = new Tracer(); @@ -179,7 +268,7 @@ If you're looking to shave a few microseconds, or milliseconds depending on your === "index.ts" - ```typescript hl_lines="8" + ```typescript hl_lines="5" import { S3 } from "aws-sdk"; import { Tracer } from '@aws-lambda-powertools/tracer'; @@ -220,15 +309,6 @@ This is useful when you need a feature available in X-Ray that is not available const logger = new Logger(); const tracer = new Tracer() tracer.provider.setLogger(logger) - - class Lambda { - - @tracer.captureLambdaHanlder() - public handler(_event: TEvent, _context: Context, _callback: Callback): void { - ... - } - - } ``` ## Testing your code diff --git a/packages/tracing/src/Tracer.ts b/packages/tracing/src/Tracer.ts index fbfc56d6cc..849d8328cb 100644 --- a/packages/tracing/src/Tracer.ts +++ b/packages/tracing/src/Tracer.ts @@ -59,7 +59,6 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * import { Segment } from 'aws-xray-sdk-core'; * * const tracer = new Tracer({ serviceName: 'my-service' }); - * const AWS = tracer.captureAWS(require('aws-sdk')); * * export const handler = async (_event: any, context: any) => { * // Create subsegment & set it as active @@ -417,8 +416,8 @@ class Tracer implements TracerInterface { * const tracer = new Tracer({ serviceName: 'my-service' }); * * export const handler = async (_event: any, _context: any) => { - * const res = someLogic() - * tracer.putAnnotation('PaymentResponse', res); + * const res = someLogic(); + * tracer.putMetadata('PaymentResponse', res); * } * ``` *