Skip to content

Logger

Logger provides an opinionated logger with output structured as JSON.

Key features

  • Capturing key fields from the Lambda context, cold starts, and structure logging output as JSON.
  • Logging Lambda invocation events when instructed (disabled by default).
  • Printing all the logs only for a percentage of invocations via log sampling (disabled by default).
  • Appending additional keys to structured logs at any point in time.
  • Providing a custom log formatter (Bring Your Own Formatter) to output logs in a structure compatible with your organization’s Logging RFC.


Screenshot of the Amazon CloudWatch Console showing an example of error logged with various log attributes
Logger showcase - Log attributes

Getting started

Installation

Install the library in your project:

1
npm install @aws-lambda-powertools/logger

Usage

The Logger utility must always be instantiated outside the Lambda handler. By doing this, subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time. In addition, Logger can keep track of a cold start and inject the appropriate fields into logs.

1
2
3
4
5
6
7
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({ serviceName: 'serverlessAirline' });

export const handler = async (_event, _context): Promise<void> => {
  logger.info('Hello World');
};

Utility settings

The library has three optional settings, which can be set via environment variables or passed in the constructor.

These settings will be used across all logs emitted:

Setting Description Environment variable Default Value Allowed Values Example Value Constructor parameter
Service name Sets the name of service of which the Lambda function is part of, that will be present across all log statements POWERTOOLS_SERVICE_NAME service_undefined Any string serverlessAirline serviceName
Logging level Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) POWERTOOLS_LOG_LEVEL INFO DEBUG, INFO, WARN, ERROR, CRITICAL, SILENT ERROR logLevel
Sample rate Probability that a Lambda invocation will print all the log items regardless of the log level setting POWERTOOLS_LOGGER_SAMPLE_RATE 0 0.0 to 1.0 0.1 sampleRateValue

See all environment variables in the Environment variables section. Check API docs to learn more about Logger constructor options.

Example using AWS Serverless Application Model (SAM)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { Logger } from '@aws-lambda-powertools/logger';

// Logger parameters fetched from the environment variables (see template.yaml tab)
const logger = new Logger();
logger.info('Hello World');

// You can also pass the parameters in the constructor
// const logger = new Logger({
//     logLevel: 'WARN',
//     serviceName: 'serverlessAirline'
// });
1
2
3
4
5
6
7
8
9
Resources:
  ShoppingCartApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: nodejs22.x
      Environment:
        Variables:
          POWERTOOLS_LOG_LEVEL: WARN
          POWERTOOLS_SERVICE_NAME: serverlessAirline

Standard structured keys

Your Logger will include the following keys to your structured logging (default log formatter):

Key Example Note
level: string INFO Logging level set for the Lambda function's invocation
message: string Query performed to DynamoDB A descriptive, human-readable representation of this log item
sampling_rate: float 0.1 When enabled, it prints all the logs of a percentage of invocations, e.g. 10%
service: string serverlessAirline A unique name identifier of the service this Lambda function belongs to, by default service_undefined
timestamp: string 2011-10-05T14:48:00.000Z Timestamp string in simplified extended ISO format (ISO 8601)
xray_trace_id: string 1-5759e988-bd862e3fe1be46a994272793 X-Ray Trace ID. This value is always presented in Lambda environment, whether tracing is enabled or not. Logger will always log this value.
error: Object { name: "Error", location: "/my-project/handler.ts:18", message: "Unexpected error #1", stack: "[stacktrace]"} Optional - An object containing information about the Error passed to the logger
Info

When POWERTOOLS_DEV environment variable is present and set to "true" or "1", Logger will pretty-print log messages for easier readability. We recommend to use this setting only when debugging on local environments.

Capturing Lambda context info

You can enrich your structured logs with key Lambda context information in multiple ways.

This functionality will include the following keys in your structured logs:

Key Example
cold_start: bool false
function_name string shopping-cart-api-lambda-prod-eu-west-1
function_memory_size: number 128
function_arn: string arn:aws:lambda:eu-west-1:123456789012:function:shopping-cart-api-lambda-prod-eu-west-1
function_request_id: string c6af9ac6-7b61-11e6-9a41-93e812345678

A note about Middy

We guarantee support for Middy.js v4.x through v6.x versions. Check their docs to learn more about Middy and its middleware stack as well as best practices when working with Powertools.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { Logger } from '@aws-lambda-powertools/logger';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';

const logger = new Logger();

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

Note

The class method decorators in this project follow the experimental implementation enabled via the experimentalDecorators compiler option in TypeScript.

Additionally, they are implemented to decorate async methods. When decorating a synchronous one, the decorator replaces its implementation with an async one causing the caller to have to await the now decorated method.

If this is not the desired behavior, you can call the logger.injectLambdaContext() method directly in your handler.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

class Lambda implements LambdaInterface {
  // Decorate your handler class method
  @logger.injectLambdaContext()
  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); // (1)
  1. Binding your handler method allows your handler to access this within the class methods.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';

const logger = new Logger();

export const handler = async (
  _event: unknown,
  context: Context
): Promise<void> => {
  logger.addContext(context);

  logger.info('This is an INFO log with some context');
};

In each case, the printed log will look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    "cold_start": true,
    "function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:shopping-cart-api-lambda-prod-eu-west-1",
    "function_memory_size": 128,
    "function_request_id": "c6af9ac6-7b61-11e6-9a41-93e812345678",
    "function_name": "shopping-cart-api-lambda-prod-eu-west-1",
    "level": "INFO",
    "message": "This is an INFO log with some context",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T21:21:08.921Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}

Log incoming event

When debugging in non-production environments, you can log the incoming event using the logEventIfEnabled() method or by setting the logEvent option in the injectLambdaContext() Middy.js middleware or class method decorator.

Warning

This is disabled by default to prevent sensitive info being logged

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
process.env.POWERTOOLS_LOGGER_LOG_EVENT = 'true';

import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

export const handler = async (event: unknown) => {
  logger.logEventIfEnabled(event); // (1)
  // ... your logic here
};
  1. You can control the event logging via the POWERTOOLS_LOGGER_LOG_EVENT environment variable.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { Logger } from '@aws-lambda-powertools/logger';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';

const logger = new Logger();

export const handler = middy(async () => {
  // ... your logic here
}).use(
  injectLambdaContext(logger, { logEvent: true }) // (1)
);
  1. The logEvent option takes precedence over the POWERTOOLS_LOGGER_LOG_EVENT environment variable.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

class Lambda implements LambdaInterface {
  @logger.injectLambdaContext({ logEvent: true }) // (1)
  public async handler(_event: unknown, _context: unknown): Promise<void> {
    // ... your logic here
  }
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
  1. The logEvent option takes precedence over the POWERTOOLS_LOGGER_LOG_EVENT environment variable.
1
2
3
{
  "foo": "bar"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "cold_start": true,
  "function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:LogEventFn",
  "function_memory_size": "128",
  "function_name": "LogEventFn",
  "function_request_id": "0a9df60d-e2de-447d-ba3e-45f149eae6c9",
  "level": "INFO",
  "message": "Lambda invocation event",
  "sampling_rate": 0,
  "service": "service_undefined",
  "timestamp": "2024-08-14T10:08:06.199Z",
  "xray_trace_id": "1-66bc8205-21f8b5190da519d22b2b0533",
  "event": {
    "foo": "bar"
  }
}

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.

Appending additional keys

You can append additional keys using either mechanism:

  • Add extra keys to a single log message by passing them to the log method directly
  • Append temporary keys to all future log messages via the appendKeys() method until resetKeys() is called
  • Set Persistent keys for the logger instance via the persistentKeys constructor option or the appendPersistentKeys() method

Extra keys

You can append additional data to a single log item by passing objects as additional parameters.

  • Pass a simple string for logging it with default key name extra
  • Pass one or multiple objects containing arbitrary data to be logged. Each data object should be placed in an enclosing object as a single property value, you can name this property as you need: { myData: arbitraryObjectToLog }
  • If you already have an object containing a message key and an additional property, you can pass this object directly
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

export const handler = async (
  event: unknown,
  _context: unknown
): Promise<unknown> => {
  const myImportantVariable = {
    foo: 'bar',
  };

  // Log additional data in single log items

  // As second parameter
  logger.info('This is a log with an extra variable', {
    data: myImportantVariable,
  });

  // You can also pass multiple parameters containing arbitrary objects
  logger.info(
    'This is a log with 3 extra objects',
    { data: myImportantVariable },
    { correlationIds: { myCustomCorrelationId: 'foo-bar-baz' } },
    { lambdaEvent: event }
  );

  // Simply pass a string for logging additional data
  logger.info('This is a log with additional string value', 'string value');

  // Directly passing an object containing both the message and the additional info
  const logObject = {
    message: 'This is a log message',
    additionalValue: 42,
  };

  logger.info(logObject);

  return {
    foo: 'bar',
  };
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
    "level": "INFO",
    "message": "This is a log with an extra variable",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:06:17.463Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "data": { "foo": "bar" }
}
{
    "level": "INFO",
    "message": "This is a log with 3 extra objects",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:06:17.466Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "data": { "foo": "bar" },
    "correlationIds": { "myCustomCorrelationId": "foo-bar-baz" },
    "lambdaEvent": { 
        "exampleEventData": {
            "eventValue": 42
        }
    }
}
{
    "level": "INFO",
    "message": "This is a log with additional string value",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:06:17.463Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "extra": "string value"
}
{
    "level": "INFO",
    "message": "This is a log message",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:06:17.463Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "additionalValue": 42
}

Temporary keys

You can append additional keys to all future log messages by using the appendKeys() method.

When is this useful?

This is helpful to contextualize log messages emitted during a specific function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
  serviceName: 'serverlessAirline',
});

const processTransaction = async (customerId: string): Promise<void> => {
  try {
    logger.appendKeys({
      customerId,
    });

    // ... your business logic

    logger.info('transaction processed');
  } finally {
    logger.resetKeys(); // (1)!
  }
};

export const handler = async (
  event: { customerId: string },
  _context: unknown
): Promise<void> => {
  await processTransaction(event.customerId);

  // .. other business logic

  logger.info('other business logic processed');
};
  1. You can also remove specific keys by calling the removeKeys() method.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "level": "INFO",
    "message": "transaction processed",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T21:49:58.084Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "customerId": "123456789012"
}
{
    "level": "INFO",
    "message": "other business logic processed",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T21:49:58.088Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}

Persistent keys

You can persist keys across Lambda invocations by using the persistentKeys constructor option or the appendPersistentKeys() method. These keys will persist even if you call the resetKeys() method.

A common use case is to set keys about your environment or application version, so that you can easily filter logs in CloudWatch Logs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
  serviceName: 'serverlessAirline',
  persistentKeys: {
    environment: 'prod',
    version: process.env.BUILD_VERSION,
  },
});

export const handler = async (
  _event: unknown,
  _context: unknown
): Promise<void> => {
  logger.info('processing transaction');

  // ... your business logic
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
  serviceName: 'serverlessAirline',
});

declare const getRemoteConfig: (env: string) => {
  environment: string;
  version: string;
};
const { environment, version } = getRemoteConfig('prod');

logger.appendPersistentKeys({ environment, version });

export const handler = async (
  _event: unknown,
  _context: unknown
): Promise<void> => {
  logger.info('processing transaction');

  // .. your business logic
};
1
2
3
4
5
6
7
8
9
{
    "level": "INFO",
    "message": "processing transaction",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T21:49:58.084Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "environment": "prod",
    "version": "1.2.0",
}

Removing additional keys

You can remove additional keys from the logger instance at any time:

  • Remove temporary keys added via the appendKeys() method by using the removeKeys() method
  • Remove persistent keys added via the persistentKeys constructor option or the appendPersistentKeys() method by using the removePersistentKeys() method
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
  serviceName: 'serverlessAirline',
});

const processTransaction = async (customerId: string): Promise<void> => {
  try {
    logger.appendKeys({
      customerId,
    });

    // ... your business logic

    logger.info('transaction processed');
  } finally {
    logger.removeKeys(['customerId']);
  }
};

export const handler = async (
  event: { customerId: string },
  _context: unknown
): Promise<void> => {
  await processTransaction(event.customerId);

  // .. other business logic

  logger.info('other business logic processed');
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
  serviceName: 'serverlessAirline',
  persistentKeys: {
    foo: true,
  },
});

declare const getRemoteConfig: (env: string) => {
  isFoo: boolean;
};

export const handler = async (
  _event: unknown,
  _context: unknown
): Promise<void> => {
  const { isFoo } = getRemoteConfig('prod');
  if (isFoo) logger.removePersistentKeys(['foo']);

  logger.info('processing transaction');

  // ... your business logic
};

Resetting keys

Logger is commonly initialized in the global scope. Due to Lambda Execution Context reuse, this means that custom keys can be persisted across invocations.

Resetting the state allows you to clear all the temporary keys you have added.

Tip: When is this useful?

This is useful when you add multiple custom keys conditionally or when you use canonical or wide logs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Logger } from '@aws-lambda-powertools/logger';

// Persistent attributes will be cached across invocations
const logger = new Logger({
  logLevel: 'info',
  persistentKeys: {
    environment: 'prod',
  },
});

// Enable the clear state flag
export const handler = async (
  event: { userId: string },
  _context: unknown
): Promise<void> => {
  try {
    // This temporary key will be included in the log & cleared after the invocation
    logger.appendKeys({
      details: { userId: event.userId },
    });

    // ... your business logic
  } finally {
    logger.info('WIDE');
    logger.resetKeys();
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Logger } from '@aws-lambda-powertools/logger';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import middy from '@middy/core';

// Persistent attributes will be cached across invocations
const logger = new Logger({
  logLevel: 'info',
  persistentKeys: {
    environment: 'prod',
  },
});

export const handler = middy(
  async (event: { userId: string }, _context: unknown): Promise<void> => {
    // This temporary key will be included in the log & cleared after the invocation
    logger.appendKeys({
      details: { userId: event.userId },
    });

    // ... your business logic

    logger.info('WIDE');
  }
).use(injectLambdaContext(logger, { resetKeys: true }));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { Logger } from '@aws-lambda-powertools/logger';

// Persistent attributes will be cached across invocations
const logger = new Logger({
  logLevel: 'info',
  persistentKeys: {
    environment: 'prod',
  },
});

class Lambda implements LambdaInterface {
  @logger.injectLambdaContext({ resetKeys: true })
  public async handler(
    event: { userId: string },
    _context: unknown
  ): Promise<void> {
    // This temporary key will be included in the log & cleared after the invocation
    logger.appendKeys({
      details: { userId: event.userId },
    });

    // ... your business logic

    logger.info('WIDE');
  }
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction); // (1)!
  1. Binding your handler method allows your handler to access this within the class methods.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "environment": "prod",
    "cold_start": true,
    "userId": "123456789012",
    "foo": "bar",
    "function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function",
    "function_memory_size": 128,
    "function_name": "foo-bar-function",
    "function_request_id": "abcdef123456abcdef123456",
    "level": "INFO",
    "message": "WIDE",
    "service": "hello-world",
    "timestamp": "2021-12-12T22:32:54.670Z",
    "xray_trace_id": "1-5759e988-bd862e3fe1be46a994272793"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "environment": "prod",
    "cold_start": false,
    "userId": "210987654321",
    "function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function",
    "function_memory_size": 128,
    "function_name": "foo-bar-function",
    "function_request_id": "abcdef123456abcdef123456",
    "level": "INFO",
    "message": "WIDE",
    "service": "hello-world",
    "timestamp": "2021-12-12T22:40:23.120Z",
    "xray_trace_id": "1-5759e988-bd862e3fe1be46a994272793"
}

Logging errors

You can log errors by using the error method and pass the error object as parameter. The error will be logged with default key name error, but you can also pass your own custom key name.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

export const handler = async (
  _event: unknown,
  _context: unknown
): Promise<void> => {
  try {
    throw new Error('Unexpected error #1');
  } catch (error) {
    // Log information about the error using the default "error" key
    logger.error('This is the first error', error as Error);
  }

  try {
    throw new Error('Unexpected error #2');
  } catch (error) {
    // Log information about the error using a custom "myCustomErrorKey" key
    logger.error('This is the second error', {
      myCustomErrorKey: error as Error,
    });
  }
};

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
    "level": "ERROR",
    "message": "This is the first error",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:12:39.345Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "error": {
        "name": "Error",
        "location": "/path/to/my/source-code/my-service/handler.ts:18",
        "message": "Unexpected error #1",
        "stack": "Error: Unexpected error #1    at lambdaHandler (/path/to/my/source-code/my-service/handler.ts:18:11)    at Object.<anonymous> (/path/to/my/source-code/my-service/handler.ts:35:1)    at Module._compile (node:internal/modules/cjs/loader:1108:14)    at Module.m._compile (/path/to/my/source-code/node_modules/ts-node/src/index.ts:1371:23)    at Module._extensions..js (node:internal/modules/cjs/loader:1137:10)    at Object.require.extensions.<computed> [as .ts] (/path/to/my/source-code/node_modules/ts-node/src/index.ts:1374:12)    at Module.load (node:internal/modules/cjs/loader:973:32)    at Function.Module._load (node:internal/modules/cjs/loader:813:14)    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)    at main (/path/to/my/source-code/node_modules/ts-node/src/bin.ts:331:12)"
    }
}
{
    "level": "ERROR",
    "message": "This is the second error",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:12:39.377Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "myCustomErrorKey": {
        "name": "Error",
        "location": "/path/to/my/source-code/my-service/handler.ts:24",
        "message": "Unexpected error #2",
        "stack": "Error: Unexpected error #2    at lambdaHandler (/path/to/my/source-code/my-service/handler.ts:24:11)    at Object.<anonymous> (/path/to/my/source-code/my-service/handler.ts:35:1)    at Module._compile (node:internal/modules/cjs/loader:1108:14)    at Module.m._compile (/path/to/my/source-code/node_modules/ts-node/src/index.ts:1371:23)    at Module._extensions..js (node:internal/modules/cjs/loader:1137:10)    at Object.require.extensions.<computed> [as .ts] (/path/to/my/source-code/node_modules/ts-node/src/index.ts:1374:12)    at Module.load (node:internal/modules/cjs/loader:973:32)    at Function.Module._load (node:internal/modules/cjs/loader:813:14)    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)    at main (/path/to/my/source-code/node_modules/ts-node/src/bin.ts:331:12)"
    }
}

Logging errors and log level

You can also log errors using the warn, info, and debug methods. Be aware of the log level though, you might miss those errors when analyzing the log later depending on the log level configuration.

Advanced

Log levels

The default log level is INFO and can be set using the logLevel constructor option or by using the POWERTOOLS_LOG_LEVEL environment variable.

We support the following log levels:

Level Numeric value
TRACE 6
DEBUG 8
INFO 12
WARN 16
ERROR 20
CRITICAL 24
SILENT 28

You can access the current log level by using the getLevelName() method. This method returns the name of the current log level as a string. If you want to change the log level at runtime, you can use the setLogLevel() method. This method accepts a string value that represents the log level you want to set, both lower and upper case values are supported.

1
2
3
4
5
6
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

logger.getLevelName(); // returns "INFO"
logger.setLogLevel('DEBUG');

If you want to access the numeric value of the current log level, you can use the level property. For example, if the current log level is INFO, logger.level property will return 12.

Silencing logs

The SILENT log level provides a simple and efficient way to suppress all log messages without the need to modify your code. When you set this log level, all log messages, regardless of their severity, will be silenced.

This feature is useful when you want to have your code instrumented to produce logs, but due to some requirement or business decision, you prefer to not emit them.

By setting the log level to SILENT, which can be done either through the logLevel constructor option or by using the POWERTOOLS_LOG_LEVEL environment variable, you can easily suppress all logs as needed.

Note

Use the SILENT log level with care, as it can make it more challenging to monitor and debug your application. Therefore, we advise using this log level judiciously.

AWS Lambda Advanced Logging Controls (ALC)

With AWS Lambda Advanced Logging Controls (ALC), you can control the output format of your logs as either TEXT or JSON and specify the minimum accepted log level for your application.

Regardless of the output format setting in Lambda, we will always output JSON formatted logging messages.

When you have this feature enabled, log messages that don’t meet the configured log level are discarded by Lambda. For example, if you set the minimum log level to WARN, you will only receive WARN and ERROR messages in your AWS CloudWatch Logs, all other log levels will be discarded by Lambda.

sequenceDiagram
    title Lambda ALC allows WARN logs only
    participant Lambda service
    participant Lambda function
    participant Application Logger

    Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN"
    Lambda service->>Lambda function: Invoke (event)
    Lambda function->>Lambda function: Calls handler
    Lambda function->>Application Logger: logger.warn("Something happened")
    Lambda function-->>Application Logger: logger.debug("Something happened")
    Lambda function-->>Application Logger: logger.info("Something happened")

    Lambda service->>Lambda service: DROP INFO and DEBUG logs

    Lambda service->>CloudWatch Logs: Ingest error logs

Priority of log level settings in Powertools for AWS Lambda

When the Advanced Logging Controls feature is enabled, we are unable to increase the minimum log level below the AWS_LAMBDA_LOG_LEVEL environment variable value, see AWS Lambda service documentation for more details.

We prioritise log level settings in this order:

  1. AWS_LAMBDA_LOG_LEVEL environment variable
  2. Setting the log level in code using the logLevel constructor option, or by calling the logger.setLogLevel() method
  3. POWERTOOLS_LOG_LEVEL environment variable

In the event you have set a log level in Powertools to a level that is lower than the ACL setting, we will output a warning log message informing you that your messages will be discarded by Lambda.

Reordering log keys position

You can change the order of standard Logger keys or any keys that will be appended later at runtime via the logRecordOrder parameter.

Note

This feature is available only in the default log formatter and not with custom log formatters.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
  serviceName: 'serverlessAirline',
  logRecordOrder: ['timestamp', 'additionalKey'],
});

export const handler = async (): Promise<void> => {
  logger.info('Hello, World!', {
    additionalKey: 'additionalValue',
  });
};
1
2
3
4
5
6
7
8
9
{
  "timestamp": "2024-09-03T02:59:06.603Z",
  "additionalKey": "additionalValue",
  "level": "INFO",
  "message": "Hello, World!",
  "sampling_rate": 0,
  "service": "serverlessAirline",
  "xray_trace_id": "1-66d67b7a-79bc7b2346b32af01b437cf8"
}

Setting timestamp to custom Timezone

By default, Logger emits records with the default Lambda timestamp in UTC, i.e. 2016-06-20T12:08:10.000Z

If you prefer to log in a specific timezone, you can configure it by setting the TZ environment variable. You can do this either as an environment variable or directly within your Lambda function settings.

Click here for a comprehensive list of available Lambda environment variables.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({ serviceName: 'serverlessAirline' });

export const handler = async (): Promise<void> => {
  logger.info('Hello, World!');

  process.env.TZ = 'Europe/Rome';

  const childLogger = logger.createChild();

  childLogger.info('Ciao, Mondo!');
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[
  {
    "level": "INFO",
    "message": "Hello, World!",
    "sampling_rate": 0,
    "service": "serverlessAirline",
    "timestamp": "2024-07-01T11:00:37.886Z",
    "xray_trace_id": "1-66828c55-2bb635c65eb609c820ebe7bc"
  },
  {
    "level": "INFO",
    "message": "Ciao, Mondo!",
    "sampling_rate": 0,
    "service": "serverlessAirline",
    "timestamp": "2024-07-01T13:00:37.934+02:00",
    "xray_trace_id": "1-66828c55-2bb635c65eb609c820ebe7bc"
  }
]

Using multiple Logger instances across your code

The createChild method allows you to create a child instance of the Logger, which inherits all of the attributes from its parent. You have the option to override any of the settings and attributes from the parent logger, including its settings, any extra keys, and the log formatter.

Once a child logger is created, the logger and its parent will act as separate instances of the Logger class, and as such any change to one won't be applied to the other.

The following example shows how to create multiple Loggers that share service name and persistent attributes while specifying different logging levels within a single Lambda invocation. As the result, only ERROR logs with all the inherited attributes will be displayed in CloudWatch Logs from the child logger, but all logs emitted will have the same service name and persistent attributes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Logger } from '@aws-lambda-powertools/logger';

// This logger has a service name, some persistent attributes
// and log level set to INFO
const logger = new Logger({
  serviceName: 'serverlessAirline',
  logLevel: 'INFO',
  persistentLogAttributes: {
    aws_account_id: '123456789012',
    aws_region: 'eu-west-1',
  },
});

// This other logger inherits all the parent's attributes
// but the log level, which is now set to ERROR
const childLogger = logger.createChild({
  logLevel: 'ERROR',
});

export const handler = async (
  _event: unknown,
  _context: unknown
): Promise<void> => {
  logger.info('This is an INFO log, from the parent logger');
  logger.error('This is an ERROR log, from the parent logger');

  childLogger.info('This is an INFO log, from the child logger');
  childLogger.error('This is an ERROR log, from the child logger');
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
    "level": "INFO",
    "message": "This is an INFO log, from the parent logger",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:32:54.667Z",
    "aws_account_id":"123456789012",
    "aws_region":"eu-west-1",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "ERROR",
    "message": "This is an ERROR log, from the parent logger",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:32:54.670Z",
    "aws_account_id":"123456789012",
    "aws_region":"eu-west-1",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "ERROR",
    "message": "This is an ERROR log, from the child logger",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:32:54.670Z",
    "aws_account_id":"123456789012",
    "aws_region":"eu-west-1",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}

Sampling debug logs

Use sampling when you want to dynamically change your log level to DEBUG based on a percentage of your concurrent/cold start invocations.

You can use values ranging from 0 to 1 (100%) when setting the sampleRateValue constructor option or POWERTOOLS_LOGGER_SAMPLE_RATE env var.

When is this useful?

Let's imagine a sudden spike increase in concurrency triggered a transient issue downstream. When looking into the logs you might not have enough information, and while you can adjust log levels it might not happen again.

This feature takes into account transient issues where additional debugging information can be useful.

Sampling decision happens at the Logger initialization. This means sampling may happen significantly more or less than depending on your traffic patterns, for example a steady low number of invocations and thus few cold starts.

Note

Open a feature request if you want Logger to calculate sampling for every invocation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { Logger } from '@aws-lambda-powertools/logger';

// Notice the log level set to 'ERROR'
const logger = new Logger({
  logLevel: 'ERROR',
  sampleRateValue: 0.5,
});

export const handler = async (
  _event: unknown,
  _context: unknown
): Promise<void> => {
  // This log item (equal to log level 'ERROR') will be printed to standard output
  // in all Lambda invocations
  logger.error('This is an ERROR log');

  // These log items (below the log level 'ERROR') have ~50% chance
  // of being printed in a Lambda invocation
  logger.debug('This is a DEBUG log that has 50% chance of being printed');
  logger.info('This is an INFO log that has 50% chance of being printed');
  logger.warn('This is a WARN log that has 50% chance of being printed');

  // Optional: refresh sample rate calculation on runtime
  // logger.refreshSampleRateCalculation();
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
    "level": "ERROR",
    "message": "This is an ERROR log",
    "sampling_rate": "0.5",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.334Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "DEBUG",
    "message": "This is a DEBUG log that has 50% chance of being printed",
    "sampling_rate": "0.5", 
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.337Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "INFO",
    "message": "This is an INFO log that has 50% chance of being printed",
    "sampling_rate": "0.5", 
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.338Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "WARN",
    "message": "This is a WARN log that has 50% chance of being printed",
    "sampling_rate": "0.5", 
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.338Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
1
2
3
4
5
6
7
8
{
    "level": "ERROR",
    "message": "This is an ERROR log",
    "sampling_rate": "0.5",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.334Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
    "level": "ERROR",
    "message": "This is an ERROR log",
    "sampling_rate": "0.5",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.334Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "DEBUG",
    "message": "This is a DEBUG log that has 50% chance of being printed",
    "sampling_rate": "0.5", 
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.337Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "INFO",
    "message": "This is an INFO log that has 50% chance of being printed",
    "sampling_rate": "0.5", 
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.338Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "WARN",
    "message": "This is a WARN log that has 50% chance of being printed",
    "sampling_rate": "0.5", 
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.338Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
1
2
3
4
5
6
7
8
{
    "level": "ERROR",
    "message": "This is an ERROR log",
    "sampling_rate": "0.5",
    "service": "serverlessAirline",
    "timestamp": "2021-12-12T22:59:06.334Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}

Custom Log formatter (Bring Your Own Formatter)

You can customize the structure (keys and values) of your log items by passing a custom log formatter, an object that implements the LogFormatter abstract class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';
import { MyCompanyLogFormatter } from './bringYourOwnFormatterClass';

const logger = new Logger({
  logFormatter: new MyCompanyLogFormatter(),
  logLevel: 'DEBUG',
  serviceName: 'serverlessAirline',
  sampleRateValue: 0.5,
  persistentLogAttributes: {
    awsAccountId: process.env.AWS_ACCOUNT_ID,
    logger: {
      name: '@aws-lambda-powertools/logger',
      version: '0.0.1',
    },
  },
});

export const handler = async (
  _event: unknown,
  context: Context
): Promise<void> => {
  logger.addContext(context);

  logger.info('This is an INFO log', {
    correlationIds: { myCustomCorrelationId: 'foo-bar-baz' },
  });
};

This is how the MyCompanyLogFormatter (dummy name) would look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { LogFormatter, LogItem } from '@aws-lambda-powertools/logger';
import type {
  LogAttributes,
  UnformattedAttributes,
} from '@aws-lambda-powertools/logger/types';

// Replace this line with your own type
type MyCompanyLog = LogAttributes;

class MyCompanyLogFormatter extends LogFormatter {
  public formatAttributes(
    attributes: UnformattedAttributes,
    additionalLogAttributes: LogAttributes
  ): LogItem {
    const baseAttributes: MyCompanyLog = {
      message: attributes.message,
      service: attributes.serviceName,
      environment: attributes.environment,
      awsRegion: attributes.awsRegion,
      correlationIds: {
        awsRequestId: attributes.lambdaContext?.awsRequestId,
        xRayTraceId: attributes.xRayTraceId,
      },
      lambdaFunction: {
        name: attributes.lambdaContext?.functionName,
        arn: attributes.lambdaContext?.invokedFunctionArn,
        memoryLimitInMB: attributes.lambdaContext?.memoryLimitInMB,
        version: attributes.lambdaContext?.functionVersion,
        coldStart: attributes.lambdaContext?.coldStart,
      },
      logLevel: attributes.logLevel,
      timestamp: this.formatTimestamp(attributes.timestamp), // You can extend this function
      logger: {
        sampleRateValue: attributes.sampleRateValue,
      },
    };

    const logItem = new LogItem({ attributes: baseAttributes });
    logItem.addAttributes(additionalLogAttributes); // add any attributes not explicitly defined

    return logItem;
  }
}

export { MyCompanyLogFormatter };

This is how the printed log would look:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "message": "This is an INFO log",
    "service": "serverlessAirline",
    "awsRegion": "eu-west-1",
    "correlationIds": {
        "awsRequestId": "c6af9ac6-7b61-11e6-9a41-93e812345678",
        "xRayTraceId": "abcdef123456abcdef123456abcdef123456",
        "myCustomCorrelationId": "foo-bar-baz"
    },
    "lambdaFunction": {
        "name": "shopping-cart-api-lambda-prod-eu-west-1",
        "arn": "arn:aws:lambda:eu-west-1:123456789012:function:shopping-cart-api-lambda-prod-eu-west-1",
        "memoryLimitInMB": 128,
        "version": "$LATEST",
        "coldStart": true
    },
    "logLevel": "INFO",
    "timestamp": "2021-12-12T23:13:53.404Z",
    "logger": {
        "sampleRateValue": "0.5",
        "name": "aws-lambda-powertools-typescript",
        "version": "0.0.1"
    },
    "awsAccountId": "123456789012"
}

Custom Log formatter and Child loggers

It is not necessary to pass the LogFormatter each time a child logger is created. The parent's LogFormatter will be inherited by the child logger.

Bring your own JSON serializer

You can extend the default JSON serializer by passing a custom serializer function to the Logger constructor, using the jsonReplacerFn option. This is useful when you want to customize the serialization of specific values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Logger } from '@aws-lambda-powertools/logger';
import type { CustomReplacerFn } from '@aws-lambda-powertools/logger/types';

const jsonReplacerFn: CustomReplacerFn = (_: string, value: unknown) =>
  value instanceof Set ? [...value] : value;

const logger = new Logger({ serviceName: 'serverlessAirline', jsonReplacerFn });

export const handler = async (): Promise<void> => {
  logger.info('Serialize with custom serializer', {
    serializedValue: new Set([1, 2, 3]),
  });
};
1
2
3
4
5
6
7
8
9
{
  "level": "INFO",
  "message": "Serialize with custom serializer",
  "sampling_rate": 0,
  "service": "serverlessAirline",
  "timestamp": "2024-07-07T09:52:14.212Z",
  "xray_trace_id": "1-668a654d-396c646b760ee7d067f32f18",
  "serializedValue": [1, 2, 3]
}

By default, Logger uses JSON.stringify() to serialize log items and a custom replacer function to serialize common unserializable values such as BigInt, circular references, and Error objects.

When you extend the default JSON serializer, we will call your custom serializer function before the default one. This allows you to customize the serialization while still benefiting from the default behavior.

Testing your code

Inject Lambda Context

When unit testing your code that makes use of logger.addContext() or injectLambdaContext middleware and decorator, you can optionally pass a dummy Lambda Context if you want your logs to contain this information.

This is a sample that provides the minimum information necessary for Logger to inject context data:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import type { Context } from 'aws-lambda';
import { describe, expect, it } from 'vitest';
declare const handler: (event: unknown, context: Context) => Promise<true>;

const context = {
  callbackWaitsForEmptyEventLoop: true,
  functionVersion: '$LATEST',
  functionName: 'foo-bar-function',
  memoryLimitInMB: '128',
  logGroupName: '/aws/lambda/foo-bar-function-123456abcdef',
  logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456',
  invokedFunctionArn:
    'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function',
  awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678',
  getRemainingTimeInMillis: () => 1234,
  done: () => console.log('Done!'),
  fail: () => console.log('Failed!'),
  succeed: () => console.log('Succeeded!'),
} satisfies Context;

describe('MyUnitTest', () => {
  it('invokes the handler successfully', async () => {
    // Prepare
    const testEvent = { test: 'test' };

    // Act
    const result = await handler(testEvent, context);

    // Assert
    expect(result).toBe(true);
  });
});

Suppress logs

When unit testing your code with Jest or Vitest you can use the POWERTOOLS_DEV environment variable in conjunction with the --silent CLI option to suppress logs from Logger.

Disabling logs while testing with Vitest
1
export POWERTOOLS_DEV=true && npx vitest --silent

Alternatively, you can also set the POWERTOOLS_DEV environment variable to true in your test setup file, or in a hoisted block at the top of your test file.