Skip to content

Logger

Do not use this library in production

AWS Lambda Powertools for TypeScript is currently released as a beta developer preview and is intended strictly for feedback purposes only.
This version is not stable, and significant breaking changes might incur as part of the upcoming production-ready release.

Do not use this library for production workloads.

Logger provides an opinionated logger with output structured as JSON.

Key features

  • Capture key fields from Lambda context, cold start and structures logging output as JSON
  • Log Lambda context when instructed (disabled by default)
  • Log sampling prints all logs for a percentage of invocations (disabled by default)
  • Append additional keys to structured log at any point in time

Getting started

Installation

Install the library in your project:

1
npm install @aws-lambda-powertools/logger

Utility settings

The library requires two settings. You can set them as environment variables, or pass them in the constructor.

These settings will be used across all logs emitted:

Setting Description Environment variable Constructor parameter
Logging level Sets how verbose Logger should be (INFO, by default). Supported values are: DEBUG, INFO, WARN, ERROR LOG_LEVEL logLevel
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 serviceName

For a complete list of supported environment variables, refer to this section.

Example using AWS Serverless Application Model (SAM)

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

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

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

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 shopping-cart-api 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 When tracing is enabled, it shows X-Ray Trace ID
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

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-central-1
function_memory_size: number 128
function_arn: string arn:aws:lambda:eu-central-1:123456789012:function:shopping-cart-api-lambda-prod-eu-central-1
function_request_id: string c6af9ac6-7b61-11e6-9a41-93e812345678

Method 1, using a Middy middleware:

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

const logger = new Logger();

const lambdaHandler = async () => {
    logger.info("This is an INFO log with some context");
};

const handler = middy(lambdaHandler)
    .use(injectLambdaContext(logger));

Method 2, calling the addContext method:

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

const logger = new Logger();

const lambdaHandler = async (_event, context) => {

    logger.addContext(context);

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

};

Method 3, using a class decorator:

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

const logger = new Logger();

class Lambda {

    @logger.injectLambdaContext()
    public handler() {
        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-central-1:123456789012:function:shopping-cart-api-lambda-prod-eu-central-1",
    "function_memory_size": 128,
    "function_request_id": "c6af9ac6-7b61-11e6-9a41-93e812345678",
    "function_name": "shopping-cart-api-lambda-prod-eu-central-1",
    "level": "INFO",
    "message": "This is an INFO log with some context",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T21:21:08.921Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}

Appending persistent additional log keys and values

You can append additional persistent keys and values in the logs generated during a Lambda invocation using either mechanism:

  • Via the Logger's appendKeys method, for all log items generated after calling this method
  • Passing them in the Logger's constructor
 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
import { Logger } from "@aws-lambda-powertools/logger";

// Add persistent log keys via the constructor
const logger = new Logger({
    persistentLogAttributes: { 
        aws_account_id: "123456789012",
        aws_region: "eu-central-1",
        logger: {
            name: "@aws-lambda-powertools/logger",
            version: "0.0.1",
        }
    }
});

// OR add persistent log keys to an existing Logger instance with the appendKeys method:
// logger.appendKeys({
//     aws_account_id: "123456789012",
//     aws_region: "eu-central-1",
//     logger: {
//         name: "@aws-lambda-powertools/logger",
//         version: "0.0.1",
//     }
// });    

const lambdaHandler: Handler = async () => {

    // This info log will print all extra custom attributes added above
    // Extra attributes: logger object with name and version of the logger library, awsAccountId, awsRegion
    logger.info("This is an INFO log");
    logger.info("This is another INFO log");

    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
{
    "level": "INFO",
    "message": "This is an INFO log",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T21:49:58.084Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "aws_account_id": "123456789012",
    "aws_region": "eu-central-1",
    "logger": { 
        "name": "@aws-lambda-powertools/logger",
        "version": "0.0.1"
    }
}
{
    "level": "INFO",
    "message": "This is another INFO log",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T21:49:58.088Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "aws_account_id": "123456789012",
    "aws_region": "eu-central-1",
    "logger": { 
        "name": "@aws-lambda-powertools/logger",
        "version": "0.0.1"
    }
}

Logger will automatically ignore any key with an undefined value

Appending additional log keys and values to a single log item

You can append additional keys and values in a single log item passing them as parameters.

 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
import { Logger } from "@aws-lambda-powertools/logger";

const logger = new Logger();

const lambdaHandler = async () => {

    const myImportantVariable = {
        foo: "bar"
    };

    // Pass additional keys and values 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
    logger.info("This is a log with 2 extra variables",
        { data: myImportantVariable },
        { correlationIds: { myCustomCorrelationId: "foo-bar-baz" }}
    );

    return {
        foo: "bar"
    };

};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "level": "INFO",
    "message": "This is a log with an extra variable",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:06:17.463Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "data": { foo: "bar" }
}
{
    "level": "INFO",
    "message": "This is a log with 2 extra variables",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:06:17.466Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456",
    "data": { "foo": "bar" },
    "correlationIds": { "myCustomCorrelationId": "foo-bar-baz" }
}

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
import { Logger } from "@aws-lambda-powertools/logger";

const logger = new Logger();

const lambdaHandler = async () => {

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

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

};
 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 an ERROR log #1",
    "service": "shopping-cart-api",
    "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": "This is the first error",
        "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 an ERROR log #2",
    "service": "shopping-cart-api",
    "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": "This is the second error",
        "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)"
    }
}

Advanced

Using multiple Logger instances across your code

Logger supports quick instance cloning via the createChild method. This can be useful for example if you want to enable multiple Loggers with different logging levels in the same Lambda invocation.

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

// With this logger, all the INFO logs will be printed
const logger = new Logger({
    logLevel: "INFO"
});

// With this logger, only the ERROR logs will be printed
const childLogger = parentLogger.createChild({
    logLevel: "ERROR"
});

const lambdaHandler: Handler = async () => {

    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
{
    "level": "INFO",
    "message": "This is an INFO log, from the parent logger",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:32:54.667Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "ERROR",
    "message": "This is an ERROR log, from the parent logger",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:32:54.670Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "ERROR",
    "message": "This is an ERROR log, from the child logger",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:32:54.670Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}

Sampling logs

Use sampling when you want to print all the log items generated in your code, based on a percentage of your concurrent/cold start invocations.

You can do that by setting a "sample rate", a float value ranging from 0.0 (0%) to 1 (100%), by using a POWERTOOLS_LOGGER_SAMPLE_RATE env var or passing the sampleRateValue parameter in the Logger constructor. This number represents the probability that a Lambda invocation will print all the log items regardless of the log level setting.

For example, by setting the "sample rate" to 0.5, roughly 50% of your lambda invocations will print all the log items, including the debug ones.

When is this useful?

In production, to avoid log data pollution and reduce CloudWatch costs, developers are encouraged to use the logger with logLevel equal to ERROR or WARN. This means that only errors or warnings will be printed.

However, it might still be useful to print all the logs (including debug ones) of a very small percentage of invocations to have a better understanding of the behaviour of your code in production even when there are no errors.

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.

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

const logger = new Logger({
    logLevel: "ERROR",
    sampleRateValue: 0.5
});

const lambdaHandler = async () => {

    // 0.5 means that you have 50% chance that these logs will be printed
    logger.info("This is INFO log #1");
    logger.info("This is INFO log #2");
    logger.info("This is INFO log #3");
    logger.info("This is INFO log #4");

    // 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": "INFO",
    "message": "This is INFO log #1",
    "sampling_rate": "0.5",
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:59:06.334Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "INFO",
    "message": "This is INFO log #2",
    "sampling_rate": "0.5", 
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:59:06.337Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "INFO",
    "message": "This is INFO log #3",
    "sampling_rate": "0.5", 
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:59:06.338Z",
    "xray_trace_id": "abcdef123456abcdef123456abcdef123456"
}
{
    "level": "INFO",
    "message": "This is INFO log #4",
    "sampling_rate": "0.5", 
    "service": "shopping-cart-api",
    "timestamp": "2021-12-12T22:59:06.338Z",
    "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 extends 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
import { Logger } from "@aws-lambda-powertools/logger";
import { MyCompanyLogFormatter } from "./utils/formatters/MyCompanyLogFormatter";

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

const lambdaHandler: Handler = async (event, context) => {
    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
import { LogFormatter } from "@aws-lambda-powertools/logger";
import { 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): MyCompanyLog {
        return {
            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,
            },
        };
    }

}

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": "shopping-cart-api",
        "awsRegion": "eu-central-1",
        "correlationIds": {
            "awsRequestId": "c6af9ac6-7b61-11e6-9a41-93e812345678",
            "xRayTraceId": "abcdef123456abcdef123456abcdef123456",
            "myCustomCorrelationId": "foo-bar-baz"
        },
        "lambdaFunction": {
            "name": "shopping-cart-api-lambda-prod-eu-central-1",
            "arn": "arn:aws:lambda:eu-central-1:123456789012:function:shopping-cart-api-lambda-prod-eu-central-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"
    }

Last update: 2021-12-28
Back to top