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.
The Logger utility must always be instantiated outside of the Lambda handler. In 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.
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 910
import{Logger}from'@aws-lambda-powertools/logger';// Logger parameters fetched from the environment variables (see template.yaml tab)constlogger=newLogger();// You can also pass the parameters in the constructor// const logger = new Logger({// logLevel: 'WARN',// serviceName: 'serverlessAirline'// });
import{Logger}from'@aws-lambda-powertools/logger';constlogger=newLogger();exportconsthandler=async(_event,context):Promise<void>=>{logger.addContext(context);logger.info('This is an INFO log with some context');};
import{Logger,injectLambdaContext}from'@aws-lambda-powertools/logger';importmiddyfrom'@middy/core';constlogger=newLogger();constlambdaHandler=async(_event:any,_context:any):Promise<void>=>{logger.info('This is an INFO log with some context');};exportconsthandler=middy(lambdaHandler).use(injectLambdaContext(logger));
1 2 3 4 5 6 7 8 910111213141516
import{Logger}from'@aws-lambda-powertools/logger';import{LambdaInterface}from'@aws-lambda-powertools/commons';constlogger=newLogger();classLambdaimplementsLambdaInterface{// Decorate your handler class method@logger.injectLambdaContext()publicasynchandler(_event:any,_context:any):Promise<void>{logger.info('This is an INFO log with some context');}}exportconstmyFunction=newLambda();exportconsthandler=myFunction.handler;
In each case, the printed log will look like this:
1 2 3 4 5 6 7 8 9101112
{"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"}
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
import{Logger}from'@aws-lambda-powertools/logger';// Add persistent log keys via the constructorconstlogger=newLogger({persistentLogAttributes:{aws_account_id:'123456789012',aws_region:'eu-west-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-west-1',// logger: {// name: '@aws-lambda-powertools/logger',// version: '0.0.1',// }// }); exportconsthandler=async(_event:any,_context:any):Promise<unknown>=>{// This info log will print all extra custom attributes added above// Extra attributes: logger object with name and version of the logger library, awsAccountId, awsRegionlogger.info('This is an INFO log');logger.info('This is another INFO log');return{foo:'bar'};};
{"level":"INFO","message":"This is an INFO log","service":"serverlessAirline","timestamp":"2021-12-12T21:49:58.084Z","xray_trace_id":"abcdef123456abcdef123456abcdef123456","aws_account_id":"123456789012","aws_region":"eu-west-1","logger":{"name":"@aws-lambda-powertools/logger","version":"0.0.1"}}{"level":"INFO","message":"This is another INFO log","service":"serverlessAirline","timestamp":"2021-12-12T21:49:58.088Z","xray_trace_id":"abcdef123456abcdef123456abcdef123456","aws_account_id":"123456789012","aws_region":"eu-west-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.
Pass a string for logging it with default key name extra. Alternatively, pass one or multiple objects with custom keys.
If you already have an object containing a message key and an additional property, you can pass this object directly.
import{Logger}from'@aws-lambda-powertools/logger';constlogger=newLogger();exportconsthandler=async(_event:any,_context:any):Promise<unknown>=>{constmyImportantVariable={foo:'bar'};// Pass additional keys and values in single log items// As second parameterlogger.info('This is a log with an extra variable',{data:myImportantVariable});// You can also pass multiple parameterslogger.info('This is a log with 2 extra variables',{data:myImportantVariable},{correlationIds:{myCustomCorrelationId:'foo-bar-baz'}});// Simply pass a string for logging additional datalogger.info('This is a log with additional string value','string value');// Directly passing an object containing both the message and the additional infoconstlogObject={message:'This is a log message',additionalValue:42};logger.info(logObject);return{foo:'bar'};};
{"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 2 extra variables","service":"serverlessAirline","timestamp":"2021-12-12T22:06:17.466Z","xray_trace_id":"abcdef123456abcdef123456abcdef123456","data":{"foo":"bar"},"correlationIds":{"myCustomCorrelationId":"foo-bar-baz"}}{"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}
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 9101112131415161718192021
import{Logger}from'@aws-lambda-powertools/logger';constlogger=newLogger();exportconsthandler=async(_event:any,_context:any):Promise<void>=>{try{thrownewError('Unexpected error #1');}catch(error){// Log information about the error using the default "error" keylogger.error('This is the first error',errorasError);}try{thrownewError('Unexpected error #2');}catch(error){// Log information about the error using a custom "myCustomErrorKey" keylogger.error('This is the second error',{myCustomErrorKey:errorasError});}};
{"level":"ERROR","message":"This is an ERROR log #1","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":"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":"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":"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)"}}
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.
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 9101112131415161718192021
import{Logger}from'@aws-lambda-powertools/logger';// With this logger, all the INFO logs will be printedconstlogger=newLogger({logLevel:'INFO'});// With this logger, only the ERROR logs will be printedconstchildLogger=logger.createChild({logLevel:'ERROR'});exportconsthandler=async(_event:any,_context:any):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 9101112131415161718192021
{"level":"INFO","message":"This is an INFO log, from the parent logger","service":"serverlessAirline","timestamp":"2021-12-12T22:32:54.667Z","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","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","xray_trace_id":"abcdef123456abcdef123456abcdef123456"}
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 9101112131415161718192021222324
import{Logger}from'@aws-lambda-powertools/logger';// Notice the log level set to 'ERROR'constlogger=newLogger({logLevel:'ERROR',sampleRateValue:0.5});exportconsthandler=async(_event:any,_context:any):Promise<void>=>{// This log item (equal to log level 'ERROR') will be printed to standard output// in all Lambda invocationslogger.error('This is an ERROR log');// These log items (below the log level 'ERROR') have ~50% chance // of being printed in a Lambda invocationlogger.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();};
{"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"}
12345678
{"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":"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"}
12345678
{"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"}
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 9101112131415161718192021222324
import{Logger}from'@aws-lambda-powertools/logger';import{MyCompanyLogFormatter}from'./utils/formatters/MyCompanyLogFormatter';constlogger=newLogger({logFormatter:newMyCompanyLogFormatter(),logLevel:'DEBUG',serviceName:'serverlessAirline',sampleRateValue:0.5,persistentLogAttributes:{awsAccountId:process.env.AWS_ACCOUNT_ID,logger:{name:'@aws-lambda-powertools/logger',version:'0.0.1'}},});exportconsthandler=async(event,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:
import{LogFormatter}from'@aws-lambda-powertools/logger';import{LogAttributes,UnformattedAttributes}from'@aws-lambda-powertools/logger/lib/types';// Replace this line with your own typetypeMyCompanyLog=LogAttributes;classMyCompanyLogFormatterextendsLogFormatter{publicformatAttributes(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 functionlogger:{sampleRateValue:attributes.sampleRateValue,},};}}export{MyCompanyLogFormatter};
This is how the printed log would look:
1 2 3 4 5 6 7 8 910111213141516171819202122232425
{"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"}
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 Jest sample that provides the minimum information necessary for Logger to inject context data: