or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-patterns.mdapi-reference.mdindex.mdintegration.md
tile.json

integration.mddocs/

Integration Guide

Integration patterns for @aws-lambda-powertools/logger with Middy, other Powertools, and AWS services.

Middy Middleware

Basic Integration

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));

Full Configuration

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
  })
);

Multiple Loggers

const serviceLogger = new Logger({ serviceName: 'service' });
const auditLogger = new Logger({ serviceName: 'audit' });

export const handler = middy(lambdaHandler).use(
  injectLambdaContext([serviceLogger, auditLogger], { resetKeys: true })
);

Middleware Execution Order

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
;

Middleware Hooks Behavior

Before Hook:

  • Adds Lambda context via logger.addContext(context)
  • Logs event if logEvent: true
  • Extracts correlation ID if correlationIdPath provided

After Hook:

  • Resets temporary keys if resetKeys: true

OnError Hook:

  • Flushes log buffer if flushBufferOnUncaughtError: true
  • Resets temporary keys if resetKeys: true

Decorator Integration

Basic Decorator

import { 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);

Decorator with Options

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 };
  }
}

Multiple Methods

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 };
  }
}

Correlation ID Integration

API Gateway

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
}));

EventBridge

export const eventHandler = middy(async (event, context) => {
  logger.info('EventBridge event'); // Includes event.id
  return { status: 'processed' };
}).use(injectLambdaContext(logger, {
  correlationIdPath: correlationPaths.EVENT_BRIDGE
}));

Application Load Balancer

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
}));

AppSync

// 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
}));

Custom Correlation ID

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';
  }
});

Propagating Correlation ID

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
}));

AWS X-Ray Integration

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
};

Manual X-Ray Segment Annotation

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();
  }
};

CloudWatch Logs Integration

Log Group Configuration

Logger outputs to stdout/stderr, which Lambda automatically sends to CloudWatch Logs.

Best Practices:

  • Set log retention: aws logs put-retention-policy --log-group-name /aws/lambda/myFunction --retention-in-days 7
  • Use log group subscription filters for centralized logging
  • Enable CloudWatch Insights for structured log queries

CloudWatch Insights Queries

-- 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 asc

Other AWS Powertools Integration

Metrics Integration

import { 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));

Tracer Integration

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));

Parameters Integration

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 };
};

Testing

Unit Testing

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);
  });
});

Integration Testing

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();
  });
});

Mocking for Tests

// 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');
});

Environment-Specific Configuration

Development

const logger = new Logger({
  serviceName: 'myService',
  logLevel: 'DEBUG',
  persistentKeys: {
    environment: 'dev',
    version: process.env.VERSION
  }
});

// Enable pretty-printing in dev
// Set: POWERTOOLS_DEV=true

Production

const 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
  })
);

Configuration from Environment

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'
  }
});

Deployment Patterns

Infrastructure as Code

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