Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/friendly-dancers-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-aws/powertools-tracer": minor
---

Add captureLambdaHandler helper
5 changes: 5 additions & 0 deletions .changeset/nice-weeks-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-aws/powertools-tracer": minor
---

Add captureAWSv3Client helper
8 changes: 6 additions & 2 deletions .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,12 @@ new TypeScriptLibProject({
parent: project,
name: "powertools-logger",
description: "Effectful AWS Lambda Powertools Logger",
devDeps: [...effectDeps, "@aws-lambda-powertools/commons@2.0.0", "@aws-lambda-powertools/logger@2.0.0"],
devDeps: [
...effectDeps,
`${lambda.package.packageName}@workspace:^`,
"@aws-lambda-powertools/commons@2.0.0",
"@aws-lambda-powertools/logger@2.0.0",
],
peerDeps: [...commonPeerDeps, "@aws-lambda-powertools/logger@>=2.0.0"],
});

Expand All @@ -141,7 +146,6 @@ const tracer = new TypeScriptLibProject({
deps: ["aws-xray-sdk-core@^3.5.3"],
devDeps: [
...effectDeps,
`${lambda.package.packageName}@workspace:^`,
"@aws-lambda-powertools/commons@2.0.0",
"@aws-lambda-powertools/tracer@2.0.0",
"@types/aws-lambda",
Expand Down
5 changes: 5 additions & 0 deletions packages/powertools-logger/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/powertools-logger/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 0 additions & 5 deletions packages/powertools-tracer/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 117 additions & 0 deletions packages/powertools-tracer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,120 @@ program.pipe(Effect.provide(Tracer.layer()), Effect.runSync)
```

Check out the a more complete example in the [examples](./examples/example.ts).

## Lambda Handler Instrumentation

Use `captureLambdaHandler` to automatically instrument your Lambda function with X-Ray tracing. This helper provides:

- **Subsegment lifecycle management** - Creates a subsegment named `## ${_HANDLER}` and closes it automatically
- **Cold start annotation** - Annotates traces with cold start information
- **Service name annotation** - Annotates traces with the service name
- **Response capture** - Serializes function responses as metadata
- **Error capture** - Serializes errors as metadata

```typescript
import { LambdaHandler } from "@effect-aws/lambda";
import { Tracer } from "@effect-aws/powertools-tracer";
import type { APIGatewayProxyEventV2 } from "aws-lambda";
import { Effect } from "effect";

const myEffectHandler = (event: APIGatewayProxyEventV2) =>
Effect.gen(function* () {
yield* Effect.log("Processing request");
return { statusCode: 200, body: JSON.stringify({ message: "Success" }) };
});

export const handler = LambdaHandler.make({
handler: Tracer.captureLambdaHandler()(myEffectHandler),
layer: Tracer.layerWithXrayTracer({ serviceName: "my-service" }),
});
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `captureResponse` | `boolean` | `true` | Serialize function responses as metadata |

To disable response capture:

```typescript
export const handler = LambdaHandler.make({
handler: Tracer.captureLambdaHandler({ captureResponse: false })(myEffectHandler),
layer: Tracer.layerWithXrayTracer(),
});
```

## AWS SDK v3 Client Instrumentation

Use `captureAWSv3Client` to automatically instrument AWS SDK v3 clients with X-Ray tracing. All AWS API calls made through instrumented clients will appear as subsegments in your traces.

### S3 Example

```typescript
import { S3ClientInstance, S3Service, makeS3Service } from "@effect-aws/client-s3";
import { Tracer } from "@effect-aws/powertools-tracer";
import { Layer } from "effect";

const InstrumentedS3ClientLayer = Layer.scoped(
S3ClientInstance.S3ClientInstance,
S3ClientInstance.make.pipe(Tracer.captureAWSv3Client),
);

export const InstrumentedS3Layer = Layer.effect(
S3Service,
makeS3Service,
).pipe(Layer.provide(InstrumentedS3ClientLayer));
```

### DynamoDB Document Example

```typescript
import { DynamoDBClientInstance } from "@effect-aws/client-dynamodb";
import {
DynamoDBDocumentClientInstance,
DynamoDBDocumentService,
makeDynamoDBDocumentService,
} from "@effect-aws/dynamodb";
import { Tracer } from "@effect-aws/powertools-tracer";
import { Layer } from "effect";

const InstrumentedDynamoDBClientLayer = Layer.scoped(
DynamoDBClientInstance.DynamoDBClientInstance,
DynamoDBClientInstance.make.pipe(Tracer.captureAWSv3Client),
);

const InstrumentedDocumentClientLayer = Layer.scoped(
DynamoDBDocumentClientInstance.DynamoDBDocumentClientInstance,
DynamoDBDocumentClientInstance.make,
).pipe(Layer.provide(InstrumentedDynamoDBClientLayer));

export const InstrumentedDynamoDBDocumentLayer = Layer.effect(
DynamoDBDocumentService,
makeDynamoDBDocumentService,
).pipe(Layer.provide(InstrumentedDocumentClientLayer));
```

> **Note:** Instrumented layers require `XrayTracer` in the environment. Use `layerWithXrayTracer` when composing your final application layer.

## Available Layers

| Layer | Description |
|-------|-------------|
| `layer(options?)` | Sets Effect's tracer (most common) |
| `layerWithXrayTracer(options?)` | Provides `XrayTracer` service AND sets Effect's tracer |
| `layerTracer(options?)` | Only provides `XrayTracer` service |
| `layerWithoutXrayTracer` | Only sets Effect's tracer (requires `XrayTracer` from context) |

Use `layerWithXrayTracer` when you need to use `captureLambdaHandler`, as it requires the `XrayTracer` service.

## Prerequisites

To use X-Ray tracing with AWS Lambda:

1. **Enable Active Tracing** on your Lambda function (AWS Console / SAM / CDK)
2. **IAM permissions**: `xray:PutTraceSegments`, `xray:PutTelemetryRecords`
3. **Environment variables** (optional):
- `POWERTOOLS_SERVICE_NAME` - Service name for traces
- `POWERTOOLS_TRACER_CAPTURE_RESPONSE` - Enable/disable response capture
- `POWERTOOLS_TRACER_CAPTURE_ERROR` - Enable/disable error capture
34 changes: 18 additions & 16 deletions packages/powertools-tracer/examples/example.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeLambda } from "@effect-aws/lambda";
import { LambdaHandler } from "@effect-aws/lambda";
import { Tracer } from "@effect-aws/powertools-tracer";
import type { APIGatewayProxyEventV2, APIGatewayProxyHandlerV2 } from "aws-lambda";
import { Effect, flow } from "effect";
Expand Down Expand Up @@ -53,22 +53,24 @@ class BadInputError {
readonly _tag = "BadInputError";
}

export const handler: APIGatewayProxyHandlerV2 = makeLambda({
handler: flow(
program,
Effect.catchTags({
BadInputError: () =>
export const handler: APIGatewayProxyHandlerV2 = LambdaHandler.make({
handler: Tracer.captureLambdaHandler()(
flow(
program,
Effect.catchTags({
BadInputError: () =>
Effect.succeed({
statusCode: 400,
body: JSON.stringify({ message: "Bad input" }),
}),
}),
Effect.catchAllDefect((defect) =>
Effect.succeed({
statusCode: 400,
body: JSON.stringify({ message: "Bad input" }),
}),
}),
Effect.catchAllDefect((defect) =>
Effect.succeed({
statusCode: 500,
body: JSON.stringify({ message: `An error occurred: ${defect}` }),
})
statusCode: 500,
body: JSON.stringify({ message: `An error occurred: ${defect}` }),
})
),
),
),
layer: Tracer.layer(),
layer: Tracer.layerWithXrayTracer(),
});
1 change: 0 additions & 1 deletion packages/powertools-tracer/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 53 additions & 4 deletions packages/powertools-tracer/src/Tracer.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
/**
* @since 1.0.0
*/
import type { TracerInterface, TracerOptions } from "@aws-lambda-powertools/tracer/types";
import type { CaptureLambdaHandlerOptions, TracerInterface, TracerOptions } from "@aws-lambda-powertools/tracer/types";
import type { Context } from "aws-lambda";
import type { ConfigError, Effect, Tracer } from "effect";
import type { Tag } from "effect/Context";
import type { Effect } from "effect/Effect";
import type { Layer } from "effect/Layer";
import type { Tracer as EffectTracer } from "effect/Tracer";
import * as internal from "./internal/tracer.js";

/**
* Effectful AWS Lambda handler type.
*
* @since 1.0.0
* @category model
*/
export type EffectHandler<T, R, E = never, A = void> = (
event: T,
context: Context,
) => Effect.Effect<A, E, R>;

/**
* @since 1.0.0
* @category constructors
*/
export const make: Effect<EffectTracer, never, XrayTracer> = internal.make;
export const make: Effect.Effect<Tracer.Tracer, never, XrayTracer> = internal.make;

/**
* @since 1.0.0
Expand Down Expand Up @@ -45,3 +56,41 @@ export const layerWithoutXrayTracer: Layer<never, never, XrayTracer> = internal.
* @category layers
*/
export const layer: (options?: TracerOptions) => Layer<never> = internal.layer;

/**
* @since 1.0.0
* @category layers
*/
export const layerWithXrayTracer: (
options?: TracerOptions,
) => Layer<XrayTracer, never, never> = internal.layerWithXrayTracer;

/**
* Wraps an Effect handler with X-Ray tracing instrumentation.
*
* Automatically:
* - Creates subsegment for the Lambda handler
* - Annotates cold start and service name
* - Captures response/errors as metadata
*
* @since 1.0.0
* @category tracing
*/
export const captureLambdaHandler: (
options?: CaptureLambdaHandlerOptions | undefined,
) => <T, R, E1, A>(
handler: EffectHandler<T, R, E1, A>,
) => EffectHandler<T, XrayTracer | R, E1 | ConfigError.ConfigError, A> = internal.captureLambdaHandler;

/**
* Instruments an AWS SDK v3 client Effect with X-Ray tracing.
*
* Use with Layer.scoped to create instrumented client layers that
* automatically capture AWS API calls in X-Ray traces.
*
* @since 1.0.0
* @category tracing
*/
export const captureAWSv3Client: <A, E, R>(
self: Effect.Effect<A, E, R>,
) => Effect.Effect<A, E, XrayTracer | R> = internal.captureAWSv3Client;
Loading
Loading