Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ The documentation is organized into sections by module.
1. [SQS Client](/docs/services/SQS.md)
1. [Cognito Client](/docs/services/COGNITO.md)
1. [Email Service](/docs/services/EMAIL.md)
1. [Logging](/docs/utils/LOGGING.md)
1. [Errors](/docs/errors/ERRORS.md)
4 changes: 2 additions & 2 deletions docs/services/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This ready to use object is of type [`LambdaConfig`](/src/services/config.servic
// some-component.ts
import { lambdaConfigValues as config } from '@leanstacks/serverless-common';

console.log(`The region is ${config.AWS_REGON}`);
Logger.debug(`The region is ${config.AWS_REGON}`);
```

## Extending `LambdaConfig` with custom configuration attributes
Expand Down Expand Up @@ -69,7 +69,7 @@ serverless component. For example...
// some-component.ts
import config from 'my-config';

console.log(`The table name is ${config.TABLE_NAME}`);
Logger.debug(`The table name is ${config.TABLE_NAME}`);
```

## Performance considerations
Expand Down
3 changes: 2 additions & 1 deletion docs/services/SQS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ async send(task: Task): Promise<void> {
QueueUrl: config.TASK_QUEUE_URL,
MessageBody: JSON.stringify(task);
});
console.log(`sent message ${output.MessageId} to queue`);

Logger.debug(`sent message ${output.MessageId} to queue`);
}
```

Expand Down
114 changes: 114 additions & 0 deletions docs/utils/LOGGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
:house: [Home](/README.md) | :books: [Docs](../DOCS.md)

# Logging

This document describes the `Logger` component from the `serverless-common` package.

## How it works

The `Logger` component is the preferred way to write logs from serverless applications. It uses the popular [winston](https://www.npmjs.com/package/winston) logger package to write messages from your serverless functions. These messages are automatically captured in AWS CloudWatch logs.

## Using the `Logger`

To use the logger in any component, first import it.

```ts
import { Logger } from '@leanstacks/serverless-common';
```

The `Logger` is the actual `winston` Logger that has been configured with some sensible defaults. To use the `Logger` in your code do something like this...

```ts
Logger.info('my message');
```

That will emit a log message which looks like this...

```json
{
"level": "info",
"message": "my message",
"requestId": "AWS Lambda Request Identifier",
"timestamp": "2023-12-19T115:20:25.608Z"
}
```

Logs are written as JSON because it is easier to [query and filter AWS CloudWatch Logs which use JSON](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#matching-terms-json-log-events). You may also establish CloudWatch metrics and alarms from CloudWatch Logs filters.

### Including `data` in messages

Often log messages contain data whether that is a simple primitive value or an object. Data should be logged in a standard and predictable way to facilitate log filtering and querying. Building upon the example above, we can log data like this...

```ts
cont tasks = await TaskService.list();
Logger.info('my message', { data: { tasks } });
```

That will emit a log message which looks like this...

```json
{
"data": {
"tasks": []
},
"level": "info",
"message": "my message",
"requestId": "AWS Lambda Request Identifier",
"timestamp": "2023-12-19T115:20:25.608Z"
}
```

### Including an `error` in messages

You may include Errors in log messages. Like with `data`, we should include the Error in a standardized way...

```ts
const error = new Error('System Error');
Logger.info('my messsage', error);
```

That will emit a log message which looks like this...

```json
{
"data": {
"tasks": []
},
"level": "info",
"message": "my message System Error",
"requestId": "AWS Lambda Request Identifier",
"stack": "full stack trace from error",
"timestamp": "2023-12-19T115:20:25.608Z"
}
```

## Configuring the `Logger`

The default configuration of the `Logger` is:

- Logging is enabled
- Level `info`, meaning info, warn, and error logging statments are written, but not debug
- Log messages are written as JSON for improved filtering and querying

### Configuration Options

| Key | Description | Default |
| ----------------- | --------------------------------------------------------------------- | ------- |
| `LOGGING_ENABLED` | Enable or disable logging altogether. | `true` |
| `LOGGING_LEVEL` | The lowest logging level. One of `debug`, `info`, `warn`, or `error`. | `info` |

To modify the default configuration, for example to write `debug` messages to the log, simply overwrite the configuration in your serverless component environment variables such as:

_Example serverless.yml file_

```yml
params:
default:
loggingLevel: info
dev:
loggingLevel: debug

provider:
environment:
LOGGING_LEVEL: ${param:loggingLevel}
```
34 changes: 20 additions & 14 deletions docs/utils/MIDDYFY.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ export const handle = middyfyAPIGateway({ handler });

The handler function appears much the same as what you may be writing already. So what is advantage? The magic happens in the `middyfyAPIGateway` function. This simple function wraps the handler in several middlewares.

First, the [`http-event-normalizer`](https://middy.js.org/docs/middlewares/http-event-normalizer) official Middy middleware ensures that all of the optional elements of an APIGatewayProxyEvent are defined even when empty.
The `logger-initializer` middleware adds AWS Lambda Context metadata to the logger so that all messages may be correlated to a specific Lambda function invocation.

Next, the [`http-json-body-parser`](https://middy.js.org/docs/middlewares/http-json-body-parser) official Middy middleware parses the HTTP request body converting it from a string of JSON into an object.
The [`http-event-normalizer`](https://middy.js.org/docs/middlewares/http-event-normalizer) official Middy middleware ensures that all of the optional elements of an APIGatewayProxyEvent are defined even when empty.

Then, the `validator-joi` middleware validates the APIGatewayProxyEvent with a Joi schema, when a schema is provided in the middleware options.
The [`http-json-body-parser`](https://middy.js.org/docs/middlewares/http-json-body-parser) official Middy middleware parses the HTTP request body converting it from a string of JSON into an object.

Finally, the `http-error-handler` middleware processes errors thrown from the handler function, creating a standardized response based upon the error type.
The `validator-joi` middleware validates the APIGatewayProxyEvent with a Joi schema, when a schema is provided in the middleware options.

The `http-error-handler` middleware processes errors thrown from the handler function, creating a standardized response based upon the error type.

## Creating an API Gateway handler with event validation

Expand Down Expand Up @@ -148,7 +150,7 @@ export const handler = async (event: ScheduledEvent, context: Context): Promise<
await TaskService.sendReminders();

} catch (error) {
console.error('Failed to send task reminders. Detail:', error);
Logger.error('Failed to send task reminders. Detail:', error);
throw new ServiceError(error);
}
};
Expand All @@ -163,7 +165,7 @@ import { middyfyScheduled } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfyScheduled({ handler, logger: console.log });
export const handle = middyfyScheduled({ handler });
```

## Creating a SNS event handler
Expand All @@ -190,7 +192,7 @@ export const handler = async (event: SNSEvent, context: Context): Promise<void>
const tasks:Task[] = await Promise.all(promises);

} catch (error) {
console.error('Failed to create tasks. Detail:', error);
Logger.error('Failed to create tasks. Detail:', error);
throw new ServiceError(error);
}
};
Expand All @@ -205,13 +207,15 @@ import { middyfySNS } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfySNS({ handler, logger: console.log });
export const handle = middyfySNS({ handler });
```

The handler is wrapped with two middlewares.

1. The `logger-initializer` middleware adds AWS Lambda Context metadata to the logger so that all messages may be correlated to a specific Lambda function invocation.

1. The `event-normalizer` middleware performs a JSON parse on the `Message`.
2. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.
1. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.

## Creating a SQS event handler

Expand Down Expand Up @@ -247,7 +251,7 @@ export const handler = async (event: SNSEvent, context: Context): Promise<SQSBat

return response;
} catch (error) {
console.error('Failed to create tasks. Detail:', error);
Logger.error('Failed to create tasks. Detail:', error);
throw new ServiceError(error);
}
};
Expand All @@ -262,13 +266,14 @@ import { middyfySQS } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfySQS({ handler, logger: console.log });
export const handle = middyfySQS({ handler });
```

The handler is wrapped with two middlewares.

1. The `logger-initializer` middleware adds AWS Lambda Context metadata to the logger so that all messages may be correlated to a specific Lambda function invocation.
1. The `event-normalizer` middleware performs a JSON parse on the `body`.
2. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.
1. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.

## Creating a Lambda event handler

Expand Down Expand Up @@ -319,7 +324,7 @@ export const handler = async (
};
}
} catch (error) {
console.warn(`Failed to find Task. Detail: ${error}`);
Logger.warn(`Failed to find Task. Detail: ${error}`);
return {
status: 500,
statusText: `${error}`,
Expand All @@ -338,9 +343,10 @@ import { middyfyLambda } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfyLambda({ handler, logger: console.log });
export const handle = middyfyLambda({ handler });
```

The handler is wrapped with one middleware.

1. The `logger-initializer` middleware adds AWS Lambda Context metadata to the logger so that all messages may be correlated to a specific Lambda function invocation.
1. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.
2 changes: 2 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128';
process.env.AWS_LAMBDA_FUNCTION_NAME = 'aws-lambda-function-name';
process.env.AWS_LAMBDA_FUNCTION_VERSION = 'aws-lambda-function-version';
process.env.AWS_REGION = 'aws-region';
process.env.LOGGING_ENABLED = 'false';
process.env.LOGGING_LEVEL = 'debug';
Loading