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.
- package/CHANGELOG.md +17 -0
- package/docs/configuration/configuration.md +34 -65
- package/docs/core/logging.md +654 -347
- package/index.d.ts +205 -73
- package/index.js +21 -21
- package/index.js.map +23 -22
- package/package.json +25 -28
package/docs/core/logging.md
CHANGED
|
@@ -1,53 +1,72 @@
|
|
|
1
1
|
# 📖 Logging
|
|
2
2
|
|
|
3
|
-
YinzerFlow
|
|
3
|
+
YinzerFlow's logging system has three independent channels:
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
###
|
|
34
|
+
### level — @default <span style="color: #2ecc71">`'warn'`</span>
|
|
12
35
|
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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'`:
|
|
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
|
-
###
|
|
58
|
+
### prefix — @default <span style="color: #2ecc71">`'YINZER'`</span>
|
|
38
59
|
|
|
39
|
-
|
|
60
|
+
Log line prefix shown in brackets. Useful for identifying different server instances.
|
|
40
61
|
|
|
41
62
|
```typescript
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
logLevel: 'error'
|
|
63
|
+
const app = new YinzerFlow({
|
|
64
|
+
logging: {
|
|
65
|
+
prefix: 'API-V2',
|
|
66
|
+
},
|
|
47
67
|
});
|
|
48
68
|
|
|
49
|
-
|
|
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
|
-
-
|
|
59
|
-
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
###
|
|
142
|
+
### accessLogger — @default <span style="color: #2ecc71">`undefined`</span>
|
|
93
143
|
|
|
94
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
|
110
|
-
- `true`:
|
|
111
|
-
|
|
112
|
-
-
|
|
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
|
-
|
|
199
|
+
## 🎛️ Diagnostics Settings
|
|
116
200
|
|
|
117
|
-
|
|
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
|
-
|
|
203
|
+
Diagnostics are organized into three categories:
|
|
125
204
|
|
|
126
|
-
|
|
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
|
-
|
|
209
|
+
### slowRequests — @default <span style="color: #e74c3c">`false`</span>
|
|
129
210
|
|
|
130
|
-
|
|
211
|
+
Log a warning when a request takes longer than this threshold.
|
|
131
212
|
|
|
132
213
|
```typescript
|
|
133
|
-
|
|
134
|
-
|
|
214
|
+
const app = new YinzerFlow({
|
|
215
|
+
logging: {
|
|
216
|
+
diagnostics: {
|
|
217
|
+
slowRequests: '500ms', // or 500 (milliseconds)
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
135
221
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
548
|
+
# ✨ Best Practices
|
|
255
549
|
|
|
256
|
-
**Use
|
|
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
|
-
|
|
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,
|
|
568
|
+
import { YinzerFlow, createLogger } from 'yinzerflow';
|
|
569
|
+
import winston from 'winston';
|
|
262
570
|
|
|
263
|
-
//
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
398
|
-
- **
|
|
399
|
-
- **
|
|
400
|
-
- **
|
|
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
|
-
|
|
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
|
-
### 🛡️
|
|
407
|
-
- **Problem**:
|
|
408
|
-
- **YinzerFlow Solution**:
|
|
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
|
-
### 🛡️
|
|
411
|
-
- **Problem**:
|
|
412
|
-
- **YinzerFlow Solution**:
|
|
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**:
|
|
416
|
-
- **YinzerFlow Solution**:
|
|
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
|
-
###
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
706
|
+
```typescript
|
|
707
|
+
const app = new YinzerFlow({
|
|
708
|
+
logging: { requests: true },
|
|
709
|
+
});
|
|
436
710
|
```
|
|
437
711
|
|
|
438
|
-
###
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
###
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
465
|
-
|
|
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
|
|
761
|
+
// ✅ Correct — logger under logging key
|
|
469
762
|
const app = new YinzerFlow({
|
|
470
|
-
|
|
471
|
-
|
|
763
|
+
logging: {
|
|
764
|
+
logger: winstonLogger,
|
|
765
|
+
},
|
|
472
766
|
});
|
|
473
767
|
```
|
|
474
768
|
|
|
475
|
-
###
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
// ❌
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
+
```
|