yinzerflow 0.6.14 → 0.7.0

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.
@@ -1,53 +1,72 @@
1
1
  # 📖 Logging
2
2
 
3
- YinzerFlow provides a flexible logging system with built-in Pittsburgh personality, performance tracking, and support for custom logger implementations.
3
+ YinzerFlow's logging system has three independent channels:
4
4
 
5
- For detailed configuration examples and patterns, see [Configuration Guide](../configuration/configuration.md).
5
+ - **App logger** Framework startup, shutdown, errors, warnings, and your `app.log` calls. Gated by `logging.level`.
6
+ - **Access log** — One nginx-style line per request/response. Gated by `logging.requests` (on/off).
7
+ - **Diagnostics** — Framework health monitoring (slow requests, large payloads, memory, event loop, rate limits). Fires independently of `logging.level` — even with `level: 'off'`, diagnostics still fire when thresholds are exceeded.
8
+
9
+ All three channels are configured under the `logging` key:
10
+
11
+ ```typescript
12
+ import { YinzerFlow } from 'yinzerflow';
13
+
14
+ const app = new YinzerFlow({
15
+ logging: {
16
+ level: 'info',
17
+ prefix: 'MY-APP',
18
+ personality: true,
19
+ requests: true,
20
+ diagnostics: {
21
+ slowRequests: '500ms',
22
+ largeResponses: '1mb',
23
+ },
24
+ },
25
+ });
26
+ ```
27
+
28
+ <span style="color: #3498db">🔗 For the brief configuration overview, see [Configuration Guide](../configuration/configuration.md)</span>
6
29
 
7
30
  # ⚙️ Usage
8
31
 
9
- ## 🎛️ Settings
32
+ ## 🎛️ App Logger Settings
10
33
 
11
- ### Log Level — @default <span style="color: #2ecc71">`'info'`</span>
34
+ ### level — @default <span style="color: #2ecc71">`'warn'`</span>
12
35
 
13
- Minimum log level to output messages.
36
+ Log level threshold messages at this severity and above are output. From least to most severe: `debug` → `info` → `warn` → `error`.
14
37
 
15
38
  ```typescript
16
- import { createLogger } from 'yinzerflow';
17
-
18
- const logger = createLogger({
19
- logLevel: 'info' // 'off', 'error', 'warn', 'info'
39
+ const app = new YinzerFlow({
40
+ logging: {
41
+ level: 'info', // 'off' | 'error' | 'warn' | 'info' | 'debug'
42
+ },
20
43
  });
21
-
22
- logger.info('This will be logged');
23
- logger.warn('This will be logged');
24
- logger.error('This will be logged');
25
44
  ```
26
45
 
27
46
  <aside>
28
47
 
29
- Options: `'off' | 'error' | 'warn' | 'info'`
48
+ Options: `'off' | 'error' | 'warn' | 'info' | 'debug'`
30
49
 
31
- - `'off'`: No logging at all
50
+ - `'off'`: No app logging at all (diagnostics still fire independently)
32
51
  - `'error'`: Only errors
33
- - `'warn'`: Warnings and errors
34
- - `'info'`: All messages (most verbose)
52
+ - `'warn'`: Warnings and errors (default — shows security warnings and config issues)
53
+ - `'info'`: Informational messages, warnings, and errors
54
+ - `'debug'`: Everything including verbose connection details
55
+
35
56
  </aside>
36
57
 
37
- ### Prefix — @default <span style="color: #2ecc71">`'YINZER'`</span>
58
+ ### prefix — @default <span style="color: #2ecc71">`'YINZER'`</span>
38
59
 
39
- Prefix for log messages to identify different components.
60
+ Log line prefix shown in brackets. Useful for identifying different server instances.
40
61
 
41
62
  ```typescript
42
- import { createLogger } from 'yinzerflow';
43
-
44
- const dbLogger = createLogger({
45
- prefix: 'DATABASE',
46
- logLevel: 'error'
63
+ const app = new YinzerFlow({
64
+ logging: {
65
+ prefix: 'API-V2',
66
+ },
47
67
  });
48
68
 
49
- dbLogger.error('Connection failed');
50
- // Output: [DATABASE] ❌ [timestamp] [ERROR] Connection failed - aw jeez
69
+ // Output: [API-V2] ✅ [2026-02-20 14:30:00.123] [INFO] Server started...
51
70
  ```
52
71
 
53
72
  <aside>
@@ -55,27 +74,57 @@ dbLogger.error('Connection failed');
55
74
  Options: `string`
56
75
 
57
76
  - Default: `'YINZER'`
58
- - Used to identify different components or modules
59
- - Appears in all log messages as `[PREFIX]`
77
+ - Appears as `[PREFIX]` at the start of every log line
78
+ - Tip: Use different prefixes when running multiple YinzerFlow instances
79
+
60
80
  </aside>
61
81
 
62
- ### Custom Logger — @default <span style="color: #2ecc71">`undefined`</span>
82
+ ### personality — @default <span style="color: #2ecc71">`true`</span>
83
+
84
+ Enable Pittsburgh personality phrases in log output. Adds a random Yinzer phrase to the end of log lines.
85
+
86
+ ```typescript
87
+ // With personality: true (default)
88
+ // [YINZER] ✅ [2026-02-20 14:30:00.123] [INFO] Server started - n'at!
89
+
90
+ // With personality: false
91
+ // [YINZER] ✅ [2026-02-20 14:30:00.123] [INFO] Server started
92
+
93
+ const app = new YinzerFlow({
94
+ logging: {
95
+ personality: false, // Clean output, no Yinzer flair
96
+ },
97
+ });
98
+ ```
99
+
100
+ <aside>
101
+
102
+ Options: `boolean`
103
+
104
+ - 🟢 `true`: Pittsburgh personality phrases appended to log lines (default)
105
+ - 🔴 `false`: Clean, professional log output
106
+
107
+ </aside>
108
+
109
+ ### logger — @default <span style="color: #2ecc71">`undefined`</span>
110
+
111
+ Custom logger for application logs. When provided, framework-internal logs (startup, shutdown, errors, warnings) route to this logger instead of the built-in formatter.
63
112
 
64
- Custom logger implementation for integration with external logging systems.
113
+ Your logger receives **raw args** no ANSI formatting, no timestamps. Your logger handles its own formatting.
65
114
 
66
115
  ```typescript
67
116
  import { YinzerFlow } from 'yinzerflow';
117
+ import winston from 'winston';
68
118
 
69
- // Winston logger implementation
70
- const winstonLogger = {
71
- info: (...args) => winston.info(args.join(' ')),
72
- warn: (...args) => winston.warn(args.join(' ')),
73
- error: (...args) => winston.error(args.join(' '))
74
- };
119
+ const winstonLogger = winston.createLogger({
120
+ level: 'info',
121
+ transports: [new winston.transports.Console()],
122
+ });
75
123
 
76
124
  const app = new YinzerFlow({
77
- port: 3000,
78
- logger: winstonLogger
125
+ logging: {
126
+ logger: winstonLogger, // Framework logs → Winston
127
+ },
79
128
  });
80
129
  ```
81
130
 
@@ -83,406 +132,664 @@ const app = new YinzerFlow({
83
132
 
84
133
  Options: `Logger | undefined`
85
134
 
86
- - Custom logger must implement `info`, `warn`, `error` methods
87
- - Each method accepts variable arguments
88
- - Methods should not return values (void)
89
- - Falls back to built-in logger if undefined
135
+ - `undefined`: Uses built-in formatter with ANSI colors and timestamps (default)
136
+ - 🎨 `Logger`: Any object with `info`, `warn`, `error` methods. `debug` is optional.
137
+
138
+ <span style="color: #3498db">**💡 Tip:**</span> In your own route handlers, import your logger directly (e.g., `import logger from './my-logger'`) rather than using the framework's `log`. This gives you your logger's full API (child loggers, serializers, structured metadata) and avoids double log-level filtering.
139
+
90
140
  </aside>
91
141
 
92
- ### Network Logs — @default <span style="color: #e74c3c">`false`</span>
142
+ ### accessLogger — @default <span style="color: #2ecc71">`undefined`</span>
93
143
 
94
- Enable nginx-style network request logging.
144
+ Custom logger for access logs (the per-request nginx-style lines). Separate from `logger` so you can route access logs to a different destination.
95
145
 
96
146
  ```typescript
97
147
  import { YinzerFlow } from 'yinzerflow';
148
+ import pino from 'pino';
149
+
150
+ const accessLog = pino({ name: 'access' });
151
+ const appLog = pino({ name: 'app' });
98
152
 
99
153
  const app = new YinzerFlow({
100
- port: 3000,
101
- networkLogs: true // Enable network request logging
154
+ logging: {
155
+ requests: true,
156
+ logger: appLog, // Framework errors/warnings → app log
157
+ accessLogger: accessLog, // Per-request lines → access log
158
+ },
102
159
  });
103
160
  ```
104
161
 
105
162
  <aside>
106
163
 
164
+ Options: `Logger | undefined`
165
+
166
+ - ✅ `undefined`: Access logs use built-in formatter (default)
167
+ - 🎨 `Logger`: Custom destination for access log lines
168
+
169
+ </aside>
170
+
171
+ ## 🎛️ Access Log Settings
172
+
173
+ ### requests — @default <span style="color: #e74c3c">`false`</span>
174
+
175
+ Enable nginx-style access logs — one line per request/response with status, method, path, bytes, and timing.
176
+
177
+ ```typescript
178
+ const app = new YinzerFlow({
179
+ logging: {
180
+ requests: true,
181
+ },
182
+ });
183
+
184
+ // Output:
185
+ // [ACCESS] ✅ ... ✅ 127.0.0.1 "GET /api/users HTTP/1.1" 200 1234bytes "-" "curl/8.0" 12ms
186
+ ```
187
+
188
+ <aside>
189
+
107
190
  Options: `boolean`
108
191
 
109
- - `false`: No network logging (default)
110
- - `true`: Enable nginx-style request/response logging
111
- - Separate from application logging
112
- - Can use different logger instance
192
+ - 🔴 `false`: No access logging (default)
193
+ - 🟢 `true`: One line per request/response
194
+
195
+ <span style="color: #f39c12">**⚡ Performance:**</span> Access logs add minimal overhead per request (sanitization + string formatting). In high-traffic production, consider routing to a separate file or log aggregator via `accessLogger`.
196
+
113
197
  </aside>
114
198
 
115
- # Best Practices
199
+ ## 🎛️ Diagnostics Settings
116
200
 
117
- - **Use appropriate log levels** - error for failures, warn for issues, info for status
118
- - **Create component-specific loggers** - Use different prefixes for different modules
119
- - **Integrate with external systems** - Use custom loggers for Winston, Pino, etc.
120
- - **Enable network logs in development** - Use `networkLogs: true` for debugging
121
- - **Use table logging for structured data** - Use `log.table()` for arrays and objects
122
- - **Avoid logging sensitive data** - Be careful with passwords, tokens, etc.
201
+ Diagnostics monitor framework health **independently of the app log level**. Even with `level: 'off'`, diagnostics fire when thresholds are exceeded. All thresholds default to `false` (disabled) — set a value to enable.
123
202
 
124
- # 💻 Examples
203
+ Diagnostics are organized into three categories:
125
204
 
126
- ### Production API
205
+ - **Per-request**: `slowRequests`, `largeResponses`, `largeRequests` — checked after each response
206
+ - **Interval**: `memory`, `eventLoop` — run on timers in the background
207
+ - **Event**: `rateLimits` — fires when the rate limiter blocks a request
127
208
 
128
- **Use Case:** Production API with structured logging and external log management
209
+ ### slowRequests @default <span style="color: #e74c3c">`false`</span>
129
210
 
130
- **Description:** Production-ready logging with Winston integration, structured JSON output, and comprehensive error tracking for monitoring and debugging.
211
+ Log a warning when a request takes longer than this threshold.
131
212
 
132
213
  ```typescript
133
- import { YinzerFlow, createLogger } from 'yinzerflow';
134
- import winston from 'winston';
214
+ const app = new YinzerFlow({
215
+ logging: {
216
+ diagnostics: {
217
+ slowRequests: '500ms', // or 500 (milliseconds)
218
+ },
219
+ },
220
+ });
135
221
 
136
- // Production Winston logger
137
- const winstonLogger = winston.createLogger({
138
- level: 'info',
139
- format: winston.format.combine(
140
- winston.format.timestamp(),
141
- winston.format.errors({ stack: true }),
142
- winston.format.json()
143
- ),
144
- transports: [
145
- new winston.transports.File({ filename: 'error.log', level: 'error' }),
146
- new winston.transports.File({ filename: 'combined.log' }),
147
- new winston.transports.Console({
148
- format: winston.format.simple()
149
- })
150
- ]
222
+ // Output: 🐌 Slow request: GET /api/heavy-query took 1234ms (threshold: 500ms)
223
+ ```
224
+
225
+ <aside>
226
+
227
+ Options: `TimeString | number | false`
228
+
229
+ - `false`: Disabled (default)
230
+ - `TimeString`: e.g. `'100ms'`, `'1s'`, `'30s'`
231
+ - `number`: Milliseconds
232
+
233
+ </aside>
234
+
235
+ ### largeResponses — @default <span style="color: #e74c3c">`false`</span>
236
+
237
+ Log a warning when a response body exceeds this size.
238
+
239
+ ```typescript
240
+ const app = new YinzerFlow({
241
+ logging: {
242
+ diagnostics: {
243
+ largeResponses: '1mb', // or 1048576 (bytes)
244
+ },
245
+ },
246
+ });
247
+
248
+ // Output: 📦 Large response: GET /api/export 2097152 bytes (threshold: 1048576 bytes)
249
+ ```
250
+
251
+ <aside>
252
+
253
+ Options: `ByteString | number | false`
254
+
255
+ - `false`: Disabled (default)
256
+ - `ByteString`: e.g. `'256kb'`, `'1mb'`, `'10mb'`
257
+ - `number`: Bytes
258
+
259
+ </aside>
260
+
261
+ ### largeRequests — @default <span style="color: #e74c3c">`false`</span>
262
+
263
+ Log a warning when a request body exceeds this size.
264
+
265
+ ```typescript
266
+ const app = new YinzerFlow({
267
+ logging: {
268
+ diagnostics: {
269
+ largeRequests: '256kb',
270
+ },
271
+ },
272
+ });
273
+
274
+ // Output: 📦 Large request: POST /api/upload 524288 bytes (threshold: 262144 bytes)
275
+ ```
276
+
277
+ <aside>
278
+
279
+ Options: `ByteString | number | false`
280
+
281
+ - `false`: Disabled (default)
282
+ - `ByteString`: e.g. `'256kb'`, `'1mb'`, `'10mb'`
283
+ - `number`: Bytes
284
+
285
+ </aside>
286
+
287
+ ### memory — @default <span style="color: #e74c3c">`false`</span>
288
+
289
+ Log periodic memory/heap usage at this interval. Useful for tracking memory leaks or high memory usage over time.
290
+
291
+ ```typescript
292
+ const app = new YinzerFlow({
293
+ logging: {
294
+ diagnostics: {
295
+ memory: '30s', // Log memory every 30 seconds
296
+ },
297
+ },
298
+ });
299
+
300
+ // Output: 💾 Heap: 45.2MB / 67.8MB | RSS: 89.1MB | External: 1.2MB
301
+ ```
302
+
303
+ <aside>
304
+
305
+ Options: `TimeString | number | false`
306
+
307
+ - `false`: Disabled (default)
308
+ - `TimeString`: e.g. `'10s'`, `'30s'`, `'1m'`, `'5m'`
309
+ - `number`: Milliseconds
310
+
311
+ <span style="color: #3498db">**💡 Tip:**</span> The timer is `unref()`'d — it won't prevent the process from exiting.
312
+
313
+ </aside>
314
+
315
+ ### eventLoop — @default <span style="color: #e74c3c">`false`</span>
316
+
317
+ Log a warning when event loop lag exceeds this threshold. Detects blocking operations by measuring `setTimeout` drift — if the callback fires significantly later than scheduled, something is blocking.
318
+
319
+ ```typescript
320
+ const app = new YinzerFlow({
321
+ logging: {
322
+ diagnostics: {
323
+ eventLoop: '100ms',
324
+ },
325
+ },
326
+ });
327
+
328
+ // Output: ⏱️ Event loop lag: 250ms (threshold: 100ms)
329
+ ```
330
+
331
+ <aside>
332
+
333
+ Options: `TimeString | number | false`
334
+
335
+ - `false`: Disabled (default)
336
+ - `TimeString`: e.g. `'50ms'`, `'100ms'`, `'500ms'`
337
+ - `number`: Milliseconds
338
+
339
+ </aside>
340
+
341
+ ### rateLimits — @default <span style="color: #e74c3c">`false`</span>
342
+
343
+ Log a warning when the rate limiter blocks a request. Useful for detecting abuse patterns.
344
+
345
+ ```typescript
346
+ const app = new YinzerFlow({
347
+ logging: {
348
+ diagnostics: {
349
+ rateLimits: true,
350
+ },
351
+ },
352
+ });
353
+
354
+ // Output: 🚫 Rate limit hit: 192.168.1.50 on /api/login
355
+ ```
356
+
357
+ <aside>
358
+
359
+ Options: `boolean`
360
+
361
+ - `false`: Disabled (default)
362
+ - `true`: Log every rate limit hit
363
+
364
+ </aside>
365
+
366
+ ## 🎯 Diagnostic Presets
367
+
368
+ Suggested diagnostic configurations for common server types. Copy the one that fits your use case and adjust thresholds as needed.
369
+
370
+ ### Production REST API
371
+
372
+ Tight thresholds for a typical JSON API. Catches slow queries, oversized payloads, and abuse early.
373
+
374
+ ```typescript
375
+ const app = new YinzerFlow({
376
+ logging: {
377
+ level: 'warn',
378
+ requests: true,
379
+ diagnostics: {
380
+ slowRequests: '500ms',
381
+ largeResponses: '1mb',
382
+ largeRequests: '256kb',
383
+ memory: '30s',
384
+ eventLoop: '100ms',
385
+ rateLimits: true,
386
+ },
387
+ },
388
+ });
389
+ ```
390
+
391
+ ### File Server / Media API
392
+
393
+ Relaxed response sizes (large files are expected), but still monitors request timing and memory. Event loop threshold is higher because file I/O can cause brief delays.
394
+
395
+ ```typescript
396
+ const app = new YinzerFlow({
397
+ logging: {
398
+ level: 'warn',
399
+ requests: true,
400
+ diagnostics: {
401
+ slowRequests: '5s',
402
+ largeResponses: '100mb', // Large file downloads expected
403
+ largeRequests: '50mb', // Large file uploads expected
404
+ memory: '15s', // Monitor more frequently (file buffers use memory)
405
+ eventLoop: '500ms', // File I/O causes brief event loop delays
406
+ rateLimits: true,
407
+ },
408
+ },
151
409
  });
410
+ ```
152
411
 
153
- // Custom logger implementation
154
- const customLogger = {
155
- info: (...args) => winstonLogger.info(args.join(' ')),
156
- warn: (...args) => winstonLogger.warn(args.join(' ')),
157
- error: (...args) => winstonLogger.error(args.join(' '))
158
- };
412
+ ### Data Query / Analytics Server
413
+
414
+ Relaxed timing thresholds (complex queries take time), but monitors memory closely since large result sets can spike heap usage.
415
+
416
+ ```typescript
417
+ const app = new YinzerFlow({
418
+ logging: {
419
+ level: 'warn',
420
+ requests: true,
421
+ diagnostics: {
422
+ slowRequests: '10s', // Complex queries are expected to be slow
423
+ largeResponses: '10mb', // Large result sets expected
424
+ largeRequests: '1mb',
425
+ memory: '10s', // Watch memory closely — large result sets spike heap
426
+ eventLoop: '1s', // Query processing blocks the loop briefly
427
+ rateLimits: true,
428
+ },
429
+ },
430
+ });
431
+ ```
432
+
433
+ ### Development / Debugging
434
+
435
+ Aggressive thresholds to catch everything during development.
436
+
437
+ ```typescript
438
+ const app = new YinzerFlow({
439
+ logging: {
440
+ level: 'debug',
441
+ requests: true,
442
+ diagnostics: {
443
+ slowRequests: '100ms', // Catch anything slow
444
+ largeResponses: '100kb', // Catch oversized responses early
445
+ largeRequests: '50kb',
446
+ memory: '5s', // Frequent memory snapshots
447
+ eventLoop: '50ms', // Catch any blocking
448
+ rateLimits: true,
449
+ },
450
+ },
451
+ });
452
+ ```
453
+
454
+ ## 🔧 `createLogger()` — Standalone Loggers
455
+
456
+ `createLogger()` creates independent logger instances for use in your own code. These are separate from the framework's internal logger.
457
+
458
+ ```typescript
459
+ import { createLogger } from 'yinzerflow';
159
460
 
160
- // Component-specific loggers
161
461
  const dbLogger = createLogger({
462
+ level: 'error',
162
463
  prefix: 'DATABASE',
163
- logLevel: 'error',
164
- logger: customLogger
464
+ personality: false,
165
465
  });
166
466
 
167
467
  const authLogger = createLogger({
468
+ level: 'warn',
168
469
  prefix: 'AUTH',
169
- logLevel: 'warn',
170
- logger: customLogger
171
470
  });
172
471
 
173
- const apiLogger = createLogger({
174
- prefix: 'API',
175
- logLevel: 'info',
176
- logger: customLogger
472
+ dbLogger.error('Connection failed');
473
+ // Output: [DATABASE] ❌ [2026-02-20 14:30:00.123] [ERROR] Connection failed
474
+
475
+ authLogger.warn('Token expiring soon');
476
+ // Output: [AUTH] ⚠️ [2026-02-20 14:30:00.456] [WARN] Token expiring soon - just sayin'
477
+ ```
478
+
479
+ ### Branded Logger Auto-Inheritance
480
+
481
+ When you pass a `createLogger()` instance as `logging.logger`, YinzerFlow auto-inherits its `level`, `prefix`, and `personality` settings. Explicit config overrides the inherited values.
482
+
483
+ ```typescript
484
+ import { YinzerFlow, createLogger } from 'yinzerflow';
485
+
486
+ const myLogger = createLogger({
487
+ level: 'debug',
488
+ prefix: 'MY-APP',
489
+ personality: false,
177
490
  });
178
491
 
492
+ // YinzerFlow inherits debug level, MY-APP prefix, personality off
179
493
  const app = new YinzerFlow({
494
+ logging: {
495
+ logger: myLogger,
496
+ },
497
+ });
498
+
499
+ // Explicit config still wins — level is 'info' even though myLogger is 'debug'
500
+ const app2 = new YinzerFlow({
501
+ logging: {
502
+ logger: myLogger,
503
+ level: 'info', // Overrides the inherited 'debug'
504
+ },
505
+ });
506
+ ```
507
+
508
+ ### Custom Logger Output Sink
509
+
510
+ Route log output through an external logger (Winston, Pino, etc.). The external logger receives raw args — no ANSI formatting.
511
+
512
+ ```typescript
513
+ import { createLogger } from 'yinzerflow';
514
+ import winston from 'winston';
515
+
516
+ const winstonInstance = winston.createLogger({
517
+ transports: [new winston.transports.Console()],
518
+ });
519
+
520
+ const logger = createLogger({
521
+ prefix: 'API',
522
+ logger: winstonInstance, // Output routes through Winston
523
+ });
524
+
525
+ logger.info('Server ready'); // Winston receives: 'Server ready'
526
+ ```
527
+
528
+ ### Per-Instance Isolation
529
+
530
+ Each YinzerFlow instance has its own logger. Two instances with different configs don't interfere with each other.
531
+
532
+ ```typescript
533
+ const api = new YinzerFlow({
180
534
  port: 3000,
181
- logger: customLogger,
182
- networkLogs: true,
183
- networkLogger: customLogger
184
- });
185
-
186
- // Database operations
187
- const connectToDatabase = async () => {
188
- try {
189
- await database.connect();
190
- dbLogger.info('Database connection established');
191
- } catch (error) {
192
- dbLogger.error('Database connection failed', error);
193
- throw error;
194
- }
195
- };
196
-
197
- // Authentication operations
198
- const validateToken = async (token: string) => {
199
- try {
200
- const user = await jwt.verify(token);
201
- authLogger.info('Token validated successfully', { userId: user.id });
202
- return user;
203
- } catch (error) {
204
- authLogger.warn('Token validation failed', { token: token.substring(0, 10) + '...' });
205
- throw new Error('Invalid token');
206
- }
207
- };
208
-
209
- // API operations
210
- app.get('/api/users', async (ctx) => {
211
- try {
212
- apiLogger.info('Fetching users', {
213
- requestId: ctx.state.requestId,
214
- ipAddress: ctx.request.ipAddress
215
- });
216
-
217
- const users = await getAllUsers();
218
-
219
- apiLogger.info('Users fetched successfully', {
220
- count: users.length,
221
- requestId: ctx.state.requestId
222
- });
223
-
224
- return { users };
225
- } catch (error) {
226
- apiLogger.error('Failed to fetch users', {
227
- error: error.message,
228
- requestId: ctx.state.requestId
229
- });
230
-
231
- ctx.response.setStatusCode(500);
232
- return { error: 'Internal server error' };
233
- }
234
- });
235
-
236
- // Error handling with logging
237
- app.onError(async (ctx, error) => {
238
- apiLogger.error('Unhandled error', {
239
- error: error.message,
240
- stack: error.stack,
241
- requestId: ctx.state.requestId,
242
- method: ctx.request.method,
243
- path: ctx.request.path,
244
- ipAddress: ctx.request.ipAddress
245
- });
246
-
247
- ctx.response.setStatusCode(500);
248
- return { error: 'Internal server error' };
535
+ logging: { prefix: 'API', level: 'warn' },
249
536
  });
250
537
 
251
- await app.listen();
538
+ const admin = new YinzerFlow({
539
+ port: 3001,
540
+ logging: { prefix: 'ADMIN', level: 'debug' },
541
+ });
542
+
543
+ // api logs: [API] ⚠️ ...
544
+ // admin logs: [ADMIN] 🔍 ...
545
+ // No cross-contamination
252
546
  ```
253
547
 
254
- ### Dev API
548
+ # Best Practices
255
549
 
256
- **Use Case:** Development server with detailed logging and debugging
550
+ - **Use `'warn'` in production** The default. You see security warnings and errors without info noise.
551
+ - **Use `'info'` or `'debug'` during development** — See startup details, connection info, route registration.
552
+ - **Enable `requests` for observability** — Access logs are cheap and invaluable for debugging production issues.
553
+ - **Route access logs separately** — Use `accessLogger` to send request lines to a different file/service than app errors.
554
+ - **Start with diagnostic presets** — Copy a preset above, then tune thresholds based on real traffic data.
555
+ - **Don't log sensitive data** — Be careful with passwords, tokens, API keys in custom log calls.
556
+ - **Use component-specific loggers** — `createLogger({ prefix: 'DATABASE' })` makes log output filterable.
557
+ - **Disable personality in production** — Set `personality: false` for clean, parseable logs in log aggregators.
257
558
 
258
- **Description:** Development configuration with built-in logger, extensive debugging information, and table logging for easier development and testing.
559
+ # 💻 Examples
560
+
561
+ ### Production API
562
+
563
+ **Use Case:** Deployed API with structured logging, diagnostics, and external log aggregation
564
+
565
+ **Description:** Production-ready logging with Winston integration for app logs, separate access log file, and diagnostic monitoring for performance issues.
259
566
 
260
567
  ```typescript
261
- import { YinzerFlow, log, createLogger } from 'yinzerflow';
568
+ import { YinzerFlow, createLogger } from 'yinzerflow';
569
+ import winston from 'winston';
262
570
 
263
- // Development loggers with different prefixes
264
- const dbLogger = createLogger({
265
- prefix: 'DATABASE',
266
- logLevel: 'info'
571
+ // Production Winston setup
572
+ const winstonLogger = winston.createLogger({
573
+ level: 'warn',
574
+ format: winston.format.combine(
575
+ winston.format.timestamp(),
576
+ winston.format.json(),
577
+ ),
578
+ transports: [
579
+ new winston.transports.File({ filename: 'error.log', level: 'error' }),
580
+ new winston.transports.File({ filename: 'combined.log' }),
581
+ ],
267
582
  });
268
583
 
269
- const authLogger = createLogger({
270
- prefix: 'AUTH',
271
- logLevel: 'warn'
584
+ // Separate transport for access logs
585
+ const accessTransport = winston.createLogger({
586
+ format: winston.format.combine(
587
+ winston.format.timestamp(),
588
+ winston.format.printf(({ message }) => message),
589
+ ),
590
+ transports: [
591
+ new winston.transports.File({ filename: 'access.log' }),
592
+ ],
272
593
  });
273
594
 
274
- const apiLogger = createLogger({
275
- prefix: 'API',
276
- logLevel: 'info'
595
+ const app = new YinzerFlow({
596
+ port: 3000,
597
+ logging: {
598
+ level: 'warn',
599
+ prefix: 'API',
600
+ personality: false, // Clean output for log aggregators
601
+ requests: true,
602
+ logger: winstonLogger, // App logs → Winston
603
+ accessLogger: accessTransport, // Access logs → separate file
604
+ diagnostics: {
605
+ slowRequests: '500ms',
606
+ largeResponses: '1mb',
607
+ largeRequests: '256kb',
608
+ memory: '30s',
609
+ eventLoop: '100ms',
610
+ rateLimits: true,
611
+ },
612
+ },
277
613
  });
278
614
 
615
+ // In route handlers, import YOUR logger directly:
616
+ app.get('/api/users', async ({ response }) => {
617
+ winstonLogger.info('Fetching users', { service: 'user-api' });
618
+ return response.success({ users: [] });
619
+ });
620
+
621
+ await app.listen();
622
+ ```
623
+
624
+ ### Dev API
625
+
626
+ **Use Case:** Local development with verbose logging and aggressive diagnostics
627
+
628
+ **Description:** Development configuration with all logging enabled, debug level, and tight diagnostic thresholds to catch issues early.
629
+
630
+ ```typescript
631
+ import { YinzerFlow } from 'yinzerflow';
632
+
279
633
  const app = new YinzerFlow({
280
634
  port: 3000,
281
- logLevel: 'info',
282
- networkLogs: true // Enable network logging for debugging
283
- });
284
-
285
- // Database operations with detailed logging
286
- const connectToDatabase = async () => {
287
- try {
288
- log.info('Connecting to database...');
289
- await database.connect();
290
- dbLogger.info('Database connection established');
291
-
292
- // Log database configuration (non-sensitive)
293
- dbLogger.info('Database configuration', {
294
- host: process.env.DB_HOST,
295
- port: process.env.DB_PORT,
296
- database: process.env.DB_NAME
297
- });
298
- } catch (error) {
299
- dbLogger.error('Database connection failed', error);
300
- throw error;
301
- }
302
- };
303
-
304
- // Authentication with detailed logging
305
- const validateToken = async (token: string) => {
306
- try {
307
- authLogger.info('Validating token', { tokenLength: token.length });
308
- const user = await jwt.verify(token);
309
- authLogger.info('Token validated successfully', { userId: user.id });
310
- return user;
311
- } catch (error) {
312
- authLogger.warn('Token validation failed', { error: error.message });
313
- throw new Error('Invalid token');
314
- }
315
- };
316
-
317
- // API operations with comprehensive logging
318
- app.get('/api/users', async (ctx) => {
319
- try {
320
- apiLogger.info('Fetching users', {
321
- requestId: ctx.state.requestId,
322
- ipAddress: ctx.request.ipAddress,
323
- userAgent: ctx.request.headers['user-agent']
324
- });
325
-
326
- const users = await getAllUsers();
327
-
328
- // Use table logging for structured data
329
- apiLogger.table(users, 'Users fetched successfully');
330
-
331
- apiLogger.info('Users fetched successfully', {
332
- count: users.length,
333
- requestId: ctx.state.requestId
334
- });
335
-
336
- return { users };
337
- } catch (error) {
338
- apiLogger.error('Failed to fetch users', {
339
- error: error.message,
340
- stack: error.stack,
341
- requestId: ctx.state.requestId
342
- });
343
-
344
- ctx.response.setStatusCode(500);
345
- return { error: 'Internal server error' };
346
- }
347
- });
348
-
349
- // Debug route with extensive logging
350
- app.get('/debug', async (ctx) => {
351
- log.info('Debug route accessed');
352
-
353
- // Log request details
354
- apiLogger.info('Debug request details', {
355
- method: ctx.request.method,
356
- path: ctx.request.path,
357
- headers: ctx.request.headers,
358
- query: ctx.request.query,
359
- params: ctx.request.params,
360
- body: ctx.request.body,
361
- ipAddress: ctx.request.ipAddress
362
- });
363
-
364
- // Log state information
365
- apiLogger.table(ctx.state, 'Request state');
366
-
367
- return {
368
- message: 'Debug information logged',
369
- timestamp: new Date().toISOString()
370
- };
371
- });
372
-
373
- // Error handling with detailed logging
374
- app.onError(async (ctx, error) => {
375
- log.error('Unhandled error occurred', error);
376
-
377
- apiLogger.error('Unhandled error details', {
378
- error: error.message,
379
- stack: error.stack,
380
- requestId: ctx.state.requestId,
381
- method: ctx.request.method,
382
- path: ctx.request.path,
383
- ipAddress: ctx.request.ipAddress,
384
- headers: ctx.request.headers,
385
- body: ctx.request.body
386
- });
387
-
388
- ctx.response.setStatusCode(500);
389
- return { error: 'Internal server error' };
635
+ logging: {
636
+ level: 'debug', // See everything
637
+ prefix: 'DEV',
638
+ personality: true, // Keep the Yinzer flair
639
+ requests: true, // See every request/response
640
+ diagnostics: {
641
+ slowRequests: '100ms', // Catch anything slow
642
+ largeResponses: '100kb',
643
+ largeRequests: '50kb',
644
+ memory: '5s',
645
+ eventLoop: '50ms',
646
+ rateLimits: true,
647
+ },
648
+ },
390
649
  });
391
650
 
651
+ app.get('/api/test', () => ({ message: 'Hello from dev' }));
652
+
392
653
  await app.listen();
393
654
  ```
394
655
 
395
656
  ## 🚀 Performance Notes
396
657
 
397
- - **Early returns**: Log level checks prevent unnecessary processing when logging is disabled
398
- - **Native console methods**: Uses built-in console methods for optimal performance
399
- - **Minimal overhead**: Logging adds minimal overhead when disabled
400
- - **Table optimization**: Table logging uses native console.table for efficient display
658
+ - **Early returns**: Log level checks are numeric comparisons — near-zero cost when a level is disabled
659
+ - **Diagnostics are independent**: They use their own logger instance, so `level: 'off'` doesn't affect them
660
+ - **Access log overhead**: One string interpolation + sanitization per request. Minimal, but consider `accessLogger` for high-traffic routing to avoid console bottleneck
661
+ - **Memory/event loop timers**: Both use `unref()` they won't prevent process exit
662
+ - **Per-instance isolation**: Each YinzerFlow instance has its own logger — no shared mutable state
401
663
 
402
664
  ## 🔒 Security Notes
403
665
 
404
- YinzerFlow implements several security measures for safe logging:
666
+ ### 🛡️ Log Field Sanitization
667
+ - **Problem**: Log injection attacks can forge log entries or inject control characters
668
+ - **YinzerFlow Solution**: All access log fields (method, path, headers) are sanitized — control characters, null bytes, and Unicode bidirectional overrides are stripped. Fields are truncated to 256 characters.
405
669
 
406
- ### 🛡️ Safe Data Handling
407
- - **Problem**: Logging sensitive data can expose secrets in log files
408
- - **YinzerFlow Solution**: Uses native `console.log` formatting to prevent accidental serialization of sensitive objects
670
+ ### 🛡️ Diagnostic Independence
671
+ - **Problem**: Turning off logging to reduce noise can hide performance and security issues
672
+ - **YinzerFlow Solution**: Diagnostics fire independently of `logging.level`. Even with `level: 'off'`, slow requests, large payloads, memory spikes, and rate limit hits are still reported.
409
673
 
410
- ### 🛡️ Log Level Protection
411
- - **Problem**: Verbose logging in production can impact performance and expose internal details
412
- - **YinzerFlow Solution**: Configurable log levels with early returns to minimize overhead when logging is disabled
674
+ ### 🛡️ Rate Limit Diagnostics
675
+ - **Problem**: Rate limit hits can indicate brute-force attacks, but rate limiting alone doesn't alert you
676
+ - **YinzerFlow Solution**: Enable `diagnostics.rateLimits` to log every blocked request with IP and path useful for detecting abuse patterns.
413
677
 
414
678
  ### 🛡️ Logger Isolation
415
- - **Problem**: Custom loggers could interfere with framework logging
416
- - **YinzerFlow Solution**: Clean interface boundaries and isolated logger instances prevent conflicts
417
-
418
- ### 🛡️ Network Logging Security
419
- - **Problem**: Network logs can expose sensitive request data
420
- - **YinzerFlow Solution**: Network logging is separate and configurable, allowing selective logging
679
+ - **Problem**: Shared mutable logger state can cause cross-instance contamination
680
+ - **YinzerFlow Solution**: Each YinzerFlow instance creates its own logger. No module-level singletons, no shared state — two instances with different configs never interfere.
421
681
 
422
682
  ## 🔧 Troubleshooting
423
683
 
424
- ### Logs Not Appearing
425
- - **Problem**: Log messages not showing up
426
- - **Fix**: Check log level configuration
684
+ ### No logs appearing at all
685
+
686
+ **Symptom:** No output from YinzerFlow.
687
+
688
+ **Cause:** Default log level is `'warn'` — `info` and `debug` messages are suppressed.
689
+
690
+ <span style="color: #2ecc71">**✅ Fix:**</span> Set `level: 'info'` or `level: 'debug'` to see more output.
427
691
 
428
692
  ```typescript
429
- // Wrong - log level too high
430
- const logger = createLogger({ logLevel: 'error' });
431
- logger.info('This will not appear'); // Blocked by log level
693
+ const app = new YinzerFlow({
694
+ logging: { level: 'info' },
695
+ });
696
+ ```
697
+
698
+ ### Access logs not appearing
699
+
700
+ **Symptom:** No per-request log lines.
701
+
702
+ **Cause:** `requests` defaults to `false`.
703
+
704
+ <span style="color: #2ecc71">**✅ Fix:**</span> Enable access logs.
432
705
 
433
- // ✅ Correct - appropriate log level
434
- const logger = createLogger({ logLevel: 'info' });
435
- logger.info('This will appear'); // Will be logged
706
+ ```typescript
707
+ const app = new YinzerFlow({
708
+ logging: { requests: true },
709
+ });
436
710
  ```
437
711
 
438
- ### Custom Logger Not Working
439
- - **Problem**: Custom logger not being used
440
- - **Fix**: Check logger interface implementation
712
+ ### Diagnostics not firing
713
+
714
+ **Symptom:** No diagnostic warnings even with slow requests.
715
+
716
+ **Cause:** All diagnostic thresholds default to `false` (disabled).
717
+
718
+ <span style="color: #2ecc71">**✅ Fix:**</span> Set thresholds for the diagnostics you want.
441
719
 
442
720
  ```typescript
443
- // Wrong - missing required methods
444
- const customLogger = {
445
- info: (...args) => console.log(...args)
446
- // Missing warn and error methods
447
- };
448
-
449
- // ✅ Correct - implement all required methods
450
- const customLogger = {
451
- info: (...args) => console.log(...args),
452
- warn: (...args) => console.warn(...args),
453
- error: (...args) => console.error(...args)
454
- };
721
+ const app = new YinzerFlow({
722
+ logging: {
723
+ diagnostics: {
724
+ slowRequests: '500ms', // Now fires for requests > 500ms
725
+ },
726
+ },
727
+ });
455
728
  ```
456
729
 
457
- ### Network Logs Not Working
458
- - **Problem**: Network request logs not appearing
459
- - **Fix**: Enable network logging in configuration
730
+ ### Custom logger not receiving logs
731
+
732
+ **Symptom:** Passed a logger to `logging.logger` but it gets no output.
733
+
734
+ **Cause:** Log level filtering happens before delegation. If the framework level is `'warn'`, `info` messages never reach your logger.
735
+
736
+ <span style="color: #2ecc71">**✅ Fix:**</span> Match the framework log level to what you want your logger to receive.
460
737
 
461
738
  ```typescript
462
- // ❌ Wrong - network logging disabled
463
739
  const app = new YinzerFlow({
464
- port: 3000,
465
- networkLogs: false // Disabled
740
+ logging: {
741
+ level: 'info', // Framework passes info+ to your logger
742
+ logger: winstonLogger, // Winston can then do its own filtering
743
+ },
744
+ });
745
+ ```
746
+
747
+ ### ANSI codes in external logger output
748
+
749
+ **Symptom:** Log lines contain `\x1b[36m` or similar escape codes in your log aggregator.
750
+
751
+ **Cause:** This happens when the built-in formatter is active instead of your custom logger.
752
+
753
+ <span style="color: #2ecc71">**✅ Fix:**</span> Verify your logger is passed correctly. External loggers (Winston, Pino) receive raw args without ANSI formatting automatically.
754
+
755
+ ```typescript
756
+ // ❌ Wrong — logger not under logging key
757
+ const app = new YinzerFlow({
758
+ logger: winstonLogger, // This is NOT the right location
466
759
  });
467
760
 
468
- // ✅ Correct - enable network logging
761
+ // ✅ Correct logger under logging key
469
762
  const app = new YinzerFlow({
470
- port: 3000,
471
- networkLogs: true // Enabled
763
+ logging: {
764
+ logger: winstonLogger,
765
+ },
472
766
  });
473
767
  ```
474
768
 
475
- ### Table Logging Not Working
476
- - **Problem**: Table logs not displaying properly
477
- - **Fix**: Use appropriate data types for table logging
769
+ ### Old config keys not working
770
+
771
+ **Symptom:** `networkLogs`, `networkLogger`, `logLevel`, or top-level `logger` don't do anything.
772
+
773
+ **Cause:** These were replaced in the logging revamp. All logging config is now under the `logging` key.
774
+
775
+ <span style="color: #2ecc71">**✅ Fix:**</span> Migrate to the new config shape:
478
776
 
479
777
  ```typescript
480
- // ❌ Wrong - primitive data
481
- logger.table('string data'); // Not suitable for table display
482
-
483
- // ✅ Correct - structured data
484
- logger.table([
485
- { id: 1, name: 'John' },
486
- { id: 2, name: 'Jane' }
487
- ]); // Will display as table
488
- ```
778
+ // ❌ Old (no longer works)
779
+ const app = new YinzerFlow({
780
+ logLevel: 'info',
781
+ logger: myLogger,
782
+ networkLogs: true,
783
+ networkLogger: myAccessLogger,
784
+ });
785
+
786
+ // ✅ New
787
+ const app = new YinzerFlow({
788
+ logging: {
789
+ level: 'info',
790
+ logger: myLogger,
791
+ requests: true,
792
+ accessLogger: myAccessLogger,
793
+ },
794
+ });
795
+ ```