diff --git a/.eslintrc.js b/.eslintrc.js
index c15fe9b23d..6ffb8193a4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,42 +1,63 @@
module.exports = {
env: {
- 'jest': true,
'browser': false,
+ 'es2020': true,
+ 'jest': true,
'node': true,
- 'es2020': true
},
- parser: '@typescript-eslint/parser',
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended'
],
+ ignorePatterns: ['tests/resources/*'],
+ parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
- 'no-console': 0,
- 'semi': [ 'error', 'always' ],
- 'newline-before-return': 2,
- 'indent': [ 'error', 2, { 'SwitchCase': 1 } ],
- 'quotes': [ 'error', 'single', { 'allowTemplateLiterals': true } ],
- 'object-curly-spacing': [ 'error', 'always' ],
+ '@typescript-eslint/ban-ts-ignore': ['off'],
+ '@typescript-eslint/camelcase': ['off'],
+ '@typescript-eslint/explicit-function-return-type': [ 'error', { 'allowExpressions': true } ],
+ '@typescript-eslint/explicit-member-accessibility': 'error',
+ '@typescript-eslint/indent': [ 'error', 2, { 'SwitchCase': 1 } ],
+ '@typescript-eslint/interface-name-prefix': ['off'],
+ '@typescript-eslint/member-delimiter-style': [ 'error', { 'multiline': { 'delimiter': 'none' } } ],
+ '@typescript-eslint/member-ordering': [ 'error', {
+ 'default': { 'memberTypes': [
+ 'signature',
+ 'public-field', // = ["public-static-field", "public-instance-field"]
+ 'protected-field', // = ["protected-static-field", "protected-instance-field"]
+ 'private-field', // = ["private-static-field", "private-instance-field"]
+ 'constructor',
+ 'public-method', // = ["public-static-method", "public-instance-method"]
+ 'protected-method', // = ["protected-static-method", "protected-instance-method"]
+ 'private-method' // = ["private-static-method", "private-instance-method"]
+ ] ,
+ 'order': 'alphabetically' }
+ } ],
+ '@typescript-eslint/no-explicit-any': 'error',
+ '@typescript-eslint/no-inferrable-types': ['off'],
+ '@typescript-eslint/no-unused-vars': [ 'error', { 'argsIgnorePattern': '^_' } ],
+ '@typescript-eslint/no-use-before-define': ['off'],
+ '@typescript-eslint/semi': [ 'error', 'always' ],
'array-bracket-spacing': [ 'error', 'always', { 'singleValue': false } ],
'arrow-body-style': [ 'error', 'as-needed' ],
'computed-property-spacing': [ 'error', 'never' ],
- 'no-multiple-empty-lines': [ 'error', { 'max': 1, 'maxBOF': 0 } ],
- 'prefer-arrow-callback': 'error',
'func-style': [ 'warn', 'expression' ],
- 'no-multi-spaces': [ 'error', { 'ignoreEOLComments': false } ],
+ 'indent': [ 'error', 2, { 'SwitchCase': 1 } ],
'keyword-spacing': 'error',
- '@typescript-eslint/semi': [ 'error', 'always' ],
- '@typescript-eslint/indent': [ 'error', 2, { 'SwitchCase': 1 } ],
- '@typescript-eslint/explicit-function-return-type': [ 'error', { 'allowExpressions': true } ],
- '@typescript-eslint/member-delimiter-style': [ 'error', { 'multiline': { 'delimiter': 'none' } } ],
- '@typescript-eslint/interface-name-prefix': ['off'],
- '@typescript-eslint/camelcase': ['off'],
- '@typescript-eslint/no-use-before-define': ['off'],
- '@typescript-eslint/ban-ts-ignore': ['off'],
- '@typescript-eslint/no-inferrable-types': ['off'],
- '@typescript-eslint/no-unused-vars': [ 'error', { 'argsIgnorePattern': '^_' } ],
- '@typescript-eslint/no-explicit-any': 'error',
- '@typescript-eslint/explicit-member-accessibility': 'error'
+ 'newline-before-return': 2,
+ 'no-console': 0,
+ 'no-multi-spaces': [ 'error', { 'ignoreEOLComments': false } ],
+ 'no-multiple-empty-lines': [ 'error', { 'max': 1, 'maxBOF': 0 } ],
+ 'object-curly-spacing': [ 'error', 'always' ],
+ 'prefer-arrow-callback': 'error',
+ 'quotes': [ 'error', 'single', { 'allowTemplateLiterals': true } ],
+ 'semi': [ 'error', 'always' ],
+ 'sort-imports': [ 'error', {
+ 'allowSeparatedGroups': true,
+ 'ignoreCase': true,
+ 'ignoreDeclarationSort': false,
+ 'ignoreMemberSort': true,
+ 'memberSyntaxSortOrder': [ 'all', 'single', 'multiple', 'none' ]
+ } ]
}
};
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8d99dd2744..982356526f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,11 +11,14 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install packages
- run: npm install
+ run: |
+ export NODE_ENV=dev
+ npm ci
+ npm run lerna-ci
- name: Run lint
- run: npm run lint
+ run: npm run lerna-lint
- name: Run tests
- run: npm run test
+ run: npm run lerna-test
- name: Report Coverage
if: ${{ github.event_name == 'pull_request' }}
uses: romeovs/lcov-reporter-action@v0.2.11
diff --git a/package-lock.json b/package-lock.json
index 04ed64eb91..3d0088480a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
+ "@types/aws-lambda": "^8.10.72",
"@types/jest": "^26.0.19",
"@types/node": "^14.14.16",
"@typescript-eslint/eslint-plugin": "^4.11.1",
@@ -4400,6 +4401,12 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "node_modules/@types/aws-lambda": {
+ "version": "8.10.72",
+ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.72.tgz",
+ "integrity": "sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA==",
+ "dev": true
+ },
"node_modules/@types/babel__core": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
@@ -19856,6 +19863,12 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "@types/aws-lambda": {
+ "version": "8.10.72",
+ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.72.tgz",
+ "integrity": "sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA==",
+ "dev": true
+ },
"@types/babel__core": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
diff --git a/package.json b/package.json
index faadf613cb..fa21e64e41 100644
--- a/package.json
+++ b/package.json
@@ -6,15 +6,15 @@
"types": "lib/",
"scripts": {
"commit": "commit",
- "ci": "lerna exec -- npm ci",
- "test": "lerna exec -- jest --coverage --detectOpenHandles",
- "build": "lerna exec -- tsc",
- "lint": "lerna exec -- eslint \"./{src,tests}/**/*.ts\"",
- "format": "lerna exec -- eslint --fix \"./{src,tests}/**/*.ts\"",
- "prepare": "lerna exec -- npm run build",
- "prepublishOnly": "lerna exec -- npm test && lerna exec -- npm run lint",
- "preversion": "lerna exec -- npm run lint",
- "version": "lerna exec -- npm run format && git add -A src",
+ "lerna-ci": "lerna exec -- npm ci",
+ "lerna-test": "lerna exec -- jest --coverage --detectOpenHandles",
+ "lerna-build": "lerna exec -- tsc",
+ "lerna-lint": "lerna exec -- eslint \"./{src,tests}/**/*.ts\"",
+ "lerna-format": "lerna exec -- eslint --fix \"./{src,tests}/**/*.ts\"",
+ "lerna-prepare": "lerna exec -- npm run build",
+ "lerna-prepublishOnly": "lerna exec -- npm test && lerna exec -- npm run lint",
+ "lerna-preversion": "lerna exec -- npm run lint",
+ "lerna-version": "lerna exec -- npm run format && git add -A src",
"postversion": "git push && git push --tags"
},
"repository": {
@@ -34,6 +34,7 @@
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
+ "@types/aws-lambda": "^8.10.72",
"@types/jest": "^26.0.19",
"@types/node": "^14.14.16",
"@typescript-eslint/eslint-plugin": "^4.11.1",
diff --git a/packages/logger/README.md b/packages/logger/README.md
new file mode 100644
index 0000000000..1bd086ee49
--- /dev/null
+++ b/packages/logger/README.md
@@ -0,0 +1,372 @@
+# `logger`
+
+## Usage
+
+### Getting started
+
+```typescript
+// Import the library
+import { Logger } from '../src';
+// When going public, it will be something like: import { Logger } from '@aws-lambda-powertools/logger';
+
+// Environment variables set for the Lambda
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+
+// Instantiate the Logger with default configuration
+const logger = new Logger();
+
+// Log with different levels
+logger.debug('This is a DEBUG log');
+logger.info('This is an INFO log');
+logger.warn('This is a WARN log');
+logger.error('This is an ERROR log');
+
+```
+
+
+ Click to expand and see the logs outputs
+
+```bash
+
+{
+ level: 'WARN',
+ message: 'This is a WARN log',
+ service: 'hello-world',
+ timestamp: '2021-03-13T18:02:49.280Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+{
+ level: 'ERROR',
+ message: 'This is an ERROR log',
+ service: 'hello-world',
+ timestamp: '2021-03-13T18:02:49.282Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+
+```
+
+
+
+### Capturing Lambda context info
+
+```typescript
+// Environment variables set for the Lambda
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+process.env.POWERTOOLS_CONTEXT_ENABLED = 'TRUE';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async (event, context) => {
+ logger.addContext(context); // This should be in a custom Middy middleware https://github.com/middyjs/middy
+
+ logger.debug('This is a DEBUG log');
+ logger.info('This is an INFO log');
+ logger.warn('This is a WARN log');
+ logger.error('This is an ERROR log');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+```
+
+
+ Click to expand and see the logs outputs
+
+```bash
+
+{
+ aws_request_id: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
+ cold_start: true,
+ lambda_function_arn: 'arn:aws:lambda:eu-central-1:123456789012:function:Example',
+ lambda_function_memory_size: 128,
+ lambda_function_name: 'foo-bar-function',
+ level: 'WARN',
+ message: 'This is a WARN log',
+ service: 'hello-world',
+ timestamp: '2021-03-13T18:11:46.919Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+{
+ aws_request_id: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
+ cold_start: true,
+ lambda_function_arn: 'arn:aws:lambda:eu-central-1:123456789012:function:Example',
+ lambda_function_memory_size: 128,
+ lambda_function_name: 'foo-bar-function',
+ level: 'ERROR',
+ message: 'This is an ERROR log',
+ service: 'hello-world',
+ timestamp: '2021-03-13T18:11:46.921Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+
+```
+
+
+
+### Appending additional keys
+
+```typescript
+
+// Environment variables set for the Lambda
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async () => {
+
+ // Pass a custom correlation ID
+ logger.warn('This is a WARN log', { correlationIds: { myCustomCorrelationId: 'foo-bar-baz' } });
+
+ // Pass an error that occurred
+ logger.error('This is an ERROR log', new Error('Something bad happened!'));
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+```
+
+
+ Click to expand and see the logs outputs
+
+```bash
+
+{
+ level: 'WARN',
+ message: 'This is a WARN log',
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:21:28.423Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456',
+ correlationIds: { myCustomCorrelationId: 'foo-bar-baz' }
+}
+{
+ level: 'ERROR',
+ message: 'This is an ERROR log',
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:21:28.426Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456',
+ error: {
+ name: 'Error',
+ message: 'Something bad happened!',
+ stack: 'Error: Something bad happened!\n' +
+ ' at lambdaHandler (/Users/username/Workspace/projects/aws-lambda-powertools-typescript/packages/logger/examples/additional-keys.ts:22:40)\n' +
+ ' at Object. (/Users/username/Workspace/projects/aws-lambda-powertools-typescript/packages/logger/examples/additional-keys.ts:30:1)\n' +
+ ' at Module._compile (node:internal/modules/cjs/loader:1108:14)\n' +
+ ' at Module.m._compile (/Users/username/Workspace/projects/aws-lambda-powertools-typescript/packages/logger/node_modules/ts-node/src/index.ts:1056:23)\n' +
+ ' at Module._extensions..js (node:internal/modules/cjs/loader:1137:10)\n' +
+ ' at Object.require.extensions. [as .ts] (/Users/username/Workspace/projects/aws-lambda-powertools-typescript/packages/logger/node_modules/ts-node/src/index.ts:1059:12)\n' +
+ ' at Module.load (node:internal/modules/cjs/loader:973:32)\n' +
+ ' at Function.Module._load (node:internal/modules/cjs/loader:813:14)\n' +
+ ' at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)\n' +
+ ' at main (/Users/username/Workspace/projects/aws-lambda-powertools-typescript/packages/logger/node_modules/ts-node/src/bin.ts:198:14)'
+ }
+}
+```
+
+
+### Reusing Logger across your code
+
+```typescript
+// Environment variables set for the Lambda
+process.env.LOG_LEVEL = 'INFO';
+
+const parentLogger = new Logger();
+
+const childLogger = parentLogger.createChild({
+ logLevel: 'ERROR'
+});
+
+const lambdaHandler: Handler = async () => {
+
+ parentLogger.info('This is an INFO log, from the parent logger');
+ parentLogger.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');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+```
+
+
+ Click to expand and see the logs outputs
+
+```bash
+
+{
+ level: 'INFO',
+ message: 'This is an INFO log, from the parent logger',
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:33:41.128Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+{
+ level: 'ERROR',
+ message: 'This is an ERROR log, from the parent logger',
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:33:41.130Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+{
+ level: 'ERROR',
+ message: 'This is an ERROR log, from the child logger',
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:33:41.131Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+
+```
+
+
+
+### Sampling debug logs
+
+```typescript
+
+// Environment variables set for the Lambda
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async () => {
+
+ 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');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+
+```
+
+
+ Click to expand and see the logs outputs
+
+```bash
+
+{
+ level: 'INFO',
+ message: 'This is INFO log #2',
+ sampling_rate: 0.5,
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:45:06.093Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+{
+ level: 'INFO',
+ message: 'This is INFO log #4',
+ sampling_rate: 0.5,
+ service: 'hello-world',
+ timestamp: '2021-03-13T20:45:06.096Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+}
+
+```
+
+
+
+## Custom logger options: log level, service name, sample rate value, log attributes, variables source, log format
+
+```typescript
+
+process.env.CUSTOM_ENV = 'prod';
+process.env.POWERTOOLS_CONTEXT_ENABLED = 'TRUE';
+
+// Custom configuration service for variables, and custom formatter to comply to different log JSON schema
+import { CustomConfigService } from './config/CustomConfigService';
+import { CustomLogFormatter } from './formatters/CustomLogFormatter';
+
+const logger = new Logger({
+ logLevel: 'INFO', // Override options
+ serviceName: 'foo-bar',
+ sampleRateValue: 0.00001,
+ customAttributes: { // Custom attributes that will be added in every log
+ awsAccountId: '123456789012',
+ logger: {
+ name: powertool.name,
+ version: powertool.version,
+ }
+ },
+ logFormatter: new CustomLogFormatter(), // Custom log formatter to print the log in a custom format (JSON schema)
+ customConfigService: new CustomConfigService() // Custom config service, that could be used for AppConfig for example
+});
+
+const lambdaHandler: Handler = async (event, context) => {
+ logger.addContext(context);
+
+ logger.info('This is an INFO log', { correlationIds: { myCustomCorrelationId: 'foo-bar-baz' } });
+
+ return {
+ foo: 'bar'
+ };
+};
+
+
+```
+
+
+ Click to expand and see the logs outputs
+
+```bash
+
+{
+ message: 'This is an INFO log',
+ service: 'foo-bar',
+ environment: 'prod',
+ awsRegion: 'eu-central-1',
+ correlationIds: {
+ awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
+ xRayTraceId: 'abcdef123456abcdef123456abcdef123456',
+ myCustomCorrelationId: 'foo-bar-baz'
+ },
+ lambdaFunction: {
+ name: 'foo-bar-function',
+ arn: 'arn:aws:lambda:eu-central-1:123456789012:function:Example',
+ memoryLimitInMB: 128,
+ version: '$LATEST',
+ coldStart: true
+ },
+ logLevel: 'INFO',
+ timestamp: '2021-03-13T21:43:47.759Z',
+ logger: {
+ sampleRateValue: 0.00001,
+ name: 'aws-lambda-powertools-typescript',
+ version: '0.0.1'
+ },
+ awsAccountId: '123456789012'
+}
+
+```
+
+
+
+## Test locally
+
+```bash
+
+npm run test
+
+npm run example:hello-world
+npm run example:hello-world-with-context
+npm run example:custom-logger-options
+npm run example:child-logger
+
+```
diff --git a/packages/logger/examples/additional-keys.ts b/packages/logger/examples/additional-keys.ts
new file mode 100644
index 0000000000..6d5c918052
--- /dev/null
+++ b/packages/logger/examples/additional-keys.ts
@@ -0,0 +1,30 @@
+import { populateEnvironmentVariables } from '../tests/helpers';
+
+// Populate runtime
+populateEnvironmentVariables();
+// Additional runtime variables
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+
+import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
+import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
+import { Handler } from 'aws-lambda';
+import { Logger } from '../src';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async () => {
+
+ // Pass a custom correlation ID
+ logger.warn('This is a WARN log', { correlationIds: { myCustomCorrelationId: 'foo-bar-baz' } });
+
+ // Pass an error that occurred
+ logger.error('This is an ERROR log', new Error('Something bad happened!'));
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
\ No newline at end of file
diff --git a/packages/logger/examples/child-logger.ts b/packages/logger/examples/child-logger.ts
new file mode 100644
index 0000000000..dd54b9a4be
--- /dev/null
+++ b/packages/logger/examples/child-logger.ts
@@ -0,0 +1,33 @@
+import { populateEnvironmentVariables } from '../tests/helpers';
+
+// Populate runtime
+populateEnvironmentVariables();
+// Additional runtime variables
+process.env.LOG_LEVEL = 'INFO';
+
+import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
+import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
+import { Handler } from 'aws-lambda';
+import { Logger } from '../src';
+
+const parentLogger = new Logger();
+
+const childLogger = parentLogger.createChild({
+ logLevel: 'ERROR'
+});
+
+const lambdaHandler: Handler = async () => {
+
+ parentLogger.info('This is an INFO log, from the parent logger');
+ parentLogger.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');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+lambdaHandler(dummyEvent, dummyContext, () => {});
\ No newline at end of file
diff --git a/packages/logger/examples/config/CustomConfigService.ts b/packages/logger/examples/config/CustomConfigService.ts
new file mode 100644
index 0000000000..e66203c502
--- /dev/null
+++ b/packages/logger/examples/config/CustomConfigService.ts
@@ -0,0 +1,16 @@
+import { EnvironmentVariablesService } from '../../src/config';
+
+class CustomConfigService extends EnvironmentVariablesService {
+
+ // Custom environment variables
+ protected customEnvironmentVariable = 'CUSTOM_ENV';
+
+ public getCurrentEnvironment(): string {
+ return this.get(this.customEnvironmentVariable);
+ }
+
+}
+
+export {
+ CustomConfigService
+};
\ No newline at end of file
diff --git a/packages/logger/examples/custom-logger-options.ts b/packages/logger/examples/custom-logger-options.ts
new file mode 100644
index 0000000000..a861bae120
--- /dev/null
+++ b/packages/logger/examples/custom-logger-options.ts
@@ -0,0 +1,42 @@
+import { populateEnvironmentVariables } from '../tests/helpers';
+
+// Populate runtime
+populateEnvironmentVariables();
+// Additional runtime variables
+process.env.CUSTOM_ENV = 'prod';
+process.env.POWERTOOLS_CONTEXT_ENABLED = 'TRUE';
+
+import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
+import * as powertool from '../../../package.json';
+import { CustomConfigService } from './config/CustomConfigService';
+import { CustomLogFormatter } from './formatters/CustomLogFormatter';
+import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
+import { Handler } from 'aws-lambda';
+import { Logger } from '../src';
+
+const logger = new Logger({
+ logLevel: 'INFO', // Override options
+ serviceName: 'foo-bar',
+ sampleRateValue: 0.00001,
+ customAttributes: { // Custom attributes that will be added in every log
+ awsAccountId: '123456789012',
+ logger: {
+ name: powertool.name,
+ version: powertool.version,
+ }
+ },
+ logFormatter: new CustomLogFormatter(), // Custom log formatter to print the log in custom structure
+ customConfigService: new CustomConfigService() // Custom config service, that could be used for AppConfig for example
+});
+
+const lambdaHandler: Handler = async (event, context) => {
+ logger.addContext(context);
+
+ logger.info('This is an INFO log', { correlationIds: { myCustomCorrelationId: 'foo-bar-baz' } });
+
+ return {
+ foo: 'bar'
+ };
+};
+
+lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
\ No newline at end of file
diff --git a/packages/logger/examples/formatters/CustomLogFormatter.ts b/packages/logger/examples/formatters/CustomLogFormatter.ts
new file mode 100644
index 0000000000..8bb0872e3c
--- /dev/null
+++ b/packages/logger/examples/formatters/CustomLogFormatter.ts
@@ -0,0 +1,37 @@
+import { LogFormatter } from '../../src/formatter';
+import { LogAttributes, UnformattedAttributes } from '../../types';
+
+type MyCompanyLog = LogAttributes;
+
+class CustomLogFormatter extends LogFormatter {
+
+ public format(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?.name,
+ arn: attributes.lambdaContext?.arn,
+ memoryLimitInMB: attributes.lambdaContext?.memoryLimitInMB,
+ version: attributes.lambdaContext?.version,
+ coldStart: attributes.lambdaContext?.coldStart,
+ },
+ logLevel: attributes.logLevel,
+ timestamp: this.formatTimestamp(attributes.timestamp),
+ logger: {
+ sampleRateValue: attributes.sampleRateValue,
+ },
+ };
+ }
+
+}
+
+export {
+ CustomLogFormatter
+};
\ No newline at end of file
diff --git a/packages/logger/examples/hello-world-with-context.ts b/packages/logger/examples/hello-world-with-context.ts
new file mode 100644
index 0000000000..806cbd9433
--- /dev/null
+++ b/packages/logger/examples/hello-world-with-context.ts
@@ -0,0 +1,31 @@
+import { populateEnvironmentVariables } from '../tests/helpers';
+
+// Populate runtime
+populateEnvironmentVariables();
+// Additional runtime variables
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+process.env.POWERTOOLS_CONTEXT_ENABLED = 'TRUE';
+
+import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
+import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
+import { Handler } from 'aws-lambda';
+import { Logger } from '../src';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async (event, context) => {
+ logger.addContext(context);
+
+ logger.debug('This is a DEBUG log');
+ logger.info('This is an INFO log');
+ logger.warn('This is a WARN log');
+ logger.error('This is an ERROR log');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
\ No newline at end of file
diff --git a/packages/logger/examples/hello-world.ts b/packages/logger/examples/hello-world.ts
new file mode 100644
index 0000000000..d922c5adee
--- /dev/null
+++ b/packages/logger/examples/hello-world.ts
@@ -0,0 +1,29 @@
+import { populateEnvironmentVariables } from '../tests/helpers';
+
+// Populate runtime
+populateEnvironmentVariables();
+// Additional runtime variables
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+
+import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
+import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
+import { Handler } from 'aws-lambda';
+import { Logger } from '../src';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async () => {
+
+ logger.debug('This is a DEBUG log');
+ logger.info('This is an INFO log');
+ logger.warn('This is a WARN log');
+ logger.error('This is an ERROR log');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
\ No newline at end of file
diff --git a/packages/logger/examples/sample-rate.ts b/packages/logger/examples/sample-rate.ts
new file mode 100644
index 0000000000..59049af7d3
--- /dev/null
+++ b/packages/logger/examples/sample-rate.ts
@@ -0,0 +1,30 @@
+import { populateEnvironmentVariables } from '../tests/helpers';
+
+// Populate runtime
+populateEnvironmentVariables();
+// Additional runtime variables
+process.env.LOG_LEVEL = 'WARN';
+process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+process.env.POWERTOOLS_LOGGER_SAMPLE_RATE = '0.5';
+
+import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
+import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
+import { Handler } from 'aws-lambda';
+import { Logger } from '../src';
+
+const logger = new Logger();
+
+const lambdaHandler: Handler = async () => {
+
+ 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');
+
+ return {
+ foo: 'bar'
+ };
+
+};
+
+lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
\ No newline at end of file
diff --git a/packages/logging/jest.config.js b/packages/logger/jest.config.js
similarity index 86%
rename from packages/logging/jest.config.js
rename to packages/logger/jest.config.js
index 6fbaae512f..9c42534a7f 100644
--- a/packages/logging/jest.config.js
+++ b/packages/logger/jest.config.js
@@ -22,10 +22,10 @@ module.exports = {
],
'coverageThreshold': {
'global': {
- 'statements': 100,
- 'branches': 100,
- 'functions': 100,
- 'lines': 100,
+ 'statements': 70,
+ 'branches': 60,
+ 'functions': 70,
+ 'lines': 70,
},
},
'coverageReporters': [
diff --git a/packages/logging/package-lock.json b/packages/logger/package-lock.json
similarity index 99%
rename from packages/logging/package-lock.json
rename to packages/logger/package-lock.json
index 4a79077332..3aaec45b25 100644
--- a/packages/logging/package-lock.json
+++ b/packages/logger/package-lock.json
@@ -1,17 +1,22 @@
{
- "name": "@aws-lambda-powertools/logging",
+ "name": "@aws-lambda-powertools/logger",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "@aws-lambda-powertools/logging",
+ "name": "@aws-lambda-powertools/logger",
"version": "0.0.0",
"license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
+ "@types/aws-lambda": "^8.10.72",
"@types/jest": "^26.0.19",
+ "@types/lodash": "^4.14.168",
"@types/node": "^14.14.16",
"@typescript-eslint/eslint-plugin": "^4.11.1",
"@typescript-eslint/parser": "^4.11.1",
@@ -19,6 +24,7 @@
"jest": "^26.6.3",
"lerna": "^3.22.1",
"ts-jest": "^26.4.4",
+ "ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
},
@@ -1144,7 +1150,6 @@
"jest-resolve": "^26.6.2",
"jest-util": "^26.6.2",
"jest-worker": "^26.6.2",
- "node-notifier": "^8.0.0",
"slash": "^3.0.0",
"source-map": "^0.6.0",
"string-length": "^4.0.1",
@@ -2046,9 +2051,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -2133,9 +2135,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -2352,9 +2351,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -2588,9 +2584,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -2729,9 +2722,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -2794,9 +2784,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3007,9 +2994,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3062,9 +3046,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3227,9 +3208,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3670,9 +3648,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3753,9 +3728,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3888,9 +3860,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -3941,9 +3910,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.6"
- },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -4397,6 +4363,12 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "node_modules/@types/aws-lambda": {
+ "version": "8.10.72",
+ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.72.tgz",
+ "integrity": "sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA==",
+ "dev": true
+ },
"node_modules/@types/babel__core": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
@@ -4497,6 +4469,12 @@
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.14.168",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
+ "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
+ "dev": true
+ },
"node_modules/@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -4937,6 +4915,12 @@
"safe-buffer": "~5.1.0"
}
},
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -6669,6 +6653,12 @@
"node": ">=10"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -6945,6 +6935,15 @@
"wrappy": "1"
}
},
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/diff-sequences": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
@@ -7235,8 +7234,7 @@
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
- "optionator": "^0.8.1",
- "source-map": "~0.6.1"
+ "optionator": "^0.8.1"
},
"bin": {
"escodegen": "bin/escodegen.js",
@@ -8980,7 +8978,6 @@
"minimist": "^1.2.5",
"neo-async": "^2.6.0",
"source-map": "^0.6.1",
- "uglify-js": "^3.1.4",
"wordwrap": "^1.0.0"
},
"bin": {
@@ -10159,7 +10156,6 @@
"@types/node": "*",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
- "fsevents": "^2.1.2",
"graceful-fs": "^4.2.4",
"jest-regex-util": "^26.0.0",
"jest-serializer": "^26.6.2",
@@ -10654,7 +10650,6 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
- "graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@@ -10961,10 +10956,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
- "dev": true
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash._reinterpolate": {
"version": "3.0.0",
@@ -15116,6 +15110,32 @@
"node": ">=10"
}
},
+ "node_modules/ts-node": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz",
+ "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==",
+ "dev": true,
+ "dependencies": {
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "source-map-support": "^0.5.17",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.7"
+ }
+ },
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -16087,6 +16107,15 @@
"node": ">=6"
}
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -19748,6 +19777,12 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "@types/aws-lambda": {
+ "version": "8.10.72",
+ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.72.tgz",
+ "integrity": "sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA==",
+ "dev": true
+ },
"@types/babel__core": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
@@ -19848,6 +19883,12 @@
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
+ "@types/lodash": {
+ "version": "4.14.168",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
+ "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
+ "dev": true
+ },
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -20167,6 +20208,12 @@
}
}
},
+ "arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -21561,6 +21608,12 @@
"yaml": "^1.10.0"
}
},
+ "create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -21779,6 +21832,12 @@
"wrappy": "1"
}
},
+ "diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true
+ },
"diff-sequences": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
@@ -24950,10 +25009,9 @@
}
},
"lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
- "dev": true
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash._reinterpolate": {
"version": "3.0.0",
@@ -28275,6 +28333,20 @@
}
}
},
+ "ts-node": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz",
+ "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==",
+ "dev": true,
+ "requires": {
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "source-map-support": "^0.5.17",
+ "yn": "3.1.1"
+ }
+ },
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -29055,6 +29127,12 @@
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
"dev": true
},
+ "yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true
+ },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/packages/logging/package.json b/packages/logger/package.json
similarity index 68%
rename from packages/logging/package.json
rename to packages/logger/package.json
index 501dd30faa..ebdcb40c5b 100644
--- a/packages/logging/package.json
+++ b/packages/logger/package.json
@@ -1,5 +1,5 @@
{
- "name": "@aws-lambda-powertools/logging",
+ "name": "@aws-lambda-powertools/logger",
"version": "0.0.0",
"description": "The logging package for the AWS Lambda powertools (TypeScript) library",
"author": {
@@ -17,7 +17,13 @@
"prepublishOnly": "npm test && npm run lint",
"preversion": "npm run lint",
"version": "npm run format && git add -A src",
- "postversion": "git push && git push --tags"
+ "postversion": "git push && git push --tags",
+ "example:hello-world": "ts-node examples/hello-world.ts",
+ "example:hello-world-with-context": "ts-node examples/hello-world-with-context.ts",
+ "example:custom-logger-options": "ts-node examples/custom-logger-options.ts",
+ "example:child-logger": "ts-node examples/child-logger.ts",
+ "example:additional-keys": "ts-node examples/additional-keys.ts",
+ "example:sample-rate": "ts-node examples/sample-rate.ts"
},
"homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/logging#readme",
"license": "MIT",
@@ -26,7 +32,9 @@
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
+ "@types/aws-lambda": "^8.10.72",
"@types/jest": "^26.0.19",
+ "@types/lodash": "^4.14.168",
"@types/node": "^14.14.16",
"@typescript-eslint/eslint-plugin": "^4.11.1",
"@typescript-eslint/parser": "^4.11.1",
@@ -34,6 +42,7 @@
"jest": "^26.6.3",
"lerna": "^3.22.1",
"ts-jest": "^26.4.4",
+ "ts-node": "^9.1.1",
"typescript": "^4.1.3"
},
"files": [
@@ -45,5 +54,8 @@
},
"bugs": {
"url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues"
+ },
+ "dependencies": {
+ "lodash": "^4.17.21"
}
}
diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts
new file mode 100644
index 0000000000..d963a82fc8
--- /dev/null
+++ b/packages/logger/src/Logger.ts
@@ -0,0 +1,289 @@
+import { Context } from 'aws-lambda';
+import { LoggerInterface } from '.';
+import { LogItem } from './log';
+
+import { cloneDeep, merge } from 'lodash/fp';
+import { ConfigServiceInterface, EnvironmentVariablesService } from './config';
+import {
+ Environment,
+ PowertoolAttributes,
+ LogAttributes,
+ LoggerOptions,
+ LogLevel,
+ LogLevelThresholds,
+ LambdaFunctionContext,
+ LoggerInput,
+ LoggerExtraInput
+} from '../types';
+import { LogFormatterInterface, PowertoolLogFormatter } from './formatter';
+
+class Logger implements LoggerInterface {
+
+ private static coldStart: boolean = true;
+
+ private customAttributes?: LogAttributes = {};
+
+ private customConfigService?: ConfigServiceInterface;
+
+ private static readonly defaultLogLevel: LogLevel = 'INFO';
+
+ private envVarsService?: EnvironmentVariablesService;
+
+ private logFormatter?: LogFormatterInterface;
+
+ private logLevel?: LogLevel;
+
+ private readonly logLevelThresholds: LogLevelThresholds = {
+ 'DEBUG' : 8,
+ 'INFO': 12,
+ 'WARN': 16,
+ 'ERROR': 20
+ };
+
+ private powertoolAttributes: PowertoolAttributes = {};
+
+ public constructor(options: LoggerOptions = {}) {
+ this.setOptions(options);
+ }
+
+ public addContext(context: Context): void {
+ if (!this.isContextEnabled()) {
+ return;
+ }
+
+ const lambdaContext: Partial = {
+ arn: context.invokedFunctionArn,
+ awsRequestId: context.awsRequestId,
+ memoryLimitInMB: Number(context.memoryLimitInMB),
+ name: context.functionName,
+ version: context.functionVersion,
+ };
+
+ this.addToPowertoolAttributes({
+ lambdaContext
+ });
+
+ }
+
+ public createChild(options: LoggerOptions = {}): Logger {
+ return cloneDeep(this).setOptions(options);
+ }
+
+ public debug(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
+ if (!this.shouldPrint('DEBUG')) {
+ return;
+ }
+ this.printLog('DEBUG', this.createLogItem('DEBUG', input, extraInput).getAttributes());
+ }
+
+ public error(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
+ if (!this.shouldPrint('ERROR')) {
+ return;
+ }
+ this.printLog('ERROR', this.createLogItem('ERROR', input, extraInput).getAttributes());
+ }
+
+ public info(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
+ if (!this.shouldPrint('INFO')) {
+ return;
+ }
+ this.printLog('INFO', this.createLogItem('INFO', input, extraInput).getAttributes());
+ }
+
+ public static isColdStart(): boolean {
+ if (Logger.coldStart === true) {
+ Logger.coldStart = false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public warn(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
+ if (!this.shouldPrint('WARN')) {
+ return;
+ }
+ this.printLog('WARN', this.createLogItem('WARN', input, extraInput).getAttributes());
+ }
+
+ private addToPowertoolAttributes(...attributesArray: Array>): void {
+ attributesArray.forEach((attributes: Partial) => {
+ this.powertoolAttributes = merge(this.getPowertoolAttributes(), attributes);
+ });
+ }
+
+ private createLogItem(logLevel: LogLevel, input: LoggerInput, extraInput: LoggerExtraInput): LogItem {
+
+ const logItem = new LogItem().addAttributes(
+ this.getLogFormatter().format(
+ merge({
+ logLevel,
+ timestamp: new Date(),
+ message: (typeof input === 'string') ? input : input.message
+ },
+ this.getPowertoolAttributes())
+ )
+ ).addAttributes(this.getCustomAttributes());
+
+ if (typeof input !== 'string') {
+ logItem.addAttributes(input);
+ }
+
+ extraInput.forEach((item: Error | LogAttributes) => {
+ const attributes = (item instanceof Error) ? {
+ error: {
+ name: item.name,
+ message: item.message,
+ stack: item.stack,
+ }
+ } : item;
+ logItem.addAttributes(attributes);
+ });
+
+ return logItem;
+ }
+
+ private getCustomAttributes(): LogAttributes {
+ return this.customAttributes || {};
+ }
+
+ private getCustomConfigService(): ConfigServiceInterface | undefined {
+ return this.customConfigService;
+ }
+
+ private getEnvVarsService(): EnvironmentVariablesService {
+ if (!this.envVarsService) {
+ this.setEnvVarsService();
+ }
+
+ return this.envVarsService;
+ }
+
+ private getLogFormatter(): LogFormatterInterface {
+ if (!this.logFormatter) {
+ this.setLogFormatter();
+ }
+
+ return this.logFormatter;
+ }
+
+ private getLogLevel(): LogLevel {
+ if (this.powertoolAttributes?.logLevel) {
+ this.setLogLevel();
+ }
+
+ return this.logLevel;
+ }
+
+ private getPowertoolAttributes(): PowertoolAttributes {
+ return this.powertoolAttributes || {};
+ }
+
+ private getSampleRateValue(): number {
+ if (!this.powertoolAttributes?.sampleRateValue) {
+ this.setSampleRateValue();
+ }
+
+ return this.powertoolAttributes?.sampleRateValue;
+ }
+
+ private isContextEnabled(): boolean {
+ return this.getCustomConfigService()?.getIsContextEnabled() === true || this.getEnvVarsService().getIsContextEnabled() === true;
+ }
+
+ private printLog(logLevel: LogLevel, log: LogAttributes): void {
+ Object.keys(log).forEach(key => (log[key] === undefined || log[key] === '' || log[key] === null) && delete log[key]);
+
+ console.log(log);
+ }
+
+ private setCustomAttributes(attributes?: LogAttributes): void {
+ this.customAttributes = attributes;
+ }
+
+ private setCustomConfigService(customConfigService?: ConfigServiceInterface): void {
+ this.customConfigService = customConfigService? customConfigService : undefined;
+ }
+
+ private setEnvVarsService(): void {
+ this.envVarsService = new EnvironmentVariablesService();
+ }
+
+ private setLogFormatter(logFormatter?: LogFormatterInterface): void {
+ this.logFormatter = logFormatter || new PowertoolLogFormatter();
+ }
+
+ private setLogLevel(logLevel?: LogLevel): void {
+ this.logLevel = (logLevel || this.getCustomConfigService()?.getLogLevel() || this.getEnvVarsService().getLogLevel()
+ || Logger.defaultLogLevel) as LogLevel;
+ }
+
+ private setOptions(options: LoggerOptions = {}): Logger {
+ const {
+ logLevel,
+ serviceName,
+ sampleRateValue,
+ logFormatter,
+ customConfigService,
+ customAttributes,
+ environment
+ } = options;
+
+ this.setEnvVarsService();
+ this.setCustomConfigService(customConfigService);
+ this.setLogLevel(logLevel);
+ this.setSampleRateValue(sampleRateValue);
+ this.setLogFormatter(logFormatter);
+ this.setPowertoolAttributes(serviceName, environment);
+ this.setCustomAttributes(customAttributes);
+
+ return this;
+ }
+
+ private setPowertoolAttributes(serviceName?: string, environment?: Environment, customAttributes: LogAttributes = {}): void {
+
+ if (this.isContextEnabled()) {
+ this.addToPowertoolAttributes( {
+ lambdaContext: {
+ coldStart: Logger.isColdStart(),
+ memoryLimitInMB: this.getEnvVarsService().getFunctionMemory(),
+ name: this.getEnvVarsService().getFunctionName(),
+ version:this.getEnvVarsService().getFunctionVersion(),
+ }
+ });
+ }
+
+ this.addToPowertoolAttributes({
+ awsRegion: this.getEnvVarsService().getAwsRegion(),
+ environment: environment || this.getCustomConfigService()?.getCurrentEnvironment() || this.getEnvVarsService().getCurrentEnvironment(),
+ sampleRateValue: this.getSampleRateValue(),
+ serviceName: serviceName || this.getCustomConfigService()?.getServiceName() || this.getEnvVarsService().getServiceName(),
+ xRayTraceId: this.getEnvVarsService().getXrayTraceId(),
+ }, customAttributes );
+ }
+
+ private setSampleRateValue(sampleRateValue?: number): void {
+ this.powertoolAttributes.sampleRateValue = sampleRateValue || this.getCustomConfigService()?.getSampleRateValue()
+ || this.getEnvVarsService().getSampleRateValue();
+ }
+
+ private shouldPrint(logLevel: LogLevel): boolean {
+ if (this.logLevelThresholds[logLevel] >= this.logLevelThresholds[this.getLogLevel()]) {
+ return true;
+ }
+
+ // TODO: refactor this logic (Math.random() does not provide cryptographically secure random numbers)
+ const sampleRateValue = this.getSampleRateValue();
+ if (sampleRateValue && (sampleRateValue === 1 || Math.random() < sampleRateValue)) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
+
+export {
+ Logger
+};
\ No newline at end of file
diff --git a/packages/logger/src/LoggerInterface.ts b/packages/logger/src/LoggerInterface.ts
new file mode 100644
index 0000000000..1b0a98919c
--- /dev/null
+++ b/packages/logger/src/LoggerInterface.ts
@@ -0,0 +1,17 @@
+import { LoggerExtraInput, LoggerInput } from '../types';
+
+interface LoggerInterface {
+
+ debug(input: LoggerInput, ...extraInput: LoggerExtraInput): void
+
+ error(input: LoggerInput, ...extraInput: LoggerExtraInput): void
+
+ info(input: LoggerInput, ...extraInput: LoggerExtraInput): void
+
+ warn(input: LoggerInput, ...extraInput: LoggerExtraInput): void
+
+}
+
+export {
+ LoggerInterface
+};
\ No newline at end of file
diff --git a/packages/logger/src/config/ConfigService.ts b/packages/logger/src/config/ConfigService.ts
new file mode 100644
index 0000000000..526f7cb892
--- /dev/null
+++ b/packages/logger/src/config/ConfigService.ts
@@ -0,0 +1,28 @@
+import { ConfigServiceInterface } from '.';
+
+abstract class ConfigService implements ConfigServiceInterface {
+
+ // Custom environment variables
+ protected contextEnabledVariable = 'POWERTOOLS_CONTEXT_ENABLED';
+ protected currentEnvironmentVariable = 'ENVIRONMENT';
+ protected logLevelVariable = 'LOG_LEVEL';
+ protected sampleRateValueVariable = 'POWERTOOLS_LOGGER_SAMPLE_RATE';
+ protected serviceNameVariable = 'POWERTOOLS_SERVICE_NAME';
+
+ abstract get(name: string): string;
+
+ abstract getCurrentEnvironment(): string;
+
+ abstract getIsContextEnabled(): boolean;
+
+ abstract getLogLevel(): string;
+
+ abstract getSampleRateValue(): number | undefined;
+
+ abstract getServiceName(): string;
+
+}
+
+export {
+ ConfigService
+};
\ No newline at end of file
diff --git a/packages/logger/src/config/ConfigServiceInterface.ts b/packages/logger/src/config/ConfigServiceInterface.ts
new file mode 100644
index 0000000000..b04f4866e9
--- /dev/null
+++ b/packages/logger/src/config/ConfigServiceInterface.ts
@@ -0,0 +1,19 @@
+interface ConfigServiceInterface {
+
+ get(name: string): string
+
+ getCurrentEnvironment(): string
+
+ getIsContextEnabled(): boolean
+
+ getLogLevel(): string
+
+ getSampleRateValue(): number | undefined
+
+ getServiceName(): string
+
+}
+
+export {
+ ConfigServiceInterface
+};
\ No newline at end of file
diff --git a/packages/logger/src/config/EnvironmentVariablesService.ts b/packages/logger/src/config/EnvironmentVariablesService.ts
new file mode 100644
index 0000000000..bc61f23fb7
--- /dev/null
+++ b/packages/logger/src/config/EnvironmentVariablesService.ts
@@ -0,0 +1,64 @@
+import { ConfigService } from '.';
+
+class EnvironmentVariablesService extends ConfigService {
+
+ // Reserved environment variables
+ private awsRegionVariable = 'AWS_REGION';
+ private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME';
+ private functionVersionVariable = 'AWS_LAMBDA_FUNCTION_VERSION';
+ private memoryLimitInMBVariable = 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE';
+ private xRayTraceIdVariable = '_X_AMZN_TRACE_ID';
+
+ public get(name: string): string {
+ return process.env[name]?.trim() || '';
+ }
+
+ public getAwsRegion(): string {
+ return this.get(this.awsRegionVariable);
+ }
+
+ public getCurrentEnvironment(): string {
+ return this.get(this.currentEnvironmentVariable);
+ }
+
+ public getFunctionMemory(): number {
+ const value = this.get(this.memoryLimitInMBVariable);
+
+ return Number(value);
+ }
+
+ public getFunctionName(): string {
+ return this.get(this.functionNameVariable);
+ }
+
+ public getFunctionVersion(): string {
+ return this.get(this.functionVersionVariable);
+ }
+
+ public getIsContextEnabled(): boolean {
+ return [ '1', 'TRUE', 'ON' ].includes(this.get(this.contextEnabledVariable).toUpperCase());
+ }
+
+ public getLogLevel(): string {
+ return this.get(this.logLevelVariable);
+ }
+
+ public getSampleRateValue(): number | undefined {
+ const value = this.get(this.sampleRateValueVariable);
+
+ return (value && value.length > 0) ? Number(value) : undefined;
+ }
+
+ public getServiceName(): string {
+ return this.get(this.serviceNameVariable);
+ }
+
+ public getXrayTraceId(): string {
+ return this.get(this.xRayTraceIdVariable);
+ }
+
+}
+
+export {
+ EnvironmentVariablesService,
+};
\ No newline at end of file
diff --git a/packages/logger/src/config/index.ts b/packages/logger/src/config/index.ts
new file mode 100644
index 0000000000..b8a77f2ab4
--- /dev/null
+++ b/packages/logger/src/config/index.ts
@@ -0,0 +1,3 @@
+export * from './ConfigService';
+export * from './ConfigServiceInterface';
+export * from './EnvironmentVariablesService';
\ No newline at end of file
diff --git a/packages/logger/src/formatter/LogFormatter.ts b/packages/logger/src/formatter/LogFormatter.ts
new file mode 100644
index 0000000000..fe062191dd
--- /dev/null
+++ b/packages/logger/src/formatter/LogFormatter.ts
@@ -0,0 +1,15 @@
+import { LogFormatterInterface } from '.';
+import { LogAttributes, UnformattedAttributes } from '../../types';
+
+abstract class LogFormatter implements LogFormatterInterface {
+
+ abstract format(attributes: UnformattedAttributes): LogAttributes;
+
+ public formatTimestamp(now: Date): string {
+ return now.toISOString();
+ }
+}
+
+export {
+ LogFormatter
+};
\ No newline at end of file
diff --git a/packages/logger/src/formatter/LogFormatterInterface.ts b/packages/logger/src/formatter/LogFormatterInterface.ts
new file mode 100644
index 0000000000..82492fe36c
--- /dev/null
+++ b/packages/logger/src/formatter/LogFormatterInterface.ts
@@ -0,0 +1,11 @@
+import { LogAttributes, UnformattedAttributes } from '../../types';
+
+interface LogFormatterInterface {
+
+ format(attributes: UnformattedAttributes): LogAttributes
+
+}
+
+export {
+ LogFormatterInterface
+};
\ No newline at end of file
diff --git a/packages/logger/src/formatter/PowertoolLogFormatter.ts b/packages/logger/src/formatter/PowertoolLogFormatter.ts
new file mode 100644
index 0000000000..da747dfd20
--- /dev/null
+++ b/packages/logger/src/formatter/PowertoolLogFormatter.ts
@@ -0,0 +1,27 @@
+import { LogFormatter } from '.';
+import { PowertoolLog } from '../../types/formats';
+import { UnformattedAttributes } from '../../types';
+
+class PowertoolLogFormatter extends LogFormatter {
+
+ public format(attributes: UnformattedAttributes): PowertoolLog {
+ return {
+ aws_request_id: attributes.lambdaContext?.awsRequestId,
+ cold_start: attributes.lambdaContext?.coldStart,
+ lambda_function_arn: attributes.lambdaContext?.arn,
+ lambda_function_memory_size: attributes.lambdaContext?.memoryLimitInMB,
+ lambda_function_name: attributes.lambdaContext?.name,
+ level: attributes.logLevel,
+ message: attributes.message,
+ sampling_rate: attributes.sampleRateValue,
+ service: attributes.serviceName,
+ timestamp: this.formatTimestamp(attributes.timestamp),
+ xray_trace_id: attributes.xRayTraceId,
+ };
+ }
+
+}
+
+export {
+ PowertoolLogFormatter
+};
\ No newline at end of file
diff --git a/packages/logger/src/formatter/index.ts b/packages/logger/src/formatter/index.ts
new file mode 100644
index 0000000000..4c88132ead
--- /dev/null
+++ b/packages/logger/src/formatter/index.ts
@@ -0,0 +1,3 @@
+export * from './LogFormatter';
+export * from './LogFormatterInterface';
+export * from './PowertoolLogFormatter';
\ No newline at end of file
diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts
new file mode 100644
index 0000000000..24e196c141
--- /dev/null
+++ b/packages/logger/src/index.ts
@@ -0,0 +1,2 @@
+export * from './Logger';
+export * from './LoggerInterface';
\ No newline at end of file
diff --git a/packages/logger/src/log/LogItem.ts b/packages/logger/src/log/LogItem.ts
new file mode 100644
index 0000000000..7c94cceaef
--- /dev/null
+++ b/packages/logger/src/log/LogItem.ts
@@ -0,0 +1,27 @@
+import { LogAttributes } from '../../types';
+import { LogItemInterface } from '.';
+import { merge } from 'lodash/fp';
+
+class LogItem implements LogItemInterface {
+
+ private attributes: LogAttributes = {};
+
+ public addAttributes(attributes: LogAttributes): LogItem {
+ this.attributes = merge(this.attributes, attributes);
+
+ return this;
+ }
+
+ public getAttributes(): LogAttributes {
+ return this.attributes;
+ }
+
+ public toJSON(): string {
+ return JSON.stringify(this.getAttributes());
+ }
+
+}
+
+export {
+ LogItem
+};
\ No newline at end of file
diff --git a/packages/logger/src/log/LogItemInterface.ts b/packages/logger/src/log/LogItemInterface.ts
new file mode 100644
index 0000000000..13aad006a8
--- /dev/null
+++ b/packages/logger/src/log/LogItemInterface.ts
@@ -0,0 +1,15 @@
+import { LogAttributes } from '../../types/Log';
+
+interface LogItemInterface {
+
+ addAttributes(attributes: LogAttributes): void
+
+ getAttributes(): LogAttributes
+
+ toJSON(): string
+
+}
+
+export {
+ LogItemInterface
+};
\ No newline at end of file
diff --git a/packages/logger/src/log/index.ts b/packages/logger/src/log/index.ts
new file mode 100644
index 0000000000..4491038de9
--- /dev/null
+++ b/packages/logger/src/log/index.ts
@@ -0,0 +1,2 @@
+export * from './LogItem';
+export * from './LogItemInterface';
\ No newline at end of file
diff --git a/packages/logger/tests/helpers/index.ts b/packages/logger/tests/helpers/index.ts
new file mode 100644
index 0000000000..d3e043d223
--- /dev/null
+++ b/packages/logger/tests/helpers/index.ts
@@ -0,0 +1 @@
+export * from './populate-environment-variables';
\ No newline at end of file
diff --git a/packages/logger/tests/helpers/populate-environment-variables.ts b/packages/logger/tests/helpers/populate-environment-variables.ts
new file mode 100644
index 0000000000..45489522a4
--- /dev/null
+++ b/packages/logger/tests/helpers/populate-environment-variables.ts
@@ -0,0 +1,17 @@
+const populateEnvironmentVariables = (): void => {
+
+ // Reserved variables
+ process.env._X_AMZN_TRACE_ID = 'abcdef123456abcdef123456abcdef123456';
+ process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function';
+ process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128';
+ process.env.AWS_REGION = 'eu-central-1';
+
+ // Powertools variables
+ process.env.LOG_LEVEL = 'DEBUG';
+ process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
+
+};
+
+export {
+ populateEnvironmentVariables
+};
\ No newline at end of file
diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts
new file mode 100644
index 0000000000..5ed5cc910e
--- /dev/null
+++ b/packages/logger/tests/unit/Logger.test.ts
@@ -0,0 +1,129 @@
+import { Logger } from '../../src';
+import { populateEnvironmentVariables } from '../helpers';
+
+const mockDate = new Date(1466424490000);
+const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string);
+
+const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
+
+describe('Logger', () => {
+
+ const originalEnvironmentVariables = process.env;
+
+ beforeAll(() => {
+ populateEnvironmentVariables();
+ });
+
+ beforeEach(() => {
+ consoleSpy.mockClear();
+ dateSpy.mockClear();
+ });
+
+ afterAll(() => {
+ process.env = originalEnvironmentVariables;
+ });
+
+ test('should return a valid INFO log', () => {
+
+ const logger = new Logger();
+
+ logger.info('foo');
+ logger.info('foo', { bar: 'baz' });
+
+ expect(console.log).toBeCalledTimes(2);
+ expect(console.log).toHaveBeenNthCalledWith(1, {
+ message: 'foo',
+ service: 'hello-world',
+ level: 'INFO',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+ expect(console.log).toHaveBeenNthCalledWith(2, {
+ bar: 'baz',
+ message: 'foo',
+ service: 'hello-world',
+ level: 'INFO',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+
+ });
+
+ test('should return a valid ERROR log', () => {
+
+ const logger = new Logger();
+
+ logger.error('foo');
+ logger.error('foo', { bar: 'baz' });
+
+ expect(console.log).toBeCalledTimes(2);
+ expect(console.log).toHaveBeenNthCalledWith(1, {
+ message: 'foo',
+ service: 'hello-world',
+ level: 'ERROR',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+ expect(console.log).toHaveBeenNthCalledWith(2, {
+ bar: 'baz',
+ message: 'foo',
+ service: 'hello-world',
+ level: 'ERROR',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+ });
+
+ test('should return a valid DEBUG log', () => {
+
+ const logger = new Logger();
+
+ logger.debug('foo');
+ logger.debug('foo', { bar: 'baz' });
+
+ expect(console.log).toBeCalledTimes(2);
+ expect(console.log).toHaveBeenNthCalledWith(1, {
+ message: 'foo',
+ service: 'hello-world',
+ level: 'DEBUG',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+ expect(console.log).toHaveBeenNthCalledWith(2, {
+ bar: 'baz',
+ message: 'foo',
+ service: 'hello-world',
+ level: 'DEBUG',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+ });
+
+ test('should return a valid WARN log', () => {
+
+ const logger = new Logger();
+
+ logger.warn('foo');
+ logger.warn( { message: 'foo', bar: 'baz' });
+
+ expect(console.log).toBeCalledTimes(2);
+ expect(console.log).toHaveBeenNthCalledWith(1, {
+ timestamp: '2016-06-20T12:08:10.000Z',
+ message: 'foo',
+ level: 'WARN',
+ service: 'hello-world',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+ expect(console.log).toHaveBeenNthCalledWith(2, {
+ bar: 'baz',
+ level: 'WARN',
+ message: 'foo',
+ service: 'hello-world',
+ timestamp: '2016-06-20T12:08:10.000Z',
+ xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
+ });
+
+ });
+
+});
+
diff --git a/packages/logging/tsconfig.json b/packages/logger/tsconfig.json
similarity index 93%
rename from packages/logging/tsconfig.json
rename to packages/logger/tsconfig.json
index 9b62cf735e..64b249fa6c 100644
--- a/packages/logging/tsconfig.json
+++ b/packages/logger/tsconfig.json
@@ -14,7 +14,7 @@
"baseUrl": "src/",
"rootDirs": [ "src/" ]
},
- "include": [ "src/**/*" ],
+ "include": [ "src/**/*", "examples/**/*" ],
"exclude": [ "./node_modules", "**/tests/*"],
"watchOptions": {
"watchFile": "useFsEvents",
diff --git a/packages/logger/types/Log.ts b/packages/logger/types/Log.ts
new file mode 100644
index 0000000000..fba01ae081
--- /dev/null
+++ b/packages/logger/types/Log.ts
@@ -0,0 +1,28 @@
+type LogLevelDebug = 'DEBUG';
+type LogLevelInfo = 'INFO';
+type LogLevelWarn = 'WARN';
+type LogLevelError = 'ERROR';
+
+type LogLevel = LogLevelDebug | LogLevelInfo | LogLevelWarn | LogLevelError;
+
+type LogLevelThresholds = {
+ [key in LogLevel]: number;
+};
+
+type LogAttributeValue = string | number | boolean | null | undefined | LogAttributeValue[] | { [key: string]: LogAttributeValue } | Error;
+type LogAttributes = { [key: string]: LogAttributeValue };
+
+type LogAttributesWithMessage = LogAttributes & {
+ message: string
+};
+
+type Environment = 'dev' | 'local' | 'staging' | 'prod' | string;
+
+export {
+ LogAttributesWithMessage,
+ LogAttributeValue,
+ Environment,
+ LogLevelThresholds,
+ LogAttributes,
+ LogLevel
+};
\ No newline at end of file
diff --git a/packages/logger/types/Logger.ts b/packages/logger/types/Logger.ts
new file mode 100644
index 0000000000..e322979f01
--- /dev/null
+++ b/packages/logger/types/Logger.ts
@@ -0,0 +1,56 @@
+import { ConfigServiceInterface } from '../src/config';
+import { LogFormatterInterface } from '../src/formatter';
+import { Environment, LogAttributes, LogAttributesWithMessage, LogLevel } from './Log';
+
+type LoggerOptions = {
+ logLevel?: LogLevel
+ serviceName?: string
+ sampleRateValue?: number
+ logFormatter?: LogFormatterInterface
+ customConfigService?: ConfigServiceInterface
+ customAttributes?: LogAttributes
+ environment?: Environment
+};
+
+type LambdaFunctionContext = {
+ name: string
+ memoryLimitInMB: number
+ version: string
+ coldStart: boolean
+ arn: string
+ awsRequestId: string
+};
+
+type PowertoolAttributes = LogAttributes & {
+ environment?: Environment
+ serviceName: string
+ sampleRateValue?: number
+ lambdaFunctionContext: LambdaFunctionContext
+ xRayTraceId?: string
+ awsRegion: string
+};
+
+type UnformattedAttributes = PowertoolAttributes & {
+ environment?: Environment
+ error?: Error
+ serviceName: string
+ sampleRateValue?: number
+ lambdaContext?: LambdaFunctionContext
+ xRayTraceId?: string
+ awsRegion: string
+ logLevel: LogLevel
+ timestamp: Date
+ message: string
+};
+
+type LoggerInput = string | LogAttributesWithMessage;
+type LoggerExtraInput = Array;
+
+export {
+ LoggerInput,
+ LoggerExtraInput,
+ LambdaFunctionContext,
+ UnformattedAttributes,
+ PowertoolAttributes,
+ LoggerOptions
+};
\ No newline at end of file
diff --git a/packages/logger/types/formats/PowertoolLog.ts b/packages/logger/types/formats/PowertoolLog.ts
new file mode 100644
index 0000000000..9550916f07
--- /dev/null
+++ b/packages/logger/types/formats/PowertoolLog.ts
@@ -0,0 +1,97 @@
+import { LogAttributes, LogLevel } from '../Log';
+
+type PowertoolLog = LogAttributes & {
+
+ /**
+ * timestamp
+ *
+ * Description: Timestamp of actual log statement.
+ * Example: "2020-05-24 18:17:33,774"
+ */
+ timestamp?: string
+
+ /**
+ * level
+ *
+ * Description: Logging level
+ * Example: "INFO"
+ */
+ level?: LogLevel
+
+ /**
+ * service
+ *
+ * Description: Service name defined.
+ * Example: "payment"
+ */
+ service: string
+
+ /**
+ * sampling_rate
+ *
+ * Description: The value of the logging sampling rate in percentage.
+ * Example: 0.1
+ */
+ sampling_rate?: number
+
+ /**
+ * message
+ *
+ * Description: Log statement value. Unserializable JSON values will be casted to string.
+ * Example: "Collecting payment"
+ */
+ message?: string
+
+ /**
+ * xray_trace_id
+ *
+ * Description: X-Ray Trace ID when Lambda function has enabled Tracing.
+ * Example: "1-5759e988-bd862e3fe1be46a994272793"
+ */
+ xray_trace_id?: string
+
+ /**
+ * cold_start
+ *
+ * Description: Indicates whether the current execution experienced a cold start.
+ * Example: false
+ */
+ cold_start?: boolean
+
+ /**
+ * lambda_function_name
+ *
+ * Description: The name of the Lambda function.
+ * Example: "example-powertools-HelloWorldFunction-1P1Z6B39FLU73"
+ */
+ lambda_function_name?: string
+
+ /**
+ * lambda_function_memory_size
+ *
+ * Description: The memory size of the Lambda function.
+ * Example: 128
+ */
+ lambda_function_memory_size?: number
+
+ /**
+ * lambda_function_arn
+ *
+ * Description: The ARN of the Lambda function.
+ * Example: "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73"
+ */
+ lambda_function_arn?: string
+
+ /**
+ * lambda_request_id
+ *
+ * Description: The request ID of the current invocation.
+ * Example: "899856cb-83d1-40d7-8611-9e78f15f32f4"
+ */
+ lambda_request_id?: string
+
+};
+
+export {
+ PowertoolLog
+};
\ No newline at end of file
diff --git a/packages/logger/types/formats/index.ts b/packages/logger/types/formats/index.ts
new file mode 100644
index 0000000000..e5a9bfb5c5
--- /dev/null
+++ b/packages/logger/types/formats/index.ts
@@ -0,0 +1 @@
+export * from './PowertoolLog';
\ No newline at end of file
diff --git a/packages/logger/types/index.ts b/packages/logger/types/index.ts
new file mode 100644
index 0000000000..3b4ef64069
--- /dev/null
+++ b/packages/logger/types/index.ts
@@ -0,0 +1,2 @@
+export * from './Log';
+export * from './Logger';
\ No newline at end of file
diff --git a/packages/logging/README.md b/packages/logging/README.md
deleted file mode 100644
index 5ba0912576..0000000000
--- a/packages/logging/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# `logging`
-
-> TODO: description
-
-## Usage
-
-```
-import { foo } from "@aws-lambda-powertools/logging"
-
-// TODO: DEMONSTRATE API
-```
diff --git a/packages/logging/src/index.ts b/packages/logging/src/index.ts
deleted file mode 100644
index 0ef8de8089..0000000000
--- a/packages/logging/src/index.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-enum LogLevel {
- DEBUG = 1,
- INFO = 2,
- WARNING = 3,
- ERROR = 4,
- CRITICAL = 5
-}
-
-interface LoggerConfig {
- LogLevel?: LogLevel
-}
-
-class Logger {
- private CURRENT_LOG_LEVEL: LogLevel = LogLevel.INFO;
-
- public constructor(config?: LoggerConfig) {
- config = config || {};
- this.setInitialLogLevel(config);
- }
-
- private setInitialLogLevel(config: LoggerConfig):void {
- if (config.LogLevel) {
- this.CURRENT_LOG_LEVEL = config.LogLevel;
- }
- else if (process.env.LOG_LEVEL) {
- const environmentVariableLevel = process.env.LOG_LEVEL;
- if (environmentVariableLevel in LogLevel) {
- this.CURRENT_LOG_LEVEL = LogLevel[environmentVariableLevel as keyof typeof LogLevel];
- } //else {
- // this.Warn(`LOG_LEVEL environment value was not valid, Received ${environmentVariableLevel} and expected one of ${Object.keys(LogLevel).join(', ')}`);
- // }
- }
- }
-
- public getCurrentLogLevel():LogLevel {
- return this.CURRENT_LOG_LEVEL;
- }
-}
-
-export {
- Logger,
- LogLevel
-};
-
diff --git a/packages/logging/tests/unit/logger.test.ts b/packages/logging/tests/unit/logger.test.ts
deleted file mode 100644
index 4b1b97b41f..0000000000
--- a/packages/logging/tests/unit/logger.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Logger, LogLevel } from '../../src';
-
-describe('Logger Class', () => {
- test('Should return a valid instance', () => {
- const logger = new Logger();
- expect(typeof logger).toBe('object');
- });
- describe('Instantiation Log levels', () => {
- afterEach(() => {
- delete process.env.LOG_LEVEL;
- });
- test('Should default to INFO if no level passed', () => {
- const logger = new Logger();
- expect(logger.getCurrentLogLevel()).toBe(LogLevel.INFO);
- });
- test('Log level passed in constructor should be maintained', () => {
- const loggerDEBUG = new Logger({ LogLevel: LogLevel.DEBUG });
- expect(loggerDEBUG.getCurrentLogLevel()).toBe(LogLevel.DEBUG);
- const loggerWARN = new Logger({ LogLevel: LogLevel.WARNING });
- expect(loggerWARN.getCurrentLogLevel()).toBe(LogLevel.WARNING);
- const loggerCRITICAL = new Logger({ LogLevel: 5 });
- expect(loggerCRITICAL.getCurrentLogLevel()).toBe(LogLevel.CRITICAL);
- });
- test('Log level passed as ENV var should be set', () => {
- process.env.LOG_LEVEL = 'ERROR';
- const logger = new Logger();
- expect(logger.getCurrentLogLevel()).toBe(LogLevel.ERROR);
- });
- test('Log level passed in constructor should override environment variable', () => {
- process.env.LOG_LEVEL = 'ERROR';
- const logger = new Logger({ LogLevel: LogLevel.CRITICAL });
- expect(logger.getCurrentLogLevel()).toBe(LogLevel.CRITICAL);
- process.env.LOG_LEVEL = 'DEBUG';
- const loggerDEBUG = new Logger();
- expect(loggerDEBUG.getCurrentLogLevel()).toBe(LogLevel.DEBUG);
- });
- test('Invalid ENV param should throw warning, and set log level to INFO', () => {
- process.env.LOG_LEVEL = 'ALERT';
- const logger = new Logger();
- expect(logger.getCurrentLogLevel()).toBe(LogLevel.INFO);
- });
- });
-});
-
diff --git a/tests/resources/contexts/hello-world.ts b/tests/resources/contexts/hello-world.ts
new file mode 100644
index 0000000000..86d5ec18f1
--- /dev/null
+++ b/tests/resources/contexts/hello-world.ts
@@ -0,0 +1,20 @@
+import { Context } from 'aws-lambda';
+
+const context: 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-central-1:123456789012:function:Example',
+ awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
+ getRemainingTimeInMillis: () => 1234,
+ done: () => console.log('Done!'),
+ fail: () => console.log('Failed!'),
+ succeed: () => console.log('Succeeded!'),
+};
+
+export {
+ context
+};
\ No newline at end of file
diff --git a/tests/resources/events/custom/hello-world.json b/tests/resources/events/custom/hello-world.json
new file mode 100644
index 0000000000..4ccb6ff485
--- /dev/null
+++ b/tests/resources/events/custom/hello-world.json
@@ -0,0 +1,5 @@
+{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3"
+}
\ No newline at end of file