@leanstacks/lambda-utils 0.1.0-alpha.3 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug.md +47 -0
- package/.github/ISSUE_TEMPLATE/story.md +25 -0
- package/.github/ISSUE_TEMPLATE/task.md +15 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +39 -0
- package/README.md +178 -1
- package/docs/LOGGING.md +333 -0
- package/docs/README.md +14 -35
- package/jest.config.ts +4 -4
- package/package.json +8 -9
- package/src/logging/logger.test.ts +400 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -78
- package/dist/logging/logger.d.ts +0 -59
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Describe the bug
|
|
10
|
+
|
|
11
|
+
_Provide a clear and concise description of what the bug is._
|
|
12
|
+
|
|
13
|
+
## Steps to reproduce
|
|
14
|
+
|
|
15
|
+
Steps to reproduce the behavior:
|
|
16
|
+
|
|
17
|
+
1. Go to '...'
|
|
18
|
+
2. Click on '....'
|
|
19
|
+
3. Scroll down to '....'
|
|
20
|
+
4. See error
|
|
21
|
+
|
|
22
|
+
## Expected behavior
|
|
23
|
+
|
|
24
|
+
_Provide a clear and concise description of what you expected to happen._
|
|
25
|
+
|
|
26
|
+
## Screenshots
|
|
27
|
+
|
|
28
|
+
_If applicable, add screenshots to help explain your problem._
|
|
29
|
+
|
|
30
|
+
## Environment
|
|
31
|
+
|
|
32
|
+
**Desktop (please complete the following information):**
|
|
33
|
+
|
|
34
|
+
- OS: [e.g. iOS]
|
|
35
|
+
- Browser [e.g. chrome, safari]
|
|
36
|
+
- Version [e.g. 22]
|
|
37
|
+
|
|
38
|
+
**Smartphone (please complete the following information):**
|
|
39
|
+
|
|
40
|
+
- Device: [e.g. iPhone6]
|
|
41
|
+
- OS: [e.g. iOS8.1]
|
|
42
|
+
- Browser [e.g. stock browser, safari]
|
|
43
|
+
- Version [e.g. 22]
|
|
44
|
+
|
|
45
|
+
## Additional context
|
|
46
|
+
|
|
47
|
+
_Add any other context about the problem here._
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Story
|
|
3
|
+
about: New feature or improvement request
|
|
4
|
+
title: ''
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Describe the story
|
|
10
|
+
|
|
11
|
+
_Provide a clear description of the new feature or improvement to existing functionality._
|
|
12
|
+
|
|
13
|
+
## Acceptance criteria
|
|
14
|
+
|
|
15
|
+
_Provide clear acceptance criteria to validate the story is complete._
|
|
16
|
+
|
|
17
|
+
[Gherkin syntax](https://cucumber.io/docs/gherkin/reference) example:
|
|
18
|
+
|
|
19
|
+
> Given the 'PERSONA' has 'DONE SOMETHING'
|
|
20
|
+
> When the 'PERSONA' does 'ONE THING'
|
|
21
|
+
> Then the 'PERSONA' must do 'ANOTHER THING'
|
|
22
|
+
|
|
23
|
+
## Additional context
|
|
24
|
+
|
|
25
|
+
_Add any other context about the story here._
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Task
|
|
3
|
+
about: A chore unrelated to features or problems
|
|
4
|
+
title: ''
|
|
5
|
+
labels: task
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Describe the task
|
|
10
|
+
|
|
11
|
+
_Provide a clear description of the task._
|
|
12
|
+
|
|
13
|
+
## Additional context
|
|
14
|
+
|
|
15
|
+
_Add any other context about the task here._
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
:loudspeaker: **Instructions**
|
|
2
|
+
|
|
3
|
+
- Begin with a **DRAFT** pull request.
|
|
4
|
+
- Follow _italicized instructions_ to add detail to assist the reviewers.
|
|
5
|
+
- After completing all checklist items, change the pull request to **READY**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
### :wrench: Change Summary
|
|
10
|
+
|
|
11
|
+
_Describe the changes included in this pull request. Link to the associated [GitHub](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) or Jira issue(s)._
|
|
12
|
+
|
|
13
|
+
- see #1234
|
|
14
|
+
- Added the [...]
|
|
15
|
+
- Updated the [...]
|
|
16
|
+
- Fixed the [...]
|
|
17
|
+
|
|
18
|
+
### :memo: Checklist
|
|
19
|
+
|
|
20
|
+
_Pull request authors must complete the following tasks before marking the PR as ready to review._
|
|
21
|
+
|
|
22
|
+
- [ ] Complete a self-review of changes
|
|
23
|
+
- [ ] Unit tests have been created or updated
|
|
24
|
+
- [ ] The code is free of [new] lint errors and warnings
|
|
25
|
+
- [ ] Update project documentation as needed: README, /docs, JSDoc, etc.
|
|
26
|
+
|
|
27
|
+
### :test_tube: Steps to Test
|
|
28
|
+
|
|
29
|
+
_Describe the process to test the changes in this pull request._
|
|
30
|
+
|
|
31
|
+
1. Go to [...]
|
|
32
|
+
2. Click on [...]
|
|
33
|
+
3. Verify that [...]
|
|
34
|
+
|
|
35
|
+
### :link: Additional Information
|
|
36
|
+
|
|
37
|
+
_Optionally, provide additional details, screenshots, or URLs that may assist the reviewer._
|
|
38
|
+
|
|
39
|
+
- [...]
|
package/README.md
CHANGED
|
@@ -1,3 +1,180 @@
|
|
|
1
1
|
# Lambda Utilities
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/@leanstacks%2Flambda-utils)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A comprehensive TypeScript utility library for AWS Lambda functions. Provides pre-configured logging, API response formatting, configuration validation, and AWS SDK clients—reducing boilerplate and promoting best practices.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Documentation](#documentation)
|
|
14
|
+
- [Contributing](#contributing)
|
|
15
|
+
- [License](#license)
|
|
16
|
+
- [Support](#support)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @leanstacks/lambda-utils
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Requirements
|
|
25
|
+
|
|
26
|
+
- Node.js 24.x or higher
|
|
27
|
+
- TypeScript 5.0 or higher
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Logging Example
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Logger, withRequestTracking } from '@leanstacks/lambda-utils';
|
|
35
|
+
|
|
36
|
+
const logger = new Logger().instance;
|
|
37
|
+
|
|
38
|
+
export const handler = async (event: any, context: any) => {
|
|
39
|
+
withRequestTracking(event, context);
|
|
40
|
+
|
|
41
|
+
logger.info('Processing request');
|
|
42
|
+
|
|
43
|
+
// Your Lambda handler logic here
|
|
44
|
+
|
|
45
|
+
return { statusCode: 200, body: 'Success' };
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### API Response Example
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { success, badRequest } from '@leanstacks/lambda-utils';
|
|
53
|
+
|
|
54
|
+
export const handler = async (event: any) => {
|
|
55
|
+
if (!event.body) {
|
|
56
|
+
return badRequest({ message: 'Body is required' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Process request
|
|
60
|
+
|
|
61
|
+
return success({ message: 'Request processed successfully' });
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- **📝 Structured Logging** – Pino logger pre-configured for Lambda with automatic AWS request context enrichment
|
|
68
|
+
- **📤 API Response Helpers** – Standard response formatting for API Gateway with proper HTTP status codes
|
|
69
|
+
- **⚙️ Configuration Validation** – Environment variable validation with Zod schema support
|
|
70
|
+
- **🔌 AWS SDK Clients** – Pre-configured AWS SDK v3 clients for DynamoDB, Lambda, and more
|
|
71
|
+
- **🔒 Full TypeScript Support** – Complete type definitions and IDE autocomplete
|
|
72
|
+
- **⚡ Lambda Optimized** – Designed for performance in serverless environments
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
Comprehensive guides and examples are available in the `docs` directory:
|
|
77
|
+
|
|
78
|
+
| Guide | Description |
|
|
79
|
+
| ------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
|
80
|
+
| **[Logging Guide](./docs/LOGGING.md)** | Configure and use structured logging with automatic AWS Lambda context |
|
|
81
|
+
| **[API Gateway Responses](./docs/API_GATEWAY_RESPONSES.md)** | Format responses for API Gateway with standard HTTP patterns |
|
|
82
|
+
| **[Configuration](./docs/CONFIGURATION.md)** | Validate and manage environment variables with type safety |
|
|
83
|
+
| **[AWS Clients](./docs/CLIENTS.md)** | Use pre-configured AWS SDK v3 clients in your handlers |
|
|
84
|
+
| **[Getting Started](./docs/GETTING_STARTED.md)** | Setup and first steps guide |
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
### Logging
|
|
89
|
+
|
|
90
|
+
The Logger utility provides structured logging configured specifically for AWS Lambda:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { Logger } from '@leanstacks/lambda-utils';
|
|
94
|
+
|
|
95
|
+
const logger = new Logger({
|
|
96
|
+
level: 'info', // debug, info, warn, error
|
|
97
|
+
format: 'json', // json or text
|
|
98
|
+
}).instance;
|
|
99
|
+
|
|
100
|
+
logger.info({ message: 'User authenticated', userId: '12345' });
|
|
101
|
+
logger.error({ message: 'Operation failed', error: err.message });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**→ See [Logging Guide](./docs/LOGGING.md) for detailed configuration and best practices**
|
|
105
|
+
|
|
106
|
+
### API Responses
|
|
107
|
+
|
|
108
|
+
Generate properly formatted responses for API Gateway:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { success, error, created, badRequest } from '@leanstacks/lambda-utils';
|
|
112
|
+
|
|
113
|
+
export const handler = async (event: any) => {
|
|
114
|
+
return success({
|
|
115
|
+
data: { id: '123', name: 'Example' },
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**→ See [API Gateway Responses](./docs/API_GATEWAY_RESPONSES.md) for all response types**
|
|
121
|
+
|
|
122
|
+
### Configuration Validation
|
|
123
|
+
|
|
124
|
+
Validate your Lambda environment configuration:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { validateConfig } from '@leanstacks/lambda-utils';
|
|
128
|
+
import { z } from 'zod';
|
|
129
|
+
|
|
130
|
+
const configSchema = z.object({
|
|
131
|
+
DATABASE_URL: z.string().url(),
|
|
132
|
+
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']),
|
|
133
|
+
API_KEY: z.string(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const config = validateConfig(configSchema);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**→ See [Configuration](./docs/CONFIGURATION.md) for validation patterns**
|
|
140
|
+
|
|
141
|
+
### AWS Clients
|
|
142
|
+
|
|
143
|
+
Use pre-configured AWS SDK v3 clients:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { getDynamoDBClient, getLambdaClient } from '@leanstacks/lambda-utils';
|
|
147
|
+
|
|
148
|
+
const dynamoDB = getDynamoDBClient();
|
|
149
|
+
const lambda = getLambdaClient();
|
|
150
|
+
|
|
151
|
+
// Use clients for API calls
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**→ See [AWS Clients](./docs/CLIENTS.md) for available clients and examples**
|
|
155
|
+
|
|
156
|
+
## Examples
|
|
157
|
+
|
|
158
|
+
Example Lambda functions using Lambda Utilities are available in the repository:
|
|
159
|
+
|
|
160
|
+
- API Gateway with logging and response formatting
|
|
161
|
+
- Configuration validation and DynamoDB integration
|
|
162
|
+
- Error handling and structured logging
|
|
163
|
+
|
|
164
|
+
## Reporting Issues
|
|
165
|
+
|
|
166
|
+
If you encounter a bug or have a feature request, please report it on [GitHub Issues](https://github.com/leanstacks/lambda-utils/issues). Include as much detail as possible to help us investigate and resolve the issue quickly.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
This project is licensed under the MIT License - see [LICENSE](./LICENSE) file for details.
|
|
171
|
+
|
|
172
|
+
## Support
|
|
173
|
+
|
|
174
|
+
- **Issues & Questions:** [GitHub Issues](https://github.com/leanstacks/lambda-utils/issues)
|
|
175
|
+
- **Documentation:** [docs](./docs/README.md)
|
|
176
|
+
- **NPM Package:** [@leanstacks/lambda-utils](https://www.npmjs.com/package/@leanstacks/lambda-utils)
|
|
177
|
+
|
|
178
|
+
## Changelog
|
|
179
|
+
|
|
180
|
+
See the project [releases](https://github.com/leanstacks/lambda-utils/releases) for version history and updates.
|
package/docs/LOGGING.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Logging Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to use the Logger utility to implement structured logging in your AWS Lambda functions using TypeScript.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Logger utility provides a thin wrapper around [Pino](https://getpino.io/) configured specifically for AWS Lambda. It automatically includes Lambda request context information in your logs and supports multiple output formats suitable for CloudWatch.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
The Logger utility is included in the `@leanstacks/lambda-utils` package:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @leanstacks/lambda-utils
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Basic Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Logger } from '@leanstacks/lambda-utils';
|
|
23
|
+
|
|
24
|
+
const logger = new Logger().instance;
|
|
25
|
+
|
|
26
|
+
export const handler = async (event: any, context: any) => {
|
|
27
|
+
logger.info('[Handler] > Processing request');
|
|
28
|
+
|
|
29
|
+
// Your handler logic here
|
|
30
|
+
|
|
31
|
+
logger.info({ key: 'value' }, '[Handler] < Completed request');
|
|
32
|
+
|
|
33
|
+
return { statusCode: 200, body: 'Success' };
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
The Logger accepts a configuration object to customize its behavior:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { Logger } from '@leanstacks/lambda-utils';
|
|
43
|
+
|
|
44
|
+
const logger = new Logger({
|
|
45
|
+
enabled: true, // Enable/disable logging (default: true)
|
|
46
|
+
level: 'info', // Minimum log level (default: 'info')
|
|
47
|
+
format: 'json', // Output format: 'json' or 'text' (default: 'json')
|
|
48
|
+
}).instance;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Configuration Options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
| --------- | ---------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
|
|
55
|
+
| `enabled` | `boolean` | `true` | Whether logging is enabled. Set to `false` to disable all logging output. |
|
|
56
|
+
| `level` | `'debug' \| 'info' \| 'warn' \| 'error'` | `'info'` | Minimum log level to output. Messages below this level are filtered out. |
|
|
57
|
+
| `format` | `'json' \| 'text'` | `'json'` | Output format for log messages. Use `'json'` for structured logging or `'text'` for human-readable logs. |
|
|
58
|
+
|
|
59
|
+
### Log Levels
|
|
60
|
+
|
|
61
|
+
Log levels are ordered by severity:
|
|
62
|
+
|
|
63
|
+
- **`debug`**: Detailed information for diagnosing problems (lowest severity)
|
|
64
|
+
- **`info`**: General informational messages about application flow
|
|
65
|
+
- **`warn`**: Warning messages for potentially harmful situations
|
|
66
|
+
- **`error`**: Error messages for serious problems (highest severity)
|
|
67
|
+
|
|
68
|
+
## Logging Examples
|
|
69
|
+
|
|
70
|
+
### Basic Logging
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const logger = new Logger().instance;
|
|
74
|
+
|
|
75
|
+
logger.debug('Detailed diagnostic information');
|
|
76
|
+
logger.info('Application event or milestone');
|
|
77
|
+
logger.warn('Warning: something unexpected occurred');
|
|
78
|
+
logger.error('Error: operation failed');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
When the log message contains a simple string, pass the string as the only aregument to the logger function.
|
|
82
|
+
|
|
83
|
+
### Structured Logging with Objects
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const logger = new Logger().instance;
|
|
87
|
+
|
|
88
|
+
const userId = '12345';
|
|
89
|
+
const permissions = ['user:read', 'user:write'];
|
|
90
|
+
|
|
91
|
+
logger.info(
|
|
92
|
+
{
|
|
93
|
+
userId,
|
|
94
|
+
permissions,
|
|
95
|
+
},
|
|
96
|
+
'User authenticated',
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
When using structured logging, pass the context attributes object as the first parameter and the string log message as the second parameter. This allows the logger to properly format messages as either JSON or text.
|
|
101
|
+
|
|
102
|
+
### Error Logging
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const logger = new Logger().instance;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Your code here
|
|
109
|
+
} catch (error) {
|
|
110
|
+
logger.error(
|
|
111
|
+
{
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
114
|
+
},
|
|
115
|
+
'Operation failed',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Advanced Usage
|
|
121
|
+
|
|
122
|
+
### Request Tracking Middleware
|
|
123
|
+
|
|
124
|
+
The `withRequestTracking` middleware automatically adds AWS Lambda context information to all log messages. This enriches your logs with request IDs, function names, and other Lambda metadata.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { Logger, withRequestTracking } from '@leanstacks/lambda-utils';
|
|
128
|
+
|
|
129
|
+
const logger = new Logger().instance;
|
|
130
|
+
|
|
131
|
+
export const handler = async (event: any, context: any) => {
|
|
132
|
+
// Add Lambda context to all subsequent log messages
|
|
133
|
+
withRequestTracking(event, context);
|
|
134
|
+
|
|
135
|
+
logger.info('Request started');
|
|
136
|
+
|
|
137
|
+
// Your handler logic here
|
|
138
|
+
|
|
139
|
+
return { statusCode: 200 };
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Environment-Based Configuration
|
|
144
|
+
|
|
145
|
+
Configure logging based on your environment:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { Logger } from '@leanstacks/lambda-utils';
|
|
149
|
+
|
|
150
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
151
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
152
|
+
|
|
153
|
+
const logger = new Logger({
|
|
154
|
+
level: isDevelopment ? 'debug' : 'info',
|
|
155
|
+
format: isProduction ? 'json' : 'text',
|
|
156
|
+
}).instance;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Singleton Pattern for Reusable Logger
|
|
160
|
+
|
|
161
|
+
For best performance, create a single logger instance and reuse it throughout your application:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// logger.ts
|
|
165
|
+
import { Logger } from '@leanstacks/lambda-utils';
|
|
166
|
+
|
|
167
|
+
export const logger = new Logger({
|
|
168
|
+
level: (process.env.LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error') || 'info',
|
|
169
|
+
format: (process.env.LOG_FORMAT as 'json' | 'text') || 'json',
|
|
170
|
+
}).instance;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then import it in your handlers:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// handler.ts
|
|
177
|
+
import { logger } from './logger';
|
|
178
|
+
|
|
179
|
+
export const handler = async (event: any) => {
|
|
180
|
+
logger.info({ message: 'Processing event', event });
|
|
181
|
+
|
|
182
|
+
// Your handler logic here
|
|
183
|
+
|
|
184
|
+
return { statusCode: 200 };
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Best Practices
|
|
189
|
+
|
|
190
|
+
### 1. Use Structured Logging
|
|
191
|
+
|
|
192
|
+
Prefer objects over string concatenation:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// ✅ Good: Structured logging
|
|
196
|
+
logger.info(
|
|
197
|
+
{
|
|
198
|
+
userId: user.id,
|
|
199
|
+
},
|
|
200
|
+
'User login',
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// ❌ Avoid: String concatenation
|
|
204
|
+
logger.info(`User ${user.id} logged in at ${new Date().toISOString()}`);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 2. Include Relevant Context
|
|
208
|
+
|
|
209
|
+
Include all relevant information that will help with debugging and monitoring:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
logger.info(
|
|
213
|
+
{
|
|
214
|
+
orderId: order.id,
|
|
215
|
+
amount: order.total,
|
|
216
|
+
paymentMethod: order.paymentMethod,
|
|
217
|
+
duration: endTime - startTime,
|
|
218
|
+
},
|
|
219
|
+
'Payment processed',
|
|
220
|
+
);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 3. Use Appropriate Log Levels
|
|
224
|
+
|
|
225
|
+
Choose log levels that match the severity and importance of the event:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
logger.debug('Cache hit for user profile'); // Development diagnostics
|
|
229
|
+
logger.info('User registered successfully'); // Normal operations
|
|
230
|
+
logger.warn('API rate limit approaching'); // Potential issues
|
|
231
|
+
logger.error('Database connection failed'); // Critical failures
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 4. Avoid Logging Sensitive Information
|
|
235
|
+
|
|
236
|
+
Never log passwords, API keys, tokens, or personally identifiable information (PII):
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// ❌ Never do this
|
|
240
|
+
logger.info({ password: user.password });
|
|
241
|
+
|
|
242
|
+
// ✅ Log safe information
|
|
243
|
+
logger.info({ userId: user.id, email: user.email });
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 5. Performance Considerations
|
|
247
|
+
|
|
248
|
+
The logger is optimized for Lambda and uses lazy evaluation. Only use `debug` level logs in development:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// Disable debug logs in production for better performance
|
|
252
|
+
const logger = new Logger({
|
|
253
|
+
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
|
|
254
|
+
}).instance;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Output Formats
|
|
258
|
+
|
|
259
|
+
### JSON Format (Default)
|
|
260
|
+
|
|
261
|
+
Best for production environments and log aggregation services like CloudWatch, Datadog, or Splunk:
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"timestamp": "2025-12-18T13:42:40.502Z",
|
|
266
|
+
"level": "INFO",
|
|
267
|
+
"requestId": "req-abc-123",
|
|
268
|
+
"message": {
|
|
269
|
+
"awsRequestId": "req-def-456",
|
|
270
|
+
"x-correlation-trace-id": "Root=1-2a-28ab;Parent=1e6;Sampled=0;Lineage=1:bf3:0",
|
|
271
|
+
"x-correlation-id": "crl-abc-123",
|
|
272
|
+
"time": 1702900123456,
|
|
273
|
+
"pid": 1,
|
|
274
|
+
"hostname": "lambda-container",
|
|
275
|
+
"key": "value",
|
|
276
|
+
"msg": "User authenticated"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Text Format
|
|
282
|
+
|
|
283
|
+
Best for local development and human-readable output:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
[2024-12-18T12:34:56.789Z] INFO: User authenticated userId=12345 requestId=req-abc-123
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Testing
|
|
290
|
+
|
|
291
|
+
When testing Lambda functions that use the logger, you can mock or configure the logger:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { Logger } from '@leanstacks/lambda-utils';
|
|
295
|
+
|
|
296
|
+
describe('MyHandler', () => {
|
|
297
|
+
it('should log info message', () => {
|
|
298
|
+
const logger = new Logger({
|
|
299
|
+
enabled: true,
|
|
300
|
+
level: 'info',
|
|
301
|
+
}).instance;
|
|
302
|
+
|
|
303
|
+
const spyLog = jest.spyOn(logger, 'info');
|
|
304
|
+
|
|
305
|
+
// Your test code here
|
|
306
|
+
|
|
307
|
+
expect(spyLog).toHaveBeenCalledWith({
|
|
308
|
+
message: 'Expected message',
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Troubleshooting
|
|
315
|
+
|
|
316
|
+
### Logs Not Appearing
|
|
317
|
+
|
|
318
|
+
1. **Check if logging is enabled**: Verify `enabled: true` in configuration
|
|
319
|
+
2. **Check log level**: Ensure the message log level meets the configured minimum level. Check the Lambda function [Logging configuration application log level](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-log-level.html).
|
|
320
|
+
3. **Check CloudWatch**: Logs appear in CloudWatch Logs under `/aws/lambda/[function-name]`
|
|
321
|
+
|
|
322
|
+
### Performance Issues
|
|
323
|
+
|
|
324
|
+
1. **Use appropriate log level**: Reduce logs in production by using `level: 'info'`
|
|
325
|
+
2. **Limit object size**: Avoid logging very large objects that could impact performance
|
|
326
|
+
3. **Use singleton pattern**: Create one logger instance and reuse it
|
|
327
|
+
|
|
328
|
+
## Further reading
|
|
329
|
+
|
|
330
|
+
- [Pino Documentation](https://getpino.io/)
|
|
331
|
+
- [AWS Lambda Environment and Context](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html)
|
|
332
|
+
- [CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html)
|
|
333
|
+
- [Back to the project documentation](README.md)
|
package/docs/README.md
CHANGED
|
@@ -2,47 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Welcome to the Lambda Utilities documentation. This library provides a comprehensive set of utilities and helper functions to streamline the development of AWS Lambda functions using TypeScript.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- [Logging](./LOGGING.md)
|
|
9
|
-
- [API Gateway Responses](./API_GATEWAY_RESPONSES.md)
|
|
10
|
-
- [Configuration](./CONFIGURATION.md)
|
|
11
|
-
- [Clients](./CLIENTS.md)
|
|
7
|
+
Lambda Utilities is a collection of pre-configured tools and helpers designed to reduce boilerplate code when developing AWS Lambda functions. It provides utilities for logging, API responses, configuration validation, and AWS SDK client management—all with full TypeScript support.
|
|
12
8
|
|
|
13
|
-
##
|
|
9
|
+
## Documentation
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Use a utility in your Lambda function:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { getLogger } from '@leanstacks/lambda-utils';
|
|
25
|
-
import { success } from '@leanstacks/lambda-utils';
|
|
26
|
-
|
|
27
|
-
const logger = getLogger();
|
|
28
|
-
|
|
29
|
-
export const handler = async (event: any) => {
|
|
30
|
-
logger.info({ message: 'Processing event', event });
|
|
31
|
-
|
|
32
|
-
// Your handler logic here
|
|
33
|
-
|
|
34
|
-
return success({ message: 'Success' });
|
|
35
|
-
};
|
|
36
|
-
```
|
|
11
|
+
- **[Logging Guide](./LOGGING.md)** – Implement structured logging in your Lambda functions with Pino and automatic AWS context enrichment
|
|
12
|
+
- **[API Gateway Responses](./API_GATEWAY_RESPONSES.md)** – Format Lambda responses for API Gateway with standard HTTP status codes and headers
|
|
13
|
+
- **[Configuration](./CONFIGURATION.md)** – Validate environment variables and configuration with Zod type safety
|
|
14
|
+
- **[AWS Clients](./CLIENTS.md)** – Pre-configured AWS SDK v3 clients optimized for Lambda
|
|
15
|
+
- **[Getting Started](./GETTING_STARTED.md)** – Quick setup and installation instructions
|
|
37
16
|
|
|
38
17
|
## Features
|
|
39
18
|
|
|
40
|
-
- **
|
|
41
|
-
- **API
|
|
42
|
-
- **Configuration
|
|
43
|
-
- **AWS Clients
|
|
44
|
-
- **Type Safe
|
|
19
|
+
- 📝 **Structured Logging** – Pino logger pre-configured for Lambda with automatic request context
|
|
20
|
+
- 📤 **API Response Helpers** – Standard response formatting for API Gateway integration
|
|
21
|
+
- ⚙️ **Configuration Validation** – Environment variable validation with Zod schema support
|
|
22
|
+
- 🔌 **AWS Clients** – Pre-configured AWS SDK v3 clients for common services
|
|
23
|
+
- 🔒 **Type Safe** – Full TypeScript support with comprehensive type definitions
|
|
45
24
|
|
|
46
25
|
## Support
|
|
47
26
|
|
|
48
|
-
For issues or questions,
|
|
27
|
+
For issues or questions, visit the [GitHub repository](https://github.com/leanstacks/lambda-utils).
|
package/jest.config.ts
CHANGED
|
@@ -3,13 +3,13 @@ import type { Config } from 'jest';
|
|
|
3
3
|
const config: Config = {
|
|
4
4
|
preset: 'ts-jest',
|
|
5
5
|
testEnvironment: 'node',
|
|
6
|
-
|
|
7
|
-
testMatch: ['**/*.test.ts'],
|
|
6
|
+
testMatch: ['<rootDir>/src/**/*.test.ts'],
|
|
8
7
|
moduleNameMapper: {
|
|
9
8
|
'^@/(.*)$': '<rootDir>/$1',
|
|
10
9
|
},
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
coverageDirectory: 'coverage',
|
|
11
|
+
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts', '!src/**/index.ts'],
|
|
12
|
+
coverageReporters: ['json', 'json-summary', 'lcov', 'text', 'clover'],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export default config;
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanstacks/lambda-utils",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "A collection of utilities and helper functions designed to streamline the development of AWS Lambda functions using TypeScript.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "rollup -c",
|
|
9
9
|
"build:watch": "rollup -c -w",
|
|
10
|
-
"clean": "rimraf dist",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"test:coverage": "jest --coverage",
|
|
10
|
+
"clean": "rimraf coverage dist",
|
|
11
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
14
13
|
"lint": "eslint src",
|
|
15
14
|
"lint:fix": "eslint src --fix",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"test:watch": "jest --watch",
|
|
17
|
+
"test:coverage": "jest --coverage"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"lambda",
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"pino": "10.1.0",
|
|
47
|
-
"pino-lambda": "4.4.1"
|
|
48
|
-
"zod": "4.2.1"
|
|
47
|
+
"pino-lambda": "4.4.1"
|
|
49
48
|
}
|
|
50
49
|
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import pino from 'pino';
|
|
3
|
+
import { CloudwatchLogFormatter, pinoLambdaDestination, StructuredLogFormatter } from 'pino-lambda';
|
|
4
|
+
import { Logger, withRequestTracking } from './logger';
|
|
5
|
+
|
|
6
|
+
// Mock pino-lambda module
|
|
7
|
+
jest.mock('pino-lambda');
|
|
8
|
+
|
|
9
|
+
// Mock pino module
|
|
10
|
+
jest.mock('pino');
|
|
11
|
+
|
|
12
|
+
describe('Logger', () => {
|
|
13
|
+
// Setup and cleanup
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('withRequestTracking', () => {
|
|
23
|
+
it('should be exported from logger module', () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
// withRequestTracking is exported from logger.ts and is the result of calling lambdaRequestTracker()
|
|
26
|
+
// from pino-lambda. Jest mocks mean it will be the mocked value.
|
|
27
|
+
|
|
28
|
+
// Act & Assert
|
|
29
|
+
// We just verify that it was exported (defined by the import statement at the top)
|
|
30
|
+
// The actual functionality of lambdaRequestTracker is tested in pino-lambda
|
|
31
|
+
expect(typeof withRequestTracking === 'function' || withRequestTracking === undefined).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('constructor', () => {
|
|
36
|
+
it('should create Logger with default configuration', () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const mockLogger = { info: jest.fn() };
|
|
39
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
40
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
41
|
+
|
|
42
|
+
// Act
|
|
43
|
+
const logger = new Logger();
|
|
44
|
+
|
|
45
|
+
// Assert
|
|
46
|
+
expect(logger).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should create Logger with custom enabled configuration', () => {
|
|
50
|
+
// Arrange
|
|
51
|
+
const mockLogger = { info: jest.fn() };
|
|
52
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
53
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const logger = new Logger({ enabled: false });
|
|
57
|
+
const _instance = logger.instance;
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(pino).toHaveBeenCalledWith(
|
|
61
|
+
expect.objectContaining({
|
|
62
|
+
enabled: false,
|
|
63
|
+
}),
|
|
64
|
+
expect.anything(),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should create Logger with custom log level configuration', () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
const mockLogger = { info: jest.fn() };
|
|
71
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
72
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
73
|
+
|
|
74
|
+
// Act
|
|
75
|
+
const logger = new Logger({ level: 'debug' });
|
|
76
|
+
const _instance = logger.instance;
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(pino).toHaveBeenCalledWith(
|
|
80
|
+
expect.objectContaining({
|
|
81
|
+
level: 'debug',
|
|
82
|
+
}),
|
|
83
|
+
expect.anything(),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should create Logger with custom format configuration (json)', () => {
|
|
88
|
+
// Arrange
|
|
89
|
+
const mockLogger = { info: jest.fn() };
|
|
90
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
91
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
92
|
+
|
|
93
|
+
// Act
|
|
94
|
+
const logger = new Logger({ format: 'json' });
|
|
95
|
+
const _instance = logger.instance;
|
|
96
|
+
|
|
97
|
+
// Assert
|
|
98
|
+
expect(StructuredLogFormatter).toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should create Logger with custom format configuration (text)', () => {
|
|
102
|
+
// Arrange
|
|
103
|
+
const mockLogger = { info: jest.fn() };
|
|
104
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
105
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
106
|
+
|
|
107
|
+
// Act
|
|
108
|
+
const logger = new Logger({ format: 'text' });
|
|
109
|
+
const _instance = logger.instance;
|
|
110
|
+
|
|
111
|
+
// Assert
|
|
112
|
+
expect(CloudwatchLogFormatter).toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should merge provided config with defaults', () => {
|
|
116
|
+
// Arrange
|
|
117
|
+
const mockLogger = { info: jest.fn() };
|
|
118
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
119
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
120
|
+
|
|
121
|
+
// Act
|
|
122
|
+
const logger = new Logger({ level: 'error' });
|
|
123
|
+
const _instance = logger.instance;
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
expect(pino).toHaveBeenCalledWith(
|
|
127
|
+
expect.objectContaining({
|
|
128
|
+
enabled: true,
|
|
129
|
+
level: 'error',
|
|
130
|
+
}),
|
|
131
|
+
expect.anything(),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('instance getter', () => {
|
|
137
|
+
it('should return a Pino logger instance', () => {
|
|
138
|
+
// Arrange
|
|
139
|
+
const mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() };
|
|
140
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
141
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
142
|
+
const logger = new Logger();
|
|
143
|
+
|
|
144
|
+
// Act
|
|
145
|
+
const instance = logger.instance;
|
|
146
|
+
|
|
147
|
+
// Assert
|
|
148
|
+
expect(instance).toBe(mockLogger);
|
|
149
|
+
expect(instance).toHaveProperty('info');
|
|
150
|
+
expect(instance).toHaveProperty('warn');
|
|
151
|
+
expect(instance).toHaveProperty('error');
|
|
152
|
+
expect(instance).toHaveProperty('debug');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should create logger instance only once (lazy initialization)', () => {
|
|
156
|
+
// Arrange
|
|
157
|
+
const mockLogger = { info: jest.fn() };
|
|
158
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
159
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
160
|
+
const logger = new Logger();
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
const instance1 = logger.instance;
|
|
164
|
+
const instance2 = logger.instance;
|
|
165
|
+
|
|
166
|
+
// Assert
|
|
167
|
+
expect(instance1).toBe(instance2);
|
|
168
|
+
expect(pino).toHaveBeenCalledTimes(1);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should configure pino with enabled flag', () => {
|
|
172
|
+
// Arrange
|
|
173
|
+
const mockLogger = { info: jest.fn() };
|
|
174
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
175
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
176
|
+
|
|
177
|
+
// Act
|
|
178
|
+
const logger = new Logger({ enabled: false });
|
|
179
|
+
const _instance = logger.instance;
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(pino).toHaveBeenCalledWith(
|
|
183
|
+
expect.objectContaining({
|
|
184
|
+
enabled: false,
|
|
185
|
+
}),
|
|
186
|
+
expect.anything(),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should configure pino with log level', () => {
|
|
191
|
+
// Arrange
|
|
192
|
+
const mockLogger = { info: jest.fn() };
|
|
193
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
194
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
195
|
+
|
|
196
|
+
// Act
|
|
197
|
+
const logger = new Logger({ level: 'warn' });
|
|
198
|
+
const _instance = logger.instance;
|
|
199
|
+
|
|
200
|
+
// Assert
|
|
201
|
+
expect(pino).toHaveBeenCalledWith(
|
|
202
|
+
expect.objectContaining({
|
|
203
|
+
level: 'warn',
|
|
204
|
+
}),
|
|
205
|
+
expect.anything(),
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should call pinoLambdaDestination with selected formatter', () => {
|
|
210
|
+
// Arrange
|
|
211
|
+
const mockLogger = { info: jest.fn() };
|
|
212
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
213
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
214
|
+
|
|
215
|
+
// Act
|
|
216
|
+
const logger = new Logger({ format: 'json' });
|
|
217
|
+
const _instance = logger.instance;
|
|
218
|
+
|
|
219
|
+
// Assert
|
|
220
|
+
expect(pinoLambdaDestination).toHaveBeenCalledWith(
|
|
221
|
+
expect.objectContaining({
|
|
222
|
+
formatter: expect.any(StructuredLogFormatter),
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should use StructuredLogFormatter when format is json', () => {
|
|
228
|
+
// Arrange
|
|
229
|
+
const mockLogger = { info: jest.fn() };
|
|
230
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
231
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
232
|
+
|
|
233
|
+
// Act
|
|
234
|
+
const logger = new Logger({ format: 'json' });
|
|
235
|
+
const _instance = logger.instance;
|
|
236
|
+
|
|
237
|
+
// Assert
|
|
238
|
+
expect(StructuredLogFormatter).toHaveBeenCalled();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should use CloudwatchLogFormatter when format is text', () => {
|
|
242
|
+
// Arrange
|
|
243
|
+
const mockLogger = { info: jest.fn() };
|
|
244
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
245
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
246
|
+
|
|
247
|
+
// Act
|
|
248
|
+
const logger = new Logger({ format: 'text' });
|
|
249
|
+
const _instance = logger.instance;
|
|
250
|
+
|
|
251
|
+
// Assert
|
|
252
|
+
expect(CloudwatchLogFormatter).toHaveBeenCalled();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('Logger configurations', () => {
|
|
257
|
+
it('should support all log levels', () => {
|
|
258
|
+
// Arrange
|
|
259
|
+
const mockLogger = { info: jest.fn() };
|
|
260
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
261
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
262
|
+
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['debug', 'info', 'warn', 'error'];
|
|
263
|
+
|
|
264
|
+
// Act & Assert
|
|
265
|
+
levels.forEach((level) => {
|
|
266
|
+
jest.clearAllMocks();
|
|
267
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
268
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
269
|
+
|
|
270
|
+
const logger = new Logger({ level });
|
|
271
|
+
const _instance = logger.instance;
|
|
272
|
+
|
|
273
|
+
expect(pino).toHaveBeenCalledWith(
|
|
274
|
+
expect.objectContaining({
|
|
275
|
+
level,
|
|
276
|
+
}),
|
|
277
|
+
expect.anything(),
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should support both json and text formats', () => {
|
|
283
|
+
// Arrange
|
|
284
|
+
const mockLogger = { info: jest.fn() };
|
|
285
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
286
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
287
|
+
|
|
288
|
+
// Act
|
|
289
|
+
const jsonLogger = new Logger({ format: 'json' });
|
|
290
|
+
const _jsonInstance = jsonLogger.instance;
|
|
291
|
+
const structuredFormatterCallCount = (StructuredLogFormatter as jest.Mock).mock.calls.length;
|
|
292
|
+
|
|
293
|
+
jest.clearAllMocks();
|
|
294
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
295
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
296
|
+
|
|
297
|
+
const textLogger = new Logger({ format: 'text' });
|
|
298
|
+
const _textInstance = textLogger.instance;
|
|
299
|
+
|
|
300
|
+
// Assert
|
|
301
|
+
expect(structuredFormatterCallCount).toBeGreaterThan(0);
|
|
302
|
+
expect(CloudwatchLogFormatter).toHaveBeenCalled();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should support enabled and disabled logging', () => {
|
|
306
|
+
// Arrange
|
|
307
|
+
const mockLogger = { info: jest.fn() };
|
|
308
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
309
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
310
|
+
|
|
311
|
+
// Act
|
|
312
|
+
const enabledLogger = new Logger({ enabled: true });
|
|
313
|
+
const _enabledInstance = enabledLogger.instance;
|
|
314
|
+
const firstCallArgs = jest.mocked(pino).mock.calls[0];
|
|
315
|
+
|
|
316
|
+
jest.clearAllMocks();
|
|
317
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
318
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
319
|
+
|
|
320
|
+
const disabledLogger = new Logger({ enabled: false });
|
|
321
|
+
const _disabledInstance = disabledLogger.instance;
|
|
322
|
+
const secondCallArgs = jest.mocked(pino).mock.calls[0];
|
|
323
|
+
|
|
324
|
+
// Assert
|
|
325
|
+
expect(firstCallArgs[0]).toEqual(expect.objectContaining({ enabled: true }));
|
|
326
|
+
expect(secondCallArgs[0]).toEqual(expect.objectContaining({ enabled: false }));
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('integration scenarios', () => {
|
|
331
|
+
it('should create multiple logger instances with different configurations', () => {
|
|
332
|
+
// Arrange
|
|
333
|
+
const mockLogger1 = { info: jest.fn(), level: 'debug' };
|
|
334
|
+
const mockLogger2 = { info: jest.fn(), level: 'error' };
|
|
335
|
+
jest
|
|
336
|
+
.mocked(pino)
|
|
337
|
+
.mockReturnValueOnce(mockLogger1 as unknown as any)
|
|
338
|
+
.mockReturnValueOnce(mockLogger2 as unknown as any);
|
|
339
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
340
|
+
|
|
341
|
+
// Act
|
|
342
|
+
const debugLogger = new Logger({ level: 'debug', format: 'json' });
|
|
343
|
+
const errorLogger = new Logger({ level: 'error', format: 'text' });
|
|
344
|
+
|
|
345
|
+
const instance1 = debugLogger.instance;
|
|
346
|
+
const instance2 = errorLogger.instance;
|
|
347
|
+
|
|
348
|
+
// Assert
|
|
349
|
+
expect(instance1).toBe(mockLogger1);
|
|
350
|
+
expect(instance2).toBe(mockLogger2);
|
|
351
|
+
expect(pino).toHaveBeenCalledTimes(2);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should handle partial configuration overrides', () => {
|
|
355
|
+
// Arrange
|
|
356
|
+
const mockLogger = { info: jest.fn() };
|
|
357
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
358
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
359
|
+
|
|
360
|
+
// Act
|
|
361
|
+
const logger = new Logger({ level: 'warn' });
|
|
362
|
+
const _instance = logger.instance;
|
|
363
|
+
|
|
364
|
+
// Assert - should have custom level but default enabled and format
|
|
365
|
+
expect(pino).toHaveBeenCalledWith(
|
|
366
|
+
expect.objectContaining({
|
|
367
|
+
enabled: true,
|
|
368
|
+
level: 'warn',
|
|
369
|
+
}),
|
|
370
|
+
expect.anything(),
|
|
371
|
+
);
|
|
372
|
+
expect(StructuredLogFormatter).toHaveBeenCalled();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should handle full configuration override', () => {
|
|
376
|
+
// Arrange
|
|
377
|
+
const mockLogger = { info: jest.fn() };
|
|
378
|
+
jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
|
|
379
|
+
(pinoLambdaDestination as jest.Mock).mockReturnValue({});
|
|
380
|
+
|
|
381
|
+
// Act
|
|
382
|
+
const logger = new Logger({
|
|
383
|
+
enabled: false,
|
|
384
|
+
level: 'error',
|
|
385
|
+
format: 'text',
|
|
386
|
+
});
|
|
387
|
+
const _instance = logger.instance;
|
|
388
|
+
|
|
389
|
+
// Assert
|
|
390
|
+
expect(pino).toHaveBeenCalledWith(
|
|
391
|
+
expect.objectContaining({
|
|
392
|
+
enabled: false,
|
|
393
|
+
level: 'error',
|
|
394
|
+
}),
|
|
395
|
+
expect.anything(),
|
|
396
|
+
);
|
|
397
|
+
expect(CloudwatchLogFormatter).toHaveBeenCalled();
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
});
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Logger, LoggerConfig, withRequestTracking } from './logging/logger';
|
package/dist/index.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import pino from 'pino';
|
|
2
|
-
import { lambdaRequestTracker, StructuredLogFormatter, CloudwatchLogFormatter, pinoLambdaDestination } from 'pino-lambda';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Logger middleware which adds AWS Lambda attributes to log messages.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { withRequestTracking } from '@leanstacks/lambda-utils';
|
|
10
|
-
*
|
|
11
|
-
* export const handler = async (event, context) => {
|
|
12
|
-
* withRequestTracking(event, context);
|
|
13
|
-
*
|
|
14
|
-
* // Your Lambda handler logic here
|
|
15
|
-
* };
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
const withRequestTracking = lambdaRequestTracker();
|
|
19
|
-
/**
|
|
20
|
-
* Logger class which provides a Pino logger instance with AWS Lambda attributes.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```typescript
|
|
24
|
-
* import { Logger } from '@leanstacks/lambda-utils';
|
|
25
|
-
* const logger = new Logger().instance;
|
|
26
|
-
*
|
|
27
|
-
* logger.info('Hello, world!');
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
class Logger {
|
|
31
|
-
constructor(config) {
|
|
32
|
-
this._loggerConfig = {
|
|
33
|
-
enabled: true,
|
|
34
|
-
level: 'info',
|
|
35
|
-
format: 'json',
|
|
36
|
-
};
|
|
37
|
-
this._instance = null;
|
|
38
|
-
/**
|
|
39
|
-
* Creates a new, fully configured Pino logger instance.
|
|
40
|
-
*/
|
|
41
|
-
this._createLogger = () => {
|
|
42
|
-
const formatter = this._loggerConfig.format === 'json' ? new StructuredLogFormatter() : new CloudwatchLogFormatter();
|
|
43
|
-
const lambdaDestination = pinoLambdaDestination({
|
|
44
|
-
formatter,
|
|
45
|
-
});
|
|
46
|
-
return pino({
|
|
47
|
-
enabled: this._loggerConfig.enabled,
|
|
48
|
-
level: this._loggerConfig.level,
|
|
49
|
-
}, lambdaDestination);
|
|
50
|
-
};
|
|
51
|
-
if (config) {
|
|
52
|
-
this._loggerConfig = {
|
|
53
|
-
enabled: config.enabled ?? true,
|
|
54
|
-
level: config.level ?? 'info',
|
|
55
|
-
format: config.format ?? 'json',
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Get the logger instance.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```typescript
|
|
64
|
-
* import { Logger } from '@leanstacks/lambda-utils';
|
|
65
|
-
* const logger = new Logger().instance;
|
|
66
|
-
*
|
|
67
|
-
* logger.info('Hello, world!');
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
get instance() {
|
|
71
|
-
if (this._instance === null) {
|
|
72
|
-
this._instance = this._createLogger();
|
|
73
|
-
}
|
|
74
|
-
return this._instance;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export { Logger, withRequestTracking };
|
package/dist/logging/logger.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import pino from 'pino';
|
|
2
|
-
/**
|
|
3
|
-
* Logger middleware which adds AWS Lambda attributes to log messages.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```typescript
|
|
7
|
-
* import { withRequestTracking } from '@leanstacks/lambda-utils';
|
|
8
|
-
*
|
|
9
|
-
* export const handler = async (event, context) => {
|
|
10
|
-
* withRequestTracking(event, context);
|
|
11
|
-
*
|
|
12
|
-
* // Your Lambda handler logic here
|
|
13
|
-
* };
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
export declare const withRequestTracking: (event: import("pino-lambda").LambdaEvent, context: import("pino-lambda").LambdaContext) => void;
|
|
17
|
-
/**
|
|
18
|
-
* Configuration options for the Logger
|
|
19
|
-
*/
|
|
20
|
-
export interface LoggerConfig {
|
|
21
|
-
/** Whether logging is enabled */
|
|
22
|
-
enabled?: boolean;
|
|
23
|
-
/** Minimum log level (e.g., 'debug', 'info', 'warn', 'error') */
|
|
24
|
-
level?: 'debug' | 'info' | 'warn' | 'error';
|
|
25
|
-
/** Output format: 'json' for StructuredLogFormatter, 'text' for CloudwatchLogFormatter */
|
|
26
|
-
format?: 'json' | 'text';
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Logger class which provides a Pino logger instance with AWS Lambda attributes.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```typescript
|
|
33
|
-
* import { Logger } from '@leanstacks/lambda-utils';
|
|
34
|
-
* const logger = new Logger().instance;
|
|
35
|
-
*
|
|
36
|
-
* logger.info('Hello, world!');
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare class Logger {
|
|
40
|
-
private _loggerConfig;
|
|
41
|
-
private _instance;
|
|
42
|
-
constructor(config?: LoggerConfig);
|
|
43
|
-
/**
|
|
44
|
-
* Creates a new, fully configured Pino logger instance.
|
|
45
|
-
*/
|
|
46
|
-
private _createLogger;
|
|
47
|
-
/**
|
|
48
|
-
* Get the logger instance.
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* import { Logger } from '@leanstacks/lambda-utils';
|
|
53
|
-
* const logger = new Logger().instance;
|
|
54
|
-
*
|
|
55
|
-
* logger.info('Hello, world!');
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
get instance(): pino.Logger;
|
|
59
|
-
}
|