Skip to content

feat(logger): set correlation ID in Logger #3726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
68 changes: 68 additions & 0 deletions docs/core/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,74 @@ When debugging in non-production environments, you can log the incoming event us

Use `POWERTOOLS_LOGGER_LOG_EVENT` environment variable to enable or disable (`true`/`false`) this feature. When using Middy.js middleware or class method decorator, the `logEvent` option will take precedence over the environment variable.

### Setting a Correlation ID

To gest started, install `@aws-lambda-powertools/jmespath` package and configure the search function for the correlationId:

=== "Setup the Logger to use JMESPath search"

```typescript
--8<-- "examples/snippets/logger/correlationIdLogger.ts"
```

???+ tip
You can retrieve correlation IDs via `getCorrelationId` method.

You can set a correlation ID using `correlationIdPath` parameter by passing a JMESPath expression, including our custom JMESPath functions or set it manually by calling `setCorrelationId` function.

=== "Setting correlation ID manually"

```typescript
--8<-- "examples/snippets/logger/correlationIdManual.ts"
```

=== "Middy.js Middleware"

```typescript
--8<-- "examples/snippets/logger/correlationIdMiddy.ts"
```

=== "Decorator"

```typescript
--8<-- "examples/snippets/logger/correlationIdDecorator.ts"
```

=== "payload.json"

```typescript
--8<-- "examples/snippets/logger/correlationIdPayload.json"
```

=== "log-output.json"

```json hl_lines="6"
--8<-- "examples/snippets/logger/correlationIdOutput.json"
```

To ease routine tasks like extracting correlation ID from popular event sources, we provide built-in JMESPath expressions.

=== "Decorator"

```typescript
--8<-- "examples/snippets/logger/correlationIdPaths.ts"
```

???+ note "Note: Any object key named with `-` must be escaped"
For example, **`request.headers."x-amzn-trace-id"`**.

| Name | Expression | Description |
| ----------------------------- | ------------------------------------- | ------------------------------- |
| **API_GATEWAY_REST** | `'requestContext.requestId'` | API Gateway REST API request ID |
| **API_GATEWAY_HTTP** | `'requestContext.requestId'` | API Gateway HTTP API request ID |
| **APPSYNC_AUTHORIZER** | `'requestContext.requestId'` | AppSync resolver request ID |
| **APPSYNC_RESOLVER** | `'request.headers."x-amzn-trace-id"'` | AppSync X-Ray Trace ID |
| **APPLICATION_LOAD_BALANCER** | `'headers."x-amzn-trace-id"'` | ALB X-Ray Trace ID |
| **EVENT_BRIDGE** | `"id"` | EventBridge Event ID |
| **LAMBDA_FUNCTION_URL** | `'requestContext.requestId'` | Lambda Function URL request ID |
| **S3_OBJECT_LAMBDA** | `'xAmzRequestId'` | S3 Object trigger request ID |
| **VPC_LATTICE** | `'headers."x-amzn-trace-id'` | VPC Lattice X-Ray Trace ID |

### Appending additional keys

You can append additional keys using either mechanism:
Expand Down
19 changes: 19 additions & 0 deletions examples/snippets/logger/correlationIdDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { Logger } from '@aws-lambda-powertools/logger';
import { search } from '@aws-lambda-powertools/logger/correlationId';

const logger = new Logger({
correlationIdSearchFn: search,
});

class Lambda implements LambdaInterface {
@logger.injectLambdaContext({
correlationIdPath: 'headers.my_request_id_header',
})
public async handler(_event: unknown, _context: unknown): Promise<void> {
logger.info('This is an INFO log with some context');
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
6 changes: 6 additions & 0 deletions examples/snippets/logger/correlationIdLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Logger } from '@aws-lambda-powertools/logger';
import { search } from '@aws-lambda-powertools/logger/correlationId';

const logger = new Logger({
correlationIdSearchFn: search,
});
13 changes: 13 additions & 0 deletions examples/snippets/logger/correlationIdManual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Logger } from '@aws-lambda-powertools/logger';
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';

const logger = new Logger();

export const handler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<void> => {
logger.setCorrelationId(event.requestContext.requestId);

logger.info('This is an INFO log with some context');
};
21 changes: 21 additions & 0 deletions examples/snippets/logger/correlationIdMiddy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Logger } from '@aws-lambda-powertools/logger';
import { search } from '@aws-lambda-powertools/logger/correlationId';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';

const logger = new Logger({
correlationIdSearchFn: search,
});

const lambdaHandler = async (
_event: unknown,
_context: unknown
): Promise<void> => {
logger.info('This is an INFO log with some context');
};

export const handler = middy(lambdaHandler).use(
injectLambdaContext(logger, {
correlationIdPath: 'headers.my_request_id_header',
})
);
7 changes: 7 additions & 0 deletions examples/snippets/logger/correlationIdOutput.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"level": "INFO",
"message": "This is an INFO log with some context",
"timestamp": "2021-05-03 11:47:12,494+0000",
"service": "payment",
"correlation_id": "correlation_id_value"
}
22 changes: 22 additions & 0 deletions examples/snippets/logger/correlationIdPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { Logger } from '@aws-lambda-powertools/logger';
import {
correlationPaths,
search,
} from '@aws-lambda-powertools/logger/correlationId';

const logger = new Logger({
correlationIdSearchFn: search,
});

class Lambda implements LambdaInterface {
@logger.injectLambdaContext({
correlationIdPath: correlationPaths.API_GATEWAY_REST,
})
public async handler(_event: unknown, _context: unknown): Promise<void> {
logger.info('This is an INFO log with some context');
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
5 changes: 5 additions & 0 deletions examples/snippets/logger/correlationIdPayload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"headers": {
"my_request_id_header": "correlation_id_value"
}
}
14 changes: 10 additions & 4 deletions packages/logger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
"./types": {
"import": "./lib/esm/types/index.js",
"require": "./lib/cjs/types/index.js"
},
"./correlationId": {
"import": "./lib/esm/correlationId.js",
"require": "./lib/cjs/correlationId.js"
}
},
"typesVersions": {
Expand All @@ -68,16 +72,18 @@
"@types/lodash.merge": "^4.6.9"
},
"peerDependencies": {
"@middy/core": "4.x || 5.x || 6.x"
"@middy/core": "4.x || 5.x || 6.x",
"@aws-lambda-powertools/jmespath": "2.x"
},
"peerDependenciesMeta": {
"@middy/core": {
"optional": true
},
"@aws-lambda-powertools/jmespath": {
"optional": true
}
},
"files": [
"lib"
],
"files": ["lib"],
"repository": {
"type": "git",
"url": "git+https://github.com/aws-powertools/powertools-lambda-typescript.git"
Expand Down
62 changes: 62 additions & 0 deletions packages/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ class Logger extends Utility implements LoggerInterface {
*/
#buffer?: CircularMap<string>;

/**
* Search function for the correlation ID.
*/
#correlationIdSearchFn?: (expression: string, data: unknown) => unknown;

/**
* The debug sampling rate configuration.
*/
Expand Down Expand Up @@ -326,6 +331,7 @@ class Logger extends Utility implements LoggerInterface {
environment: this.powertoolsLogData.environment,
persistentLogAttributes: this.persistentLogAttributes,
jsonReplacerFn: this.#jsonReplacerFn,
correlationIdSearchFn: this.#correlationIdSearchFn,
...(this.#bufferConfig.enabled && {
logBufferOptions: {
maxBytes: this.#bufferConfig.maxBytes,
Expand Down Expand Up @@ -480,6 +486,9 @@ class Logger extends Utility implements LoggerInterface {
loggerRef.refreshSampleRateCalculation();
loggerRef.addContext(context);
loggerRef.logEventIfEnabled(event, options?.logEvent);
if (options?.correlationIdPath) {
loggerRef.setCorrelationId(event, options?.correlationIdPath);
}

try {
return await originalMethod.apply(this, [event, context, callback]);
Expand Down Expand Up @@ -1261,6 +1270,7 @@ class Logger extends Utility implements LoggerInterface {
jsonReplacerFn,
logRecordOrder,
logBufferOptions,
correlationIdSearchFn,
} = options;

if (persistentLogAttributes && persistentKeys) {
Expand All @@ -1287,6 +1297,7 @@ class Logger extends Utility implements LoggerInterface {
this.setLogIndentation();
this.#jsonReplacerFn = jsonReplacerFn;
this.#setLogBuffering(logBufferOptions);
this.#correlationIdSearchFn = correlationIdSearchFn;

return this;
}
Expand Down Expand Up @@ -1439,6 +1450,57 @@ class Logger extends Utility implements LoggerInterface {
logLevel <= this.#bufferConfig.bufferAtVerbosity
);
}

/**
* Set the correlation ID for the log item.
* This method can be used to set the correlation ID for the log item or to search for the correlation ID in the event.
*
* @example
* ```typescript
* import { Logger } from '@aws-lambda-powertools/logger';
*
* const logger = new Logger();
* logger.setCorrelationId('my-correlation-id'); // sets the correlation ID directly with the first argument as value
* ```
*
* ```typescript
* import { Logger } from '@aws-lambda-powertools/logger';
* import { search } from '@aws-lambda-powertools/logger/correlationId';
*
* const logger = new Logger({ correlationIdSearchFn: search });
* logger.setCorrelationId(event, 'requestContext.requestId'); // sets the correlation ID from the event using JMSPath expression
*
*
* @param value - The value to set as the correlation ID or the event to search for the correlation ID
* @param correlationIdPath - The path to search for the correlation ID in the event, if not set value is used directly
*/
public setCorrelationId(value: unknown, correlationIdPath?: string): void {
if (typeof correlationIdPath === 'string') {
if (!this.#correlationIdSearchFn) {
this.warn(
'correlationIdPath is set but no search function was provided. The correlation ID will not be added to the log attributes.'
);
return;
}
const correlationId = this.#correlationIdSearchFn(
correlationIdPath,
value
);
if (correlationId) this.appendKeys({ correlation_id: correlationId });
return;
}

// If no correlationIdPath is provided, set the correlation ID directly
this.appendKeys({ correlation_id: value });
}

/**
* Get the correlation ID from the log attributes.
* @returns The correlation ID from the log attributes
*/
public getCorrelationId(): unknown {
return this.temporaryLogAttributes.correlation_id;
}
}

export { Logger };
61 changes: 61 additions & 0 deletions packages/logger/src/correlationId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { JSONObject } from '@aws-lambda-powertools/commons/types';
import { search as JMESPathSearch } from '@aws-lambda-powertools/jmespath';
import { PowertoolsFunctions } from '@aws-lambda-powertools/jmespath/functions';

/**
* This function is used to search for a correlation ID in the event data and is a wrapper
* around the JMESPath search function. It allows you to specify a JMESPath expression
* to extract the correlation ID from the event data.
* @param expression - The JMESPath expression to use for searching the correlation ID.
* @param data - The event data to search in.
* @returns - The correlation ID extracted from the event data.
*/
const search = (expression: string, data: unknown) => {
return JMESPathSearch(expression, data as JSONObject, {
customFunctions: new PowertoolsFunctions(),
});
};

/**
* The correlationPaths object contains the JMESPath expressions for extracting the correlation ID for various AWS services.
*/
const correlationPaths = {
/**
* API Gateway REST API request ID
*/
API_GATEWAY_REST: 'requestContext.requestId',
/**
* API Gateway HTTP API request ID
*/
API_GATEWAY_HTTP: 'requestContext.requestId',
/**
* AppSync API request ID
*/
APPSYNC_AUTHORIZER: 'requestContext.requestId',
/**
* AppSync resolver X-Ray trace ID
*/
APPSYNC_RESOLVER: 'request.headers."x-amzn-trace-id"',
/**
* ALB X-Ray trace ID
*/
APPLICATION_LOAD_BALANCER: 'headers."x-amzn-trace-id"',
/**
* EventBridge event ID
*/
EVENT_BRIDGE: 'id',
/**
* Lambda Function URL request ID
*/
LAMBDA_FUNCTION_URL: 'requestContext.requestId',
/**
* S3 Object trigger request ID
*/
S3_OBJECT_LAMBDA: 'xAmzRequestId',
/**
* VPC Lattice X-Ray trace ID
*/
VPC_LATTICE: 'headers."x-amzn-trace-id"',
} as const;

export { correlationPaths, search };
4 changes: 2 additions & 2 deletions packages/logger/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { Logger } from './Logger.js';
export { LogLevel, LogLevelThreshold } from './constants.js';
export { LogFormatter } from './formatter/LogFormatter.js';
export { LogItem } from './formatter/LogItem.js';
export { LogLevel, LogLevelThreshold } from './constants.js';
export { Logger } from './Logger.js';
4 changes: 4 additions & 0 deletions packages/logger/src/middleware/middy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ const injectLambdaContext = (
request.context,
options
);

if (options?.correlationIdPath) {
logger.setCorrelationId(request.event, options.correlationIdPath);
}
}
};

Expand Down
Loading