@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 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.0",
4
- "description": "Logger package for OneBun framework",
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
+ });