Integration patterns for @aws-lambda-powertools/logger with Middy, other Powertools, and AWS services.
import { Logger } from '@aws-lambda-powertools/logger';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';
const logger = new Logger({ serviceName: 'myService' });
const lambdaHandler = async (event, context) => {
logger.info('Processing');
return { statusCode: 200 };
};
export const handler = middy(lambdaHandler).use(injectLambdaContext(logger));import { correlationPaths } from '@aws-lambda-powertools/logger/correlationId';
export const handler = middy(lambdaHandler).use(
injectLambdaContext(logger, {
logEvent: false, // Log event payload (avoid in prod)
resetKeys: true, // Clear temporary keys after invocation
flushBufferOnUncaughtError: true, // Flush buffer on errors
correlationIdPath: correlationPaths.API_GATEWAY_REST
})
);const serviceLogger = new Logger({ serviceName: 'service' });
const auditLogger = new Logger({ serviceName: 'audit' });
export const handler = middy(lambdaHandler).use(
injectLambdaContext([serviceLogger, auditLogger], { resetKeys: true })
);import middy from '@middy/core';
import httpJsonBodyParser from '@middy/http-json-body-parser';
import httpErrorHandler from '@middy/http-error-handler';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
const logger = new Logger({ serviceName: 'api' });
export const handler = middy(lambdaHandler)
.use(injectLambdaContext(logger, { logEvent: false })) // Early: inject context first
.use(httpJsonBodyParser()) // Parse body
.use(httpErrorHandler()) // Handle errors last
;Before Hook:
logger.addContext(context)logEvent: truecorrelationIdPath providedAfter Hook:
resetKeys: trueOnError Hook:
flushBufferOnUncaughtError: trueresetKeys: trueimport { Logger } from '@aws-lambda-powertools/logger';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import type { Context } from 'aws-lambda';
const logger = new Logger({ serviceName: 'myService' });
class Lambda implements LambdaInterface {
@logger.injectLambdaContext()
public async handler(event: unknown, context: Context): Promise<unknown> {
logger.info('Processing');
return { statusCode: 200 };
}
}
const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);class Lambda implements LambdaInterface {
@logger.injectLambdaContext({
logEvent: false,
resetKeys: true,
flushBufferOnUncaughtError: true
})
public async handler(event: unknown, context: Context): Promise<unknown> {
logger.appendKeys({ tempKey: 'value' });
logger.info('Processing');
// tempKey automatically cleared after invocation
return { statusCode: 200 };
}
}const logger = new Logger({ serviceName: 'myService' });
class Lambda implements LambdaInterface {
@logger.injectLambdaContext({ resetKeys: true })
public async handler(event: unknown, context: Context): Promise<unknown> {
return await this.process(event);
}
private async process(event: unknown): Promise<unknown> {
logger.info('Processing in private method'); // Context available
return { statusCode: 200 };
}
}import { correlationPaths } from '@aws-lambda-powertools/logger/correlationId';
// REST API
export const restHandler = middy(async (event, context) => {
logger.info('API request'); // Includes requestContext.requestId
return { statusCode: 200 };
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.API_GATEWAY_REST
}));
// HTTP API
export const httpHandler = middy(async (event, context) => {
logger.info('HTTP request'); // Includes requestContext.requestId
return { statusCode: 200 };
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.API_GATEWAY_HTTP
}));export const eventHandler = middy(async (event, context) => {
logger.info('EventBridge event'); // Includes event.id
return { status: 'processed' };
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.EVENT_BRIDGE
}));export const albHandler = middy(async (event, context) => {
logger.info('ALB request'); // Includes x-amzn-trace-id header
return { statusCode: 200 };
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.APPLICATION_LOAD_BALANCER
}));// Resolver
export const resolverHandler = middy(async (event, context) => {
logger.info('AppSync resolver'); // Includes x-amzn-trace-id
return data;
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.APPSYNC_RESOLVER
}));
// Authorizer
export const authorizerHandler = middy(async (event, context) => {
logger.info('AppSync authorizer'); // Includes requestContext.requestId
return { isAuthorized: true };
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.APPSYNC_AUTHORIZER
}));import { search } from '@aws-lambda-powertools/logger/correlationId';
// Custom JMESPath
export const handler = middy(async (event, context) => {
logger.info('Custom correlation'); // Includes custom path value
return { statusCode: 200 };
}).use(injectLambdaContext(logger, {
correlationIdPath: 'headers."x-custom-correlation-id"'
}));
// Custom function
const logger = new Logger({
serviceName: 'myService',
correlationIdSearchFn: (event: any) => {
return event.customCorrelationId || search('metadata.traceId', event) || 'unknown';
}
});import { Logger } from '@aws-lambda-powertools/logger';
import { correlationPaths } from '@aws-lambda-powertools/logger/correlationId';
const logger = new Logger({ serviceName: 'api' });
export const handler = middy(async (event, context) => {
logger.info('Request received');
// Get correlation ID
const correlationId = logger.getCorrelationId();
// Pass to downstream services
await fetch('https://api.example.com/data', {
headers: {
'X-Correlation-ID': String(correlationId)
}
});
// Or to EventBridge
await eventBridge.putEvents({
Entries: [{
Source: 'my.app',
DetailType: 'Order',
Detail: JSON.stringify({ correlationId, ...data })
}]
});
return { statusCode: 200 };
}).use(injectLambdaContext(logger, {
correlationIdPath: correlationPaths.API_GATEWAY_REST
}));Logger automatically includes X-Ray trace ID when available:
const logger = new Logger({ serviceName: 'myService' });
export const handler = async (event, context) => {
logger.addContext(context);
// Trace ID automatically added from _X_AMZN_TRACE_ID env var
logger.info('Processing'); // Log includes xray_trace_id
// Use with AWS SDK v3
await dynamodb.send(new GetItemCommand({ /* ... */ }));
// Trace automatically linked in X-Ray console
};import AWSXRay from 'aws-xray-sdk-core';
const logger = new Logger({ serviceName: 'myService' });
export const handler = async (event, context) => {
const segment = AWSXRay.getSegment();
const subsegment = segment?.addNewSubsegment('custom-operation');
try {
logger.info('Custom operation start');
await performOperation();
subsegment?.addAnnotation('status', 'success');
logger.info('Custom operation complete');
} catch (error) {
subsegment?.addAnnotation('status', 'error');
logger.error('Custom operation failed', error as Error);
} finally {
subsegment?.close();
}
};Logger outputs to stdout/stderr, which Lambda automatically sends to CloudWatch Logs.
Best Practices:
aws logs put-retention-policy --log-group-name /aws/lambda/myFunction --retention-in-days 7-- Find errors for specific request
fields @timestamp, message, function_request_id
| filter function_request_id = "abc-123-def-456"
| sort @timestamp desc
-- Count errors by service
fields @timestamp, service, message
| filter level = "ERROR"
| stats count() by service
-- Find slow invocations
fields @timestamp, function_name, @duration
| filter @duration > 1000
| sort @duration desc
-- Correlation ID tracking
fields @timestamp, message, correlation_id
| filter correlation_id = "trace-123"
| sort @timestamp ascimport { Logger } from '@aws-lambda-powertools/logger';
import { Metrics, MetricUnit } from '@aws-lambda-powertools/metrics';
import { logMetrics } from '@aws-lambda-powertools/metrics/middleware';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';
const logger = new Logger({ serviceName: 'myService' });
const metrics = new Metrics({ namespace: 'MyApp', serviceName: 'myService' });
const lambdaHandler = async (event, context) => {
logger.info('Processing request');
metrics.addMetric('RequestProcessed', MetricUnit.Count, 1);
return { statusCode: 200 };
};
export const handler = middy(lambdaHandler)
.use(injectLambdaContext(logger))
.use(logMetrics(metrics));import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { captureLambdaHandler } from '@aws-lambda-powertools/tracer/middleware';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';
const logger = new Logger({ serviceName: 'myService' });
const tracer = new Tracer({ serviceName: 'myService' });
const lambdaHandler = async (event, context) => {
const segment = tracer.getSegment();
logger.info('Processing with trace', {
traceId: segment?.trace_id,
segmentId: segment?.id
});
const subsegment = segment?.addNewSubsegment('custom-operation');
try {
await performOperation();
} finally {
subsegment?.close();
}
return { statusCode: 200 };
};
export const handler = middy(lambdaHandler)
.use(injectLambdaContext(logger))
.use(captureLambdaHandler(tracer));import { Logger } from '@aws-lambda-powertools/logger';
import { getParameter } from '@aws-lambda-powertools/parameters/ssm';
const logger = new Logger({ serviceName: 'myService' });
export const handler = async (event, context) => {
logger.info('Fetching parameters');
const apiKey = await getParameter('/myapp/api-key', { decrypt: true });
logger.info('Parameters loaded', {
parameterCount: 1,
// Don't log sensitive values
hasApiKey: !!apiKey
});
return { statusCode: 200 };
};import { Logger } from '@aws-lambda-powertools/logger';
describe('Logger', () => {
let logger: Logger;
let consoleLogSpy: jest.SpyInstance;
beforeEach(() => {
logger = new Logger({ serviceName: 'test' });
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
});
afterEach(() => {
consoleLogSpy.mockRestore();
});
it('logs info messages', () => {
logger.info('Test message', { userId: '123' });
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]);
expect(logOutput.level).toBe('INFO');
expect(logOutput.message).toBe('Test message');
expect(logOutput.userId).toBe('123');
});
it('respects log level', () => {
logger.setLogLevel('ERROR');
logger.info('Should not log');
logger.error('Should log');
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
});
});import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';
describe('Handler with Logger', () => {
const logger = new Logger({ serviceName: 'test' });
const mockContext: Context = {
functionName: 'test-function',
awsRequestId: 'test-request-id',
invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789:function:test',
memoryLimitInMB: '128',
// ... other required Context fields
} as Context;
it('includes Lambda context in logs', async () => {
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
logger.addContext(mockContext);
logger.info('Test with context');
const logOutput = JSON.parse(consoleLogSpy.mock.calls[0][0]);
expect(logOutput.function_name).toBe('test-function');
expect(logOutput.function_request_id).toBe('test-request-id');
consoleLogSpy.mockRestore();
});
});// Mock logger for testing
const mockLogger = {
trace: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
critical: jest.fn(),
addContext: jest.fn(),
appendKeys: jest.fn(),
removeKeys: jest.fn()
} as unknown as Logger;
// Use in tests
async function myFunction(logger: Logger) {
logger.info('Processing');
// ... logic
}
it('logs processing message', async () => {
await myFunction(mockLogger);
expect(mockLogger.info).toHaveBeenCalledWith('Processing');
});const logger = new Logger({
serviceName: 'myService',
logLevel: 'DEBUG',
persistentKeys: {
environment: 'dev',
version: process.env.VERSION
}
});
// Enable pretty-printing in dev
// Set: POWERTOOLS_DEV=trueconst logger = new Logger({
serviceName: 'myService',
logLevel: process.env.LOG_LEVEL || 'INFO',
sampleRateValue: 0.01, // 1% debug sampling
persistentKeys: {
environment: 'production',
version: process.env.VERSION,
region: process.env.AWS_REGION
},
logBufferOptions: {
enabled: true,
flushOnErrorLog: true
}
});
export const handler = middy(lambdaHandler).use(
injectLambdaContext(logger, {
logEvent: false, // Never log events in prod
resetKeys: true,
flushBufferOnUncaughtError: true
})
);const logger = new Logger({
serviceName: process.env.POWERTOOLS_SERVICE_NAME || 'default',
logLevel: (process.env.LOG_LEVEL as LogLevel) || 'INFO',
sampleRateValue: parseFloat(process.env.SAMPLE_RATE || '0'),
persistentKeys: {
version: process.env.VERSION || 'unknown',
environment: process.env.ENVIRONMENT || 'dev'
}
});CDK:
import * as lambda from 'aws-cdk-lib/aws-lambda';
new lambda.Function(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
environment: {
LOG_LEVEL: 'INFO',
POWERTOOLS_SERVICE_NAME: 'myService',
POWERTOOLS_LOGGER_SAMPLE_RATE: '0.01'
}
});Serverless Framework:
provider:
environment:
LOG_LEVEL: INFO
POWERTOOLS_SERVICE_NAME: ${self:service}
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.01
functions:
myFunction:
handler: src/handler.main
environment:
LOG_LEVEL: ${env:LOG_LEVEL, 'INFO'}SAM:
Globals:
Function:
Environment:
Variables:
LOG_LEVEL: INFO
POWERTOOLS_SERVICE_NAME: !Ref ServiceName
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.01
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs20.x