@onebun/logger 0.1.0 → 0.1.1
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/README.md +221 -0
- package/package.json +12 -2
- package/src/docs-examples.test.ts +337 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# @onebun/logger
|
|
2
|
+
|
|
3
|
+
Structured logging package for OneBun framework with Effect.js integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📝 **Structured Logging** - JSON and pretty-print output formats
|
|
8
|
+
- 🎨 **Pretty Console Output** - Colorized logs for development
|
|
9
|
+
- 📊 **JSON Format** - Line-delimited JSON for production
|
|
10
|
+
- 🔍 **Trace Integration** - Automatic trace ID inclusion in logs
|
|
11
|
+
- 👶 **Child Loggers** - Create loggers with inherited context
|
|
12
|
+
- ⚡ **Effect.js Integration** - Full Effect.js ecosystem support
|
|
13
|
+
- 🔄 **Dual API** - Both Effect and synchronous interfaces
|
|
14
|
+
- 🏷️ **Log Levels** - trace, debug, info, warn, error, fatal
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add @onebun/logger
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Basic Usage
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createSyncLogger, makeLogger, LoggerService } from '@onebun/logger';
|
|
28
|
+
import { Effect, Layer } from 'effect';
|
|
29
|
+
|
|
30
|
+
// Create a logger based on NODE_ENV
|
|
31
|
+
const loggerLayer = makeLogger();
|
|
32
|
+
|
|
33
|
+
// Use with Effect
|
|
34
|
+
const program = Effect.gen(function* () {
|
|
35
|
+
const logger = yield* LoggerService;
|
|
36
|
+
yield* logger.info('Application started');
|
|
37
|
+
yield* logger.debug('Debug information', { userId: 123 });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await Effect.runPromise(Effect.provide(program, loggerLayer));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Synchronous API
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { createSyncLogger, makeDevLogger, LoggerService } from '@onebun/logger';
|
|
47
|
+
import { Effect, Layer } from 'effect';
|
|
48
|
+
|
|
49
|
+
// Get logger instance
|
|
50
|
+
const loggerLayer = makeDevLogger();
|
|
51
|
+
const logger = Effect.runSync(
|
|
52
|
+
Effect.provide(LoggerService, loggerLayer)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Create sync wrapper
|
|
56
|
+
const syncLogger = createSyncLogger(logger);
|
|
57
|
+
|
|
58
|
+
// Use directly
|
|
59
|
+
syncLogger.info('Hello, World!');
|
|
60
|
+
syncLogger.error('Something went wrong', new Error('Oops'));
|
|
61
|
+
syncLogger.debug('Debug data', { userId: 123, action: 'login' });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Log Levels
|
|
65
|
+
|
|
66
|
+
| Level | Value | Description |
|
|
67
|
+
|-------|-------|-------------|
|
|
68
|
+
| `trace` | 0 | Detailed tracing information |
|
|
69
|
+
| `debug` | 1 | Debug information for development |
|
|
70
|
+
| `info` | 2 | General informational messages |
|
|
71
|
+
| `warn` | 3 | Warning messages |
|
|
72
|
+
| `error` | 4 | Error messages |
|
|
73
|
+
| `fatal` | 5 | Critical errors that may crash the app |
|
|
74
|
+
|
|
75
|
+
## Output Formats
|
|
76
|
+
|
|
77
|
+
### Pretty Format (Development)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
2024-01-15T10:30:00.000Z [INFO ] Application started
|
|
81
|
+
2024-01-15T10:30:00.001Z [DEBUG ] User logged in {"userId":123}
|
|
82
|
+
2024-01-15T10:30:00.002Z [ERROR ] Request failed {"error":"Connection timeout"}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
With trace context:
|
|
86
|
+
```
|
|
87
|
+
2024-01-15T10:30:00.000Z [INFO ] [trace:34da6a3c span:902b7f00] Processing request
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### JSON Format (Production)
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{"timestamp":"2024-01-15T10:30:00.000Z","level":"info","message":"Application started"}
|
|
94
|
+
{"timestamp":"2024-01-15T10:30:00.001Z","level":"debug","message":"User logged in","context":{"userId":123}}
|
|
95
|
+
{"timestamp":"2024-01-15T10:30:00.002Z","level":"error","message":"Request failed","error":{"name":"Error","message":"Connection timeout","stack":"..."}}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Child Loggers
|
|
99
|
+
|
|
100
|
+
Create loggers with additional context:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const logger = createSyncLogger(baseLogger);
|
|
104
|
+
|
|
105
|
+
// Create child logger with context
|
|
106
|
+
const requestLogger = logger.child({
|
|
107
|
+
requestId: 'abc-123',
|
|
108
|
+
userId: 456
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// All logs include the context
|
|
112
|
+
requestLogger.info('Processing request');
|
|
113
|
+
// Output: {"...", "context": {"requestId": "abc-123", "userId": 456}}
|
|
114
|
+
|
|
115
|
+
// Create nested child
|
|
116
|
+
const dbLogger = requestLogger.child({ component: 'database' });
|
|
117
|
+
dbLogger.debug('Query executed');
|
|
118
|
+
// Output: {"...", "context": {"requestId": "abc-123", "userId": 456, "component": "database"}}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { makeDevLogger, makeProdLogger, LogLevel } from '@onebun/logger';
|
|
125
|
+
import { PrettyFormatter, JsonFormatter } from '@onebun/logger';
|
|
126
|
+
import { ConsoleTransport } from '@onebun/logger';
|
|
127
|
+
|
|
128
|
+
// Development logger with custom config
|
|
129
|
+
const devLogger = makeDevLogger({
|
|
130
|
+
minLevel: LogLevel.Debug,
|
|
131
|
+
defaultContext: {
|
|
132
|
+
service: 'my-service',
|
|
133
|
+
version: '1.0.0'
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Production logger
|
|
138
|
+
const prodLogger = makeProdLogger({
|
|
139
|
+
minLevel: LogLevel.Info,
|
|
140
|
+
defaultContext: {
|
|
141
|
+
service: 'my-service',
|
|
142
|
+
environment: 'production'
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Auto-select based on NODE_ENV
|
|
147
|
+
const logger = makeLogger(); // dev if NODE_ENV !== 'production'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Logging with Context
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Log with inline context
|
|
154
|
+
logger.info('User action', { userId: 123, action: 'login' });
|
|
155
|
+
|
|
156
|
+
// Log with error
|
|
157
|
+
logger.error('Request failed', new Error('Network error'));
|
|
158
|
+
|
|
159
|
+
// Log with error and context
|
|
160
|
+
logger.error('Database error', new Error('Connection lost'), {
|
|
161
|
+
database: 'users',
|
|
162
|
+
query: 'SELECT * FROM users'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Multiple arguments
|
|
166
|
+
logger.debug('Processing', { step: 1 }, { items: 10 }, 'extra data');
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Trace Integration
|
|
170
|
+
|
|
171
|
+
The logger automatically includes trace information when used with `@onebun/trace`:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { TraceService } from '@onebun/trace';
|
|
175
|
+
|
|
176
|
+
// Trace context is automatically included in logs
|
|
177
|
+
@Get('/users/:id')
|
|
178
|
+
@Trace('get-user')
|
|
179
|
+
async getUser(@Param('id') id: string) {
|
|
180
|
+
this.logger.info('Fetching user', { userId: id });
|
|
181
|
+
// Log automatically includes traceId and spanId
|
|
182
|
+
return this.userService.findById(id);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Effect.js Integration
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { Effect, pipe } from 'effect';
|
|
190
|
+
import { LoggerService } from '@onebun/logger';
|
|
191
|
+
|
|
192
|
+
const program = pipe(
|
|
193
|
+
LoggerService,
|
|
194
|
+
Effect.flatMap((logger) =>
|
|
195
|
+
pipe(
|
|
196
|
+
logger.info('Starting operation'),
|
|
197
|
+
Effect.flatMap(() => performOperation()),
|
|
198
|
+
Effect.tap(() => logger.info('Operation completed')),
|
|
199
|
+
Effect.catchAll((error) =>
|
|
200
|
+
pipe(
|
|
201
|
+
logger.error('Operation failed', error),
|
|
202
|
+
Effect.flatMap(() => Effect.fail(error))
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Best Practices
|
|
211
|
+
|
|
212
|
+
1. **Use appropriate log levels** - Don't log everything at info level
|
|
213
|
+
2. **Include context** - Add relevant data for debugging
|
|
214
|
+
3. **Use child loggers** - Maintain context across related operations
|
|
215
|
+
4. **Protect sensitive data** - Don't log passwords or tokens
|
|
216
|
+
5. **Use JSON in production** - Easier to parse and analyze
|
|
217
|
+
6. **Enable trace integration** - Correlate logs across services
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
[LGPL-3.0](../../LICENSE)
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/logger",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Structured logging for OneBun framework - JSON and pretty output with trace support",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "RemRyahirev",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"onebun",
|
|
9
|
+
"logger",
|
|
10
|
+
"logging",
|
|
11
|
+
"structured-logging",
|
|
12
|
+
"json-logging",
|
|
13
|
+
"bun",
|
|
14
|
+
"typescript",
|
|
15
|
+
"effect"
|
|
16
|
+
],
|
|
7
17
|
"repository": {
|
|
8
18
|
"type": "git",
|
|
9
19
|
"url": "git+https://github.com/RemRyahirev/onebun.git",
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Examples Tests for @onebun/logger
|
|
3
|
+
*
|
|
4
|
+
* This file tests code examples from:
|
|
5
|
+
* - packages/logger/README.md
|
|
6
|
+
* - docs/api/logger.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
describe,
|
|
11
|
+
it,
|
|
12
|
+
expect,
|
|
13
|
+
} from 'bun:test';
|
|
14
|
+
import { Effect } from 'effect';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createSyncLogger,
|
|
18
|
+
makeLogger,
|
|
19
|
+
LoggerService,
|
|
20
|
+
} from '../src';
|
|
21
|
+
|
|
22
|
+
describe('Logger README Examples', () => {
|
|
23
|
+
describe('Basic Usage (README)', () => {
|
|
24
|
+
/**
|
|
25
|
+
* @source packages/logger/README.md#basic-usage
|
|
26
|
+
*/
|
|
27
|
+
it('should create logger based on NODE_ENV', () => {
|
|
28
|
+
// From README: Create a logger based on NODE_ENV
|
|
29
|
+
const loggerLayer = makeLogger();
|
|
30
|
+
|
|
31
|
+
expect(loggerLayer).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should use logger with Effect', async () => {
|
|
35
|
+
// From README: Use with Effect
|
|
36
|
+
// Note: The README example uses Effect.gen, but guidelines say to use pipe
|
|
37
|
+
const loggerLayer = makeLogger();
|
|
38
|
+
|
|
39
|
+
const program = Effect.flatMap(LoggerService, (logger) =>
|
|
40
|
+
Effect.all([
|
|
41
|
+
logger.info('Application started'),
|
|
42
|
+
logger.debug('Debug information', { userId: 123 }),
|
|
43
|
+
]),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Should run without error
|
|
47
|
+
await Effect.runPromise(Effect.provide(program, loggerLayer));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('Synchronous API (README)', () => {
|
|
52
|
+
it('should create sync logger and use directly', async () => {
|
|
53
|
+
// From README: Synchronous API example
|
|
54
|
+
const loggerLayer = makeLogger();
|
|
55
|
+
|
|
56
|
+
// Get logger instance
|
|
57
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
58
|
+
|
|
59
|
+
// Create sync wrapper
|
|
60
|
+
const syncLogger = createSyncLogger(logger);
|
|
61
|
+
|
|
62
|
+
// From README: Use directly
|
|
63
|
+
expect(() => {
|
|
64
|
+
syncLogger.info('Hello, World!');
|
|
65
|
+
syncLogger.error('Something went wrong', new Error('Oops'));
|
|
66
|
+
syncLogger.debug('Debug data', { userId: 123, action: 'login' });
|
|
67
|
+
}).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Log Levels (README)', () => {
|
|
72
|
+
it('should support all log levels', async () => {
|
|
73
|
+
const loggerLayer = makeLogger();
|
|
74
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
75
|
+
const syncLogger = createSyncLogger(logger);
|
|
76
|
+
|
|
77
|
+
// From README: Log Levels table
|
|
78
|
+
expect(() => {
|
|
79
|
+
syncLogger.trace('Detailed tracing information'); // Level 0
|
|
80
|
+
syncLogger.debug('Debug information for development'); // Level 1
|
|
81
|
+
syncLogger.info('General informational messages'); // Level 2
|
|
82
|
+
syncLogger.warn('Warning messages'); // Level 3
|
|
83
|
+
syncLogger.error('Error messages'); // Level 4
|
|
84
|
+
syncLogger.fatal('Critical errors that may crash the app'); // Level 5
|
|
85
|
+
}).not.toThrow();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('Child Loggers (README)', () => {
|
|
90
|
+
it('should create child logger with context', async () => {
|
|
91
|
+
const loggerLayer = makeLogger();
|
|
92
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
93
|
+
const syncLogger = createSyncLogger(logger);
|
|
94
|
+
|
|
95
|
+
// From README: Create child logger with context
|
|
96
|
+
const requestLogger = syncLogger.child({
|
|
97
|
+
requestId: 'abc-123',
|
|
98
|
+
userId: 456,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// All logs include the context
|
|
102
|
+
expect(() => {
|
|
103
|
+
requestLogger.info('Processing request');
|
|
104
|
+
}).not.toThrow();
|
|
105
|
+
|
|
106
|
+
// From README: Create nested child
|
|
107
|
+
const dbLogger = requestLogger.child({ component: 'database' });
|
|
108
|
+
expect(() => {
|
|
109
|
+
dbLogger.debug('Query executed');
|
|
110
|
+
}).not.toThrow();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Logger API Documentation Examples', () => {
|
|
116
|
+
describe('SyncLogger Interface (docs/api/logger.md)', () => {
|
|
117
|
+
it('should implement all interface methods', async () => {
|
|
118
|
+
// From docs: SyncLogger interface
|
|
119
|
+
const loggerLayer = makeLogger();
|
|
120
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
121
|
+
const syncLogger = createSyncLogger(logger);
|
|
122
|
+
|
|
123
|
+
// Verify all methods exist
|
|
124
|
+
expect(typeof syncLogger.trace).toBe('function');
|
|
125
|
+
expect(typeof syncLogger.debug).toBe('function');
|
|
126
|
+
expect(typeof syncLogger.info).toBe('function');
|
|
127
|
+
expect(typeof syncLogger.warn).toBe('function');
|
|
128
|
+
expect(typeof syncLogger.error).toBe('function');
|
|
129
|
+
expect(typeof syncLogger.fatal).toBe('function');
|
|
130
|
+
expect(typeof syncLogger.child).toBe('function');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('Logging with Context (docs/api/logger.md)', () => {
|
|
135
|
+
it('should log with object context', async () => {
|
|
136
|
+
const loggerLayer = makeLogger();
|
|
137
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
138
|
+
const syncLogger = createSyncLogger(logger);
|
|
139
|
+
|
|
140
|
+
// From docs: Object Context example
|
|
141
|
+
expect(() => {
|
|
142
|
+
syncLogger.info('User action', {
|
|
143
|
+
userId: '123',
|
|
144
|
+
action: 'login',
|
|
145
|
+
ip: '192.168.1.1',
|
|
146
|
+
userAgent: 'Mozilla/5.0',
|
|
147
|
+
});
|
|
148
|
+
}).not.toThrow();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should log errors', async () => {
|
|
152
|
+
const loggerLayer = makeLogger();
|
|
153
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
154
|
+
const syncLogger = createSyncLogger(logger);
|
|
155
|
+
|
|
156
|
+
// From docs: Error Logging example
|
|
157
|
+
const error = new Error('Something went wrong');
|
|
158
|
+
|
|
159
|
+
expect(() => {
|
|
160
|
+
// Error objects are specially handled
|
|
161
|
+
syncLogger.error('Operation failed', error);
|
|
162
|
+
|
|
163
|
+
// With additional context
|
|
164
|
+
syncLogger.error('Operation failed', error, {
|
|
165
|
+
operationId: '123',
|
|
166
|
+
userId: '456',
|
|
167
|
+
});
|
|
168
|
+
}).not.toThrow();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should log with multiple arguments', async () => {
|
|
172
|
+
const loggerLayer = makeLogger();
|
|
173
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
174
|
+
const syncLogger = createSyncLogger(logger);
|
|
175
|
+
|
|
176
|
+
const requestData = { method: 'GET', path: '/api/users' };
|
|
177
|
+
|
|
178
|
+
// From docs: Multiple Arguments example
|
|
179
|
+
expect(() => {
|
|
180
|
+
syncLogger.debug(
|
|
181
|
+
'Processing request',
|
|
182
|
+
requestData, // Object merged into context
|
|
183
|
+
{ step: 1 }, // Another object merged
|
|
184
|
+
'additional info', // String goes to additionalData
|
|
185
|
+
42, // Number goes to additionalData
|
|
186
|
+
);
|
|
187
|
+
}).not.toThrow();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('Child Loggers Pattern (docs/api/logger.md)', () => {
|
|
192
|
+
it('should create child logger with inherited context', async () => {
|
|
193
|
+
const loggerLayer = makeLogger();
|
|
194
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
195
|
+
const baseLogger = createSyncLogger(logger);
|
|
196
|
+
|
|
197
|
+
// From docs: Child logger pattern
|
|
198
|
+
const processOrder = async (orderId: string): Promise<void> => {
|
|
199
|
+
// Create child logger with order context
|
|
200
|
+
const orderLogger = baseLogger.child({
|
|
201
|
+
orderId,
|
|
202
|
+
operation: 'processOrder',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
orderLogger.info('Starting order processing');
|
|
206
|
+
|
|
207
|
+
// Simulate validation
|
|
208
|
+
const validateOrder = (log: typeof orderLogger): void => {
|
|
209
|
+
log.debug('Validating order');
|
|
210
|
+
// Context (orderId, operation) is inherited
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
validateOrder(orderLogger);
|
|
214
|
+
orderLogger.info('Order processing completed');
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Just await without expect - void promise works correctly
|
|
218
|
+
await processOrder('123');
|
|
219
|
+
// If we got here without throwing, the test passed
|
|
220
|
+
expect(true).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('Best Practices (docs/api/logger.md)', () => {
|
|
225
|
+
it('should use appropriate log levels', async () => {
|
|
226
|
+
const loggerLayer = makeLogger();
|
|
227
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
228
|
+
const syncLogger = createSyncLogger(logger);
|
|
229
|
+
|
|
230
|
+
// From docs: Best Practices - Use Appropriate Log Levels
|
|
231
|
+
expect(() => {
|
|
232
|
+
// trace: Very detailed, usually disabled
|
|
233
|
+
syncLogger.trace('Entering function', { args: ['test'] });
|
|
234
|
+
|
|
235
|
+
// debug: Useful for debugging
|
|
236
|
+
syncLogger.debug('Cache lookup', { key: 'user:123', hit: true });
|
|
237
|
+
|
|
238
|
+
// info: Normal operations
|
|
239
|
+
syncLogger.info('User logged in', { userId: '123' });
|
|
240
|
+
|
|
241
|
+
// warn: Potential issues
|
|
242
|
+
syncLogger.warn('Rate limit approaching', { current: 95, limit: 100 });
|
|
243
|
+
|
|
244
|
+
// error: Errors that need attention
|
|
245
|
+
syncLogger.error('Database connection failed', new Error('Timeout'));
|
|
246
|
+
|
|
247
|
+
// fatal: Critical errors
|
|
248
|
+
syncLogger.fatal('Application cannot start', new Error('No config'));
|
|
249
|
+
}).not.toThrow();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should include relevant context', async () => {
|
|
253
|
+
const loggerLayer = makeLogger();
|
|
254
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
255
|
+
const syncLogger = createSyncLogger(logger);
|
|
256
|
+
|
|
257
|
+
// From docs: Best Practices - Include Relevant Context
|
|
258
|
+
// Good: Includes useful context
|
|
259
|
+
expect(() => {
|
|
260
|
+
syncLogger.info('Order placed', {
|
|
261
|
+
orderId: 'order-123',
|
|
262
|
+
customerId: 'customer-456',
|
|
263
|
+
total: 99.99,
|
|
264
|
+
itemCount: 3,
|
|
265
|
+
});
|
|
266
|
+
}).not.toThrow();
|
|
267
|
+
|
|
268
|
+
// Bad: Missing context (still valid but not recommended)
|
|
269
|
+
expect(() => {
|
|
270
|
+
syncLogger.info('Order placed');
|
|
271
|
+
}).not.toThrow();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should use child loggers for operations', async () => {
|
|
275
|
+
const loggerLayer = makeLogger();
|
|
276
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
277
|
+
const baseLogger = createSyncLogger(logger);
|
|
278
|
+
|
|
279
|
+
// From docs: Best Practices - Use Child Loggers for Operations
|
|
280
|
+
const processRequest = async (
|
|
281
|
+
requestId: string,
|
|
282
|
+
userId: string,
|
|
283
|
+
): Promise<void> => {
|
|
284
|
+
const requestLogger = baseLogger.child({ requestId, userId });
|
|
285
|
+
|
|
286
|
+
requestLogger.info('Request started');
|
|
287
|
+
|
|
288
|
+
const step1 = (): void => {
|
|
289
|
+
requestLogger.debug('Step 1 executing');
|
|
290
|
+
};
|
|
291
|
+
const step2 = (): void => {
|
|
292
|
+
requestLogger.debug('Step 2 executing');
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
step1();
|
|
297
|
+
step2();
|
|
298
|
+
requestLogger.info('Request completed');
|
|
299
|
+
} catch (error) {
|
|
300
|
+
requestLogger.error('Request failed', error as Error);
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Just await without expect - void promise works correctly
|
|
306
|
+
await processRequest('req-123', 'user-456');
|
|
307
|
+
// If we got here without throwing, the test passed
|
|
308
|
+
expect(true).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should not log sensitive data', async () => {
|
|
312
|
+
const loggerLayer = makeLogger();
|
|
313
|
+
const logger = Effect.runSync(Effect.provide(LoggerService, loggerLayer));
|
|
314
|
+
const syncLogger = createSyncLogger(logger);
|
|
315
|
+
|
|
316
|
+
// From docs: Best Practices - Don't Log Sensitive Data
|
|
317
|
+
const email = 'user@example.com';
|
|
318
|
+
const password = 'secret123';
|
|
319
|
+
|
|
320
|
+
// Good: Omit sensitive fields
|
|
321
|
+
expect(() => {
|
|
322
|
+
syncLogger.info('User login', { email });
|
|
323
|
+
}).not.toThrow();
|
|
324
|
+
|
|
325
|
+
// Or mask them
|
|
326
|
+
expect(() => {
|
|
327
|
+
syncLogger.info('User login', { email, password: '***' });
|
|
328
|
+
}).not.toThrow();
|
|
329
|
+
|
|
330
|
+
// Bad: Logs password (don't do this in real code)
|
|
331
|
+
// This test just verifies it doesn't crash
|
|
332
|
+
expect(() => {
|
|
333
|
+
syncLogger.info('User login', { email, password });
|
|
334
|
+
}).not.toThrow();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
});
|