scribelog 1.0.5 → 1.1.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/README.md CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  # Scribelog 🪵📝
2
3
 
3
4
  [![npm version](https://img.shields.io/npm/v/scribelog.svg)](https://www.npmjs.com/package/scribelog)
@@ -5,20 +6,23 @@
5
6
  [![Build Status](https://github.com/tolongames/scribelog/actions/workflows/node.js.yml/badge.svg)](https://github.com/tolongames/scribelog/actions/workflows/node.js.yml)
6
7
  <!-- Add other badges if you have them (e.g., coverage) -->
7
8
 
8
- **Scribelog** is an advanced, highly configurable logging library for Node.js applications, written in TypeScript. It offers flexible formatting, support for multiple destinations (transports), child loggers, and automatic error catching, aiming for a great developer experience.
9
+ **Scribelog** is an advanced, highly configurable logging library for Node.js applications, written in TypeScript. It offers flexible formatting, support for multiple destinations (transports), child loggers, automatic error catching, and printf-style interpolation, aiming for a great developer experience.
9
10
 
10
11
  ---
11
12
 
12
13
  ## ✨ Key Features
13
14
 
14
15
  * **Standard Logging Levels:** Uses familiar levels (`error`, `warn`, `info`, `http`, `verbose`, `debug`, `silly`).
15
- * **Highly Flexible Formatting:** Combine powerful formatters (`simple`, `json`, `timestamp`, `metadata`, `errors`, etc.) using a composable API (`format.combine`). Customize timestamps, include/exclude metadata, and more.
16
+ * **Highly Flexible Formatting:** Combine powerful formatters (`simple`, `json`, `timestamp`, `metadata`, `errors`, `splat`, etc.) using a composable API (`format.combine`). Customize timestamps, include/exclude metadata, and more.
17
+ * **Printf-Style Logging:** Use `printf`-like placeholders (`%s`, `%d`, `%j`) for easy message interpolation.
16
18
  * **Console Color Support:** Automatic, readable colorization for the `simple` format in TTY environments.
17
- * **Multiple Transports:** Log to different destinations. Comes with a built-in `ConsoleTransport`. (File transport planned!).
19
+ * **Multiple Transports:** Log to different destinations. Built-in `ConsoleTransport` and `FileTransport` with rotation options.
18
20
  * **Child Loggers:** Easily create contextual loggers (`logger.child({...})`) that inherit settings but add specific metadata (like `requestId`).
19
21
  * **Automatic Error Handling:** Optionally catch and log `uncaughtException` and `unhandledRejection` events, including stack traces.
20
22
  * **TypeScript First:** Written entirely in TypeScript for type safety and excellent editor autocompletion.
21
23
 
24
+ **Explore all features in the [Full Documentation](./DOCUMENTATION.md)!** ⬅
25
+
22
26
  ---
23
27
 
24
28
  ## 📦 Installation
@@ -45,47 +49,51 @@ import { createLogger, format, transports } from 'scribelog';
45
49
  // - Transport: Console
46
50
  const logger = createLogger();
47
51
 
48
- // Log messages at different levels
52
+ // Standard logging
49
53
  logger.info('Application started successfully.');
50
- logger.warn('Warning: Cache memory usage high.', { usage: '85%' }); // Add metadata
51
-
52
- // --- Correct way to log Errors ---
53
- // Pass a message string as the first argument,
54
- // and the Error object in the metadata (typically under the 'error' key).
55
- // The `format.errors()` formatter (included in defaults) will handle it.
56
- const dbError = new Error('Database connection timeout');
57
- (dbError as any).code = 'DB_TIMEOUT'; // You can add custom properties to errors
58
- logger.error('Database Error Occurred', { error: dbError });
54
+ logger.warn('Warning: Cache memory usage high.', { usage: '85%' }); // With metadata
59
55
 
60
- logger.info('Operation completed', { user: 'admin', durationMs: 120 });
56
+ // Printf-style formatting (using the default format's built-in splat())
57
+ const username = 'Alice';
58
+ const userId = 123;
59
+ logger.info('User %s (ID: %d) logged in.', username, userId);
61
60
 
62
- // Debug logs won't appear with the default 'info' level
63
- logger.debug('Detailed step for debugging.');
61
+ // Correct way to log Errors (pass error object in metadata)
62
+ const dbError = new Error('Database connection timeout');
63
+ (dbError as any).code = 'DB_TIMEOUT'; // Add custom properties
64
+ logger.error('Database Error Occurred', { error: dbError }); // format.errors() will process this
64
65
 
65
- // --- Example with JSON format and debug level ---
66
- const jsonLogger = createLogger({
67
- level: 'debug', // Log 'debug' and higher levels
68
- format: format.defaultJsonFormat, // Use predefined JSON format (includes errors, timestamp, etc.)
66
+ // Log to a file in JSON format
67
+ const fileLogger = createLogger({
68
+ level: 'debug', // Log more details to the file
69
+ transports: [
70
+ new transports.File({
71
+ filename: 'app-%DATE%.log', // Filename pattern (date-fns)
72
+ interval: '1d', // Rotate daily
73
+ path: './logs', // Store logs in a 'logs' subfolder
74
+ compress: 'gzip', // Compress rotated files
75
+ maxFiles: 7, // Keep 7 days of logs
76
+ format: format.defaultJsonFormat // Log as JSON to the file
77
+ })
78
+ ]
69
79
  });
70
80
 
71
- jsonLogger.debug('Debugging operation X', { operationId: 'op-xyz' });
72
- // Example JSON Output:
73
- // {"level":"debug","message":"Debugging operation X","timestamp":"...ISO_STRING...","operationId":"op-xyz"}
81
+ fileLogger.debug('Writing detailed debug log to file.', { data: { complex: true }});
82
+ fileLogger.info('User action logged to file.', { userId: 456 });
74
83
  ```
75
84
 
76
- **Example Output (Default Simple Format with Colors):**
85
+ **Example Console Output (Default Simple Format with Colors):**
77
86
 
78
87
  ```bash
79
- # (Timestamp will be gray, [INFO] green, [WARN] yellow, [ERROR] red)
88
+ # (Timestamps gray, Levels colored, Metadata inspected)
80
89
  2024-05-01T10:00:00.123Z [INFO]: Application started successfully.
81
90
  2024-05-01T10:00:01.456Z [WARN]: Warning: Cache memory usage high. { usage: '85%' }
91
+ 2024-05-01T10:00:01.890Z [INFO]: User Alice (ID: 123) logged in.
82
92
  2024-05-01T10:00:02.789Z [ERROR]: Database connection timeout { exception: true, eventType: undefined, errorName: 'Error', code: 'DB_TIMEOUT' }
83
93
  Error: Database connection timeout
84
94
  at <anonymous>:10:17
85
95
  ... (stack trace) ...
86
- 2024-05-01T10:00:03.111Z [INFO]: Operation completed { user: 'admin', durationMs: 120 }
87
96
  ```
88
- *(Note: `eventType` is undefined here because the error wasn't logged via `handleExceptions`/`handleRejections`)*
89
97
 
90
98
  ---
91
99
 
@@ -105,180 +113,182 @@ Create and configure your logger using `createLogger(options?: LoggerOptions)`.
105
113
  | `handleRejections` | `boolean` | `false` | Catch and log `unhandledRejection` events. |
106
114
  | `exitOnError` | `boolean` | `true` | If handling exceptions/rejections, exit process (`process.exit(1)`) after logging. |
107
115
 
108
- **Example: Creating a JSON logger for production**
116
+ **Example: Advanced Configuration**
109
117
 
110
118
  ```ts
111
- import { createLogger, format, transports } from 'scribelog';
119
+ import { createLogger, format, transports, LogLevel } from 'scribelog';
112
120
 
113
- const prodLogger = createLogger({
114
- level: process.env.LOG_LEVEL || 'info', // Read level from environment or default to info
115
- format: format.defaultJsonFormat, // Use predefined JSON format
121
+ // Helper function to safely get log level from environment
122
+ function getLogLevel(): LogLevel { /* ... (implementation from previous example) ... */ }
123
+
124
+ const advancedLogger = createLogger({
125
+ level: getLogLevel(),
126
+ format: format.combine( // Custom format pipeline
127
+ format.errors({ stack: true }),
128
+ format.splat(), // Apply splat formatting early
129
+ format.timestamp({ format: 'isoDateTime' }), // Use date-fns named format
130
+ format.level(),
131
+ format.message(),
132
+ format.metadata({ alias: 'context' }), // Nest metadata
133
+ format.json() // Output as JSON
134
+ ),
116
135
  transports: [
136
+ // Log info and above to console with simple format
117
137
  new transports.Console({
118
- // Console specific options if needed
138
+ level: 'info',
139
+ format: format.defaultSimpleFormat // Override main format
119
140
  }),
120
- // Future: new transports.File({ filename: '/var/log/app.log', level: 'warn' })
141
+ // Log everything to a rotating file
142
+ new transports.File({
143
+ filename: '/var/log/my-app/app.log',
144
+ level: 'debug', // Log everything to file
145
+ // format: // Inherits the JSON format from the logger
146
+ size: '10M', // Rotate after 10 MB
147
+ maxFiles: 5, // Keep 5 rotated files
148
+ compress: 'gzip'
149
+ })
121
150
  ],
122
151
  defaultMeta: {
123
- service: 'my-prod-service',
152
+ service: 'advanced-service',
124
153
  pid: process.pid,
125
- // You can add more static metadata here
126
154
  },
127
- handleExceptions: true, // Recommended for production
128
- handleRejections: true, // Recommended for production
129
- // exitOnError: true // Default, recommended for production
155
+ handleExceptions: true,
156
+ handleRejections: true,
157
+ exitOnError: false // Don't exit automatically
130
158
  });
131
159
 
132
- prodLogger.info('Production logger initialized.');
133
- try {
134
- // Simulate an operation that might fail
135
- throw new Error('Critical configuration error!');
136
- } catch (error) {
137
- // Log the caught error correctly
138
- prodLogger.error('Failed to apply configuration', { error: error as Error });
139
- }
140
-
141
- // Example of an unhandled rejection that would be caught if not caught here
142
- // Promise.reject('Something failed asynchronously');
160
+ advancedLogger.info('Advanced logger ready.');
161
+ advancedLogger.debug('This goes only to the file transport as JSON.');
162
+ advancedLogger.error('An error occurred!', { error: new Error("Config read failed"), critical: true });
143
163
  ```
144
164
 
145
165
  ---
146
166
 
147
167
  ## 📊 Logging Levels
148
168
 
149
- Scribelog uses standard `npm` logging levels (ordered from most to least severe):
169
+ (Content is the same as before - list of levels and explanation)
150
170
 
151
171
  ```text
152
172
  error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
153
173
  ```
154
-
155
- Setting the `level` option filters messages *at or above* the specified severity. `level: 'info'` logs `info`, `warn`, and `error`. `level: 'debug'` logs everything.
156
-
157
174
  ---
158
175
 
159
176
  ## 🎨 Formatting
160
177
 
161
- Formatters transform the log `info` object before it reaches transports. Use `format.combine(...)` to chain them.
178
+ Formatters transform the log `info` object. Chain them using `format.combine(...)`. The order matters!
162
179
 
163
180
  **How it Works:**
164
- `createLogger` -> `log()`/`logEntry()` -> Creates `LogInfo` object -> Passes to `format` function -> `format` function applies its chain (`combine`) -> Result (string or object) passed to `transport.log()`.
181
+ `log(msg, ...args)` -> Creates `LogInfo { message, splat?, ... }` -> Passes to `format` -> `combine` applies chain -> Result passed to `transport.log()`.
165
182
 
166
183
  ### Available Formatters
167
184
 
168
- * **`format.timestamp(options?)`**: Adds/formats a timestamp.
169
- * `alias?: string`: Key for the formatted timestamp (default: `'timestamp'`).
170
- * `format?: string | ((date: Date) => string)`: `date-fns` format string or custom function (default: ISO 8601).
171
- ```ts
172
- format.timestamp({ format: 'yyyy-MM-dd HH:mm:ss' }) // -> Adds { timestamp: '2024-05-01 10:30:00' }
173
- format.timestamp({ alias: '@timestamp' }) // -> Adds { '@timestamp': '...ISO...' }
174
- ```
185
+ * **`format.timestamp(options?)`**: Adds/formats a timestamp (default: ISO).
186
+ * `alias?: string` (default: `'timestamp'`)
187
+ * `format?: string | ((date: Date) => string)` (`date-fns` format or function).
175
188
  * **`format.level(options?)`**: Adds the log level string.
176
- * `alias?: string`: Key for the level (default: `'level'`).
177
- * **`format.message(options?)`**: Adds the log message string.
178
- * `alias?: string`: Key for the message (default: `'message'`).
179
- * **`format.errors(options?)`**: Extracts info from an `Error` object (expected at `info.error`). Adds `errorName`, `stack?`, `originalReason?` and potentially other error properties to the `info` object. Sets `info.message` to `error.message` if `info.message` was empty. Removes the original `info.error` field. **Place this early in your `combine` chain.**
180
- * `stack?: boolean`: Include stack trace (default: `true`).
181
- * **`format.metadata(options?)`**: Gathers all remaining properties into the main object or under an alias. Excludes standard fields added by other formatters (`level`, `message`, `timestamp`, `errorName`, `stack`, `exception`, `eventType` etc.).
182
- * `alias?: string`: If provided, nest metadata under this key and remove original keys.
183
- * `exclude?: string[]`: Array of additional keys to exclude from metadata collection.
184
- * **`format.json(options?)`**: **Terminal Formatter.** Serializes the final `info` object to a JSON string.
185
- * `space?: string | number`: Pretty-printing spaces for `JSON.stringify`.
186
- * **`format.simple(options?)`**: **Terminal Formatter.** Creates a human-readable, colored (if TTY) string. Includes `timestamp`, `level`, `message`, `{ metadata }`, and `stack` (on a new line if present).
187
- * `colors?: boolean`: Force colors on or off (default: auto-detect based on TTY).
189
+ * `alias?: string` (default: `'level'`).
190
+ * **`format.message(options?)`**: Adds the log message string (after potential `splat` formatting).
191
+ * `alias?: string` (default: `'message'`).
192
+ * **`format.splat()`**: Interpolates the `message` string using `util.format` and arguments found in `info.splat`. Place **after** `errors()` but **before** `message()` and `metadata()`.
193
+ * **`format.errors(options?)`**: Extracts info from an `Error` object (expected at `info.error`). Adds `errorName`, `stack?`, etc. Place **early** in the chain.
194
+ * `stack?: boolean` (default: `true`).
195
+ * **`format.metadata(options?)`**: Gathers remaining properties. Excludes standard fields (`level`, `message`, `timestamp`, etc.) and error fields.
196
+ * `alias?: string`: Nest metadata under this key.
197
+ * `exclude?: string[]`: Additional keys to exclude.
198
+ * **`format.json(options?)`**: **Terminal.** Serializes `info` to JSON.
199
+ * `space?: string | number`: Pretty-print spaces.
200
+ * **`format.simple(options?)`**: **Terminal.** Creates a human-readable string (colored if TTY). Includes `timestamp`, `level`, `message`, `{ metadata }`, and `stack` (new line).
201
+ * `colors?: boolean`: Force colors (default: auto-detect).
188
202
 
189
203
  ### Combining Formatters (`format.combine`)
190
204
 
191
- The order matters! Formatters run sequentially. Terminal formatters (`json`, `simple`) should be last.
192
-
193
205
  ```ts
194
206
  import { createLogger, format } from 'scribelog';
195
207
 
196
- // Example: Log only level, message, and custom timestamp in simple format
197
- const minimalFormat = format.combine(
198
- // Note: errors() is not included here
199
- format.timestamp({ format: 'HH:mm:ss.SSS' }),
208
+ // Example: Simple format with printf-style interpolation first
209
+ const printfSimpleFormat = format.combine(
210
+ format.splat(), // Apply interpolation first
211
+ format.errors({ stack: false }),
212
+ format.timestamp({ format: 'HH:mm:ss' }),
200
213
  format.level(),
201
- format.message(),
202
- // No metadata() - ignores other fields like { extra: '...' }
203
- format.simple() // simple() will only use timestamp, level, message
214
+ format.message(), // Will use the result from splat()
215
+ format.metadata(),
216
+ format.simple()
204
217
  );
205
- const minimalLogger = createLogger({ format: minimalFormat });
206
- minimalLogger.info('Minimal log', { extra: 'this is ignored'});
207
- // Output: 10:45:00.123 [INFO]: Minimal log
208
-
209
- // Example: JSON output with specific fields and nested metadata
210
- const customJsonFormat = format.combine(
211
- format.errors({ stack: false }), // Include basic error info, no stack
212
- format.timestamp({ alias: '@ts' }), // Rename timestamp field
213
- format.level({ alias: 'severity' }), // Rename level field
214
- format.message(), // Keep message field
215
- format.metadata({ alias: 'data' }), // Nest other data under 'data'
216
- format.json() // Output as JSON
217
- );
218
- const customJsonLogger = createLogger({ format: customJsonFormat });
219
- customJsonLogger.warn('Warning with nested meta', { user: 'test', id: 1 });
220
- // Output: {"@ts":"...","severity":"warn","message":"Warning with nested meta","data":{"user":"test","id":1}}
221
-
222
- const errorExample = new Error("Failed task");
223
- (errorExample as any).details = { code: 500 };
224
- customJsonLogger.error("Task failed", { error: errorExample });
225
- // Output: {"@ts":"...", "severity":"error", "message":"Failed task", "errorName":"Error", "originalReason":undefined, "data":{"details":{"code":500}}}
218
+ const printfLogger = createLogger({ format: printfSimpleFormat });
219
+ printfLogger.info('User %s logged in (ID: %d)', 'Bob', 42, { ip: '127.0.0.1' });
220
+ // Output: 11:22:33 [INFO]: User Bob logged in (ID: 42) { ip: '127.0.0.1' }
226
221
  ```
227
222
 
228
223
  ### Predefined Formats
229
224
 
230
- * `format.defaultSimpleFormat`: Equivalent to `combine(errors({ stack: true }), timestamp(), level(), message(), metadata(), simple())`. **This is the default format for `createLogger`.**
231
- * `format.defaultJsonFormat`: Equivalent to `combine(errors({ stack: true }), timestamp(), level(), message(), metadata(), json())`.
225
+ * `format.defaultSimpleFormat`: `combine(errors(stack), splat(), timestamp(), level(), message(), metadata(), simple())`. **Default for `createLogger`.**
226
+ * `format.defaultJsonFormat`: `combine(errors(stack), splat(), timestamp(), level(), message(), metadata(), json())`.
232
227
 
233
228
  ---
234
229
 
235
230
  ## 📤 Transports
236
231
 
237
- Define log destinations. You can use multiple transports.
232
+ Define log destinations.
238
233
 
239
234
  ### `transports.Console(options?: ConsoleTransportOptions)`
240
235
 
241
- Logs to `process.stdout` or `process.stderr`.
236
+ Logs to `process.stdout` / `process.stderr`.
237
+
238
+ * `level?: string`: Transport-specific minimum level.
239
+ * `format?: LogFormat`: Transport-specific format.
240
+ * `useStdErrLevels?: string[]`: Levels to log to `stderr` (default: `['error']`).
241
+
242
+ ### `transports.File(options: FileTransportOptions)`
242
243
 
243
- * `level?: string`: Minimum level for this specific transport. Filters logs *after* the main logger level filter.
244
- * `format?: LogFormat`: Specific format for this transport. Overrides the logger's format.
245
- * `useStdErrLevels?: string[]`: Array of levels to direct to `stderr` (default: `['error']`).
244
+ Logs to a rotating file using [`rotating-file-stream`](https://github.com/iccicci/rotating-file-stream).
246
245
 
247
- **Example: Separate Info and Error Streams**
246
+ * `filename: string`: Path/name of the log file (required). Can include date patterns like `%DATE%`.
247
+ * `level?: string`: Transport-specific minimum level.
248
+ * `format?: LogFormat`: Transport-specific format (defaults to `format.defaultJsonFormat`).
249
+ * `size?: string`: Max file size before rotation (e.g., `'10M'`).
250
+ * `interval?: string`: Rotation interval (e.g., `'1d'`, `'2h'`).
251
+ * `path?: string`: Directory for rotated/archived files.
252
+ * `compress?: string | boolean`: Compress rotated files (`'gzip'` or `true`).
253
+ * `maxFiles?: number`: Max number of rotated files to keep.
254
+ * `maxSize?: string`: Max total size of all log files.
255
+ * `createPath?: boolean`: Create log directory if it doesn't exist (default: `true`).
256
+ * `fsWriteStreamOptions?: object`: Options passed to `fs.createWriteStream`.
257
+ * *(See `rotating-file-stream` docs for more options like `utc`, generators)*
258
+
259
+ **Example: Logging to Console and File**
248
260
 
249
261
  ```ts
250
262
  import { createLogger, format, transports } from 'scribelog';
251
263
 
252
- const logger = createLogger({
253
- level: 'info', // Logger allows info, warn, error
264
+ const fileAndConsoleLogger = createLogger({
265
+ level: 'debug',
254
266
  transports: [
255
- // Log INFO and WARN to stdout using simple format
267
+ // Console for immediate feedback (info and above)
256
268
  new transports.Console({
257
- level: 'warn', // Only logs warn and error passed from logger
258
- format: format.simple({ colors: true }),
259
- useStdErrLevels: [], // Nothing from here goes to stderr
269
+ level: 'info',
270
+ format: format.simple({ colors: true })
260
271
  }),
261
- // Log only ERRORs to stderr using JSON format
262
- new transports.Console({
263
- level: 'error', // Only logs error passed from logger
264
- format: format.json(), // Use JSON for errors
265
- useStdErrLevels: ['error'] // Ensure errors go to stderr
272
+ // File for detailed logs (debug and above, JSON)
273
+ new transports.File({
274
+ filename: 'app-debug.log',
275
+ level: 'debug',
276
+ format: format.defaultJsonFormat,
277
+ size: '5M', // Rotate every 5MB
278
+ maxFiles: 3
266
279
  })
267
280
  ]
268
281
  });
269
282
 
270
- logger.info('User logged in'); // Filtered out by the first transport's level ('warn')
271
- logger.warn('Disk space low'); // Goes to first console (stdout, simple)
272
- logger.error('DB Error', { error: new Error('Connection failed')}); // Goes to BOTH (stdout simple, stderr JSON)
273
- logger.debug('Should not appear'); // Filtered out by logger's level ('info')
283
+ fileAndConsoleLogger.debug('Detailed info only in file');
284
+ fileAndConsoleLogger.info('General info in console and file');
285
+ fileAndConsoleLogger.error('Error in console and file', { error: new Error('Failure') });
274
286
  ```
275
287
 
276
288
  ---
277
289
 
278
290
  ## 🌱 Child Loggers
279
291
 
280
- Create contextual loggers using `logger.child(defaultMeta)`. They inherit settings but automatically add the specified metadata.
281
-
282
292
  ```ts
283
293
  import { createLogger } from 'scribelog';
284
294
 
@@ -335,9 +345,11 @@ logger.info('Application running with error handlers.');
335
345
 
336
346
  The logger adds `{ exception: true, eventType: '...', ...errorDetails }` to the log metadata for these events, processed by the `format.errors()` formatter. Remember to have `format.errors()` in your format chain to see detailed error info.
337
347
 
348
+ ---
349
+
338
350
  ## 💻 Showcase
339
351
 
340
- You can run this script to see how scribelog works.
352
+ You can run this script to see a demonstration of various Scribelog features in action.
341
353
 
342
354
  <details>
343
355
  <summary>Click to show/hide showcase.ts code</summary>
@@ -372,57 +384,54 @@ async function runShowcase() {
372
384
 
373
385
  // === 1. Basic Configuration & Levels ===
374
386
  console.log('--- 1. Basic Configuration & Levels ---');
375
- // Create a logger with a specific level. Default format is 'simple'.
376
- const logger1 = createLogger({ level: 'debug' }); // Set level to 'debug' to see more logs
387
+ const logger1 = createLogger({ level: 'debug' });
377
388
  logger1.info('Logger 1 (level: debug, format: simple)');
378
389
  logger1.warn('Warning from Logger 1');
379
390
  logger1.debug('Debug message from Logger 1 (should appear)');
380
- logger1.error('Error from Logger 1'); // Default ConsoleTransport sends this to stderr
391
+ logger1.error('Error from Logger 1');
381
392
 
382
393
  // === 2. Different Formats ===
383
394
  console.log('\n--- 2. Different Formats ---');
384
395
 
385
396
  // 2a. JSON Format
386
397
  console.log('\n--- 2a. JSON Format ---');
387
- const logger2a = createLogger({
388
- format: format.defaultJsonFormat, // Use the predefined JSON format (includes errors, timestamp etc.)
389
- level: 'info' // Set level for this specific logger
390
- });
398
+ const logger2a = createLogger({ format: format.defaultJsonFormat, level: 'info' });
391
399
  logger2a.info('Log in JSON format.', { data: true, value: 123 });
392
400
  const jsonError = new Error("JSON Formatted Error");
393
- (jsonError as any).code = "E_JSON"; // Add custom error property
394
- // Log an error object within metadata
401
+ (jsonError as any).code = "E_JSON";
395
402
  logger2a.error('An error occurred (JSON)', { error: jsonError, user: 'admin' });
396
403
 
397
404
  // 2b. Custom Simple Format (Colored)
398
405
  console.log('\n--- 2b. Custom Simple Format (Colored) ---');
399
406
  const customSimpleFormat = format.combine(
400
- format.timestamp({ format: 'HH:mm:ss' }), // Custom time format only
401
- format.level(), // Add level string
402
- format.message(), // Add message string
403
- format.metadata({ exclude: ['pid'] }), // Add other metadata, but exclude 'pid'
404
- format.errors({ stack: false }), // Add error info (name, message) but no stack
405
- format.simple({ colors: true }) // Force colors ON for the output string
407
+ format.timestamp({ format: 'HH:mm:ss' }),
408
+ format.level(),
409
+ format.splat(), // Apply splat formatting
410
+ format.message(),
411
+ format.metadata({ exclude: ['pid'] }),
412
+ format.errors({ stack: false }),
413
+ format.simple({ colors: true })
406
414
  );
407
415
  const logger2b = createLogger({ format: customSimpleFormat, level: 'info' });
408
- const originalChalkLevel2b = chalk.level; // Store current chalk level
409
- chalk.level = 1; // Force basic colors for this demo section
410
- logger2b.info('Custom simple format', { pid: 12345, user: 'demo' });
411
- logger2b.error('Another error (custom simple)', { error: new Error("Simple format error") });
412
- chalk.level = originalChalkLevel2b; // Restore original chalk level
416
+ const originalChalkLevel2b = chalk.level;
417
+ chalk.level = 1; // Force colors
418
+ logger2b.info('Custom simple format for %s', 'user', { pid: 12345, user: 'demo' }); // Use splat
419
+ logger2b.error('Another error (custom simple)', { error: new Error("Simple format error")});
420
+ chalk.level = originalChalkLevel2b; // Restore
413
421
 
414
422
  // 2c. Custom JSON Format with Aliases and Nesting
415
423
  console.log('\n--- 2c. Custom JSON Format (Aliases & Nesting) ---');
416
424
  const customJson = format.combine(
417
- format.timestamp({ alias: '@timestamp' }), // Rename timestamp field
418
- format.level({ alias: 'severity' }), // Rename level field
419
- format.message(), // Keep message field
420
- format.errors({ stack: true }), // Include full error details + stack
421
- format.metadata({ alias: 'details' }), // Nest all other metadata under 'details'
422
- format.json() // Output as JSON
425
+ format.errors({ stack: true }), // Handle errors first
426
+ format.splat(), // Then splat
427
+ format.timestamp({ alias: '@timestamp' }),
428
+ format.level({ alias: 'severity' }),
429
+ format.message(),
430
+ format.metadata({ alias: 'details' }),
431
+ format.json()
423
432
  );
424
433
  const logger2c = createLogger({ format: customJson, level: 'info' });
425
- logger2c.warn('Custom JSON with nested meta', { transactionId: 'xyz', status: 'WARN' });
434
+ logger2c.warn('Warn %s nested meta', 'with', { transactionId: 'xyz', status: 'WARN' }); // Use splat
426
435
  const nestedError = new Error("Nested Error");
427
436
  nestedError.stack = "Fake stack\n at place";
428
437
  logger2c.error("Error with nested meta", { error: nestedError, code: 503 });
@@ -430,139 +439,116 @@ async function runShowcase() {
430
439
 
431
440
  // === 3. Multiple Transports ===
432
441
  console.log('\n--- 3. Multiple Transports ---');
433
- const originalChalkLevel3 = chalk.level; // Store current chalk level
434
- chalk.level = 0; // Disable colors globally for easier comparison of output
442
+ const originalChalkLevel3 = chalk.level;
443
+ chalk.level = 0; // Disable colors for comparison
435
444
  const logger3 = createLogger({
436
- level: 'debug', // Main logger allows all levels through
445
+ level: 'debug',
437
446
  transports: [
438
- // Transport 1: Logs 'info' and above, uses simple format, errors to stderr
439
447
  new transports.Console({
440
- level: 'info', // Transport-specific level filter
441
- format: format.defaultSimpleFormat, // Use simple format (colors off due to global chalk level)
442
- useStdErrLevels: ['error'] // Only 'error' level goes to stderr
448
+ level: 'info',
449
+ format: format.defaultSimpleFormat, // Contains splat()
450
+ useStdErrLevels: ['error']
443
451
  }),
444
- // Transport 2: Logs 'debug' and above, uses JSON format, warn/error to stderr
445
452
  new transports.Console({
446
- level: 'debug', // Transport-specific level filter
447
- format: format.defaultJsonFormat, // Use JSON format
448
- useStdErrLevels: ['warn', 'error'] // 'warn' and 'error' go to stderr
453
+ level: 'debug',
454
+ format: format.defaultJsonFormat, // Contains splat()
455
+ useStdErrLevels: ['warn', 'error']
449
456
  })
450
457
  ]
451
458
  });
452
- logger3.debug('Debug log (JSON only)'); // Only Transport 2 logs this
453
- logger3.info('Info log (Simple on stdout & JSON on stdout)'); // Both transports log (both to stdout)
454
- logger3.warn('Warn log (Simple on stdout & JSON on stderr)'); // Both transports log (JSON to stderr)
455
- logger3.error('Error log (Simple on stderr & JSON on stderr)'); // Both transports log (both to stderr)
456
- chalk.level = originalChalkLevel3; // Restore original chalk level
459
+ logger3.debug('Debug log: %s', 'JSON only');
460
+ logger3.info('Info log: %s', 'Simple & JSON');
461
+ logger3.warn('Warn log: %s', 'Simple(stdout) & JSON(stderr)');
462
+ logger3.error('Error log: %s', 'Simple(stderr) & JSON(stderr)');
463
+ chalk.level = originalChalkLevel3; // Restore
457
464
 
458
- // === 4. Child Loggers ===
465
+ // === 4. Loggery Potomne (child) ===
459
466
  console.log('\n--- 4. Child Loggers ---');
460
- // Parent logger setup
461
467
  const parentLogger = createLogger({
462
- level: 'debug', // Set parent level
463
- format: format.simple({ colors: false }), // Use simple format without colors for clarity
464
- defaultMeta: { service: 'MainService' } // Parent's default metadata
468
+ level: 'debug',
469
+ format: format.simple({ colors: false }), // Use simple, no colors
470
+ defaultMeta: { service: 'MainService' }
465
471
  });
466
- parentLogger.info('Parent log.'); // Contains { service: 'MainService' }
467
-
468
- // Create first child, inheriting settings but adding 'module'
472
+ parentLogger.info('Parent log.');
469
473
  const childLogger1 = parentLogger.child({ module: 'ModuleA' });
470
- childLogger1.info('Child 1 log.'); // Contains { service: 'MainService', module: 'ModuleA' }
471
-
472
- // Create a child of the first child, inheriting and overriding 'module'
474
+ childLogger1.info('Child 1 log for action: %s', 'read'); // Use splat
473
475
  const childLogger2 = childLogger1.child({ function: 'doWork', module: 'OverrideModule' });
474
- childLogger2.debug('Child 2 log (debug).', { value: 42 }); // Contains { service: 'MainService', module: 'OverrideModule', function: 'doWork', value: 42 }
476
+ childLogger2.debug('Child 2 log (debug).', { value: 42 });
475
477
 
476
- // === 5. logEntry Method ===
478
+ // === 5. logEntry ===
477
479
  console.log('\n--- 5. logEntry Method ---');
478
- // Create a logger specifically for HTTP level logs
479
480
  const entryLogger = createLogger({ level: 'http' });
480
- // Use logEntry to log a pre-structured object.
481
- // Scribelog will still process it through the format pipeline.
482
481
  entryLogger.logEntry({
483
- level: 'http', // Must be >= logger's level ('http')
484
- message: 'Manual log entry with custom data',
485
- // Add any custom fields relevant to this log
486
- method: 'POST',
487
- url: '/api/users',
488
- statusCode: 201,
482
+ level: 'http',
483
+ message: 'Manual %s entry with custom data %j', // Add splat placeholders
484
+ splat: ['log', { status: 201 }], // Provide splat data
485
+ method: 'POST', url: '/api/users', statusCode: 201,
489
486
  });
490
- // Another example
491
- entryLogger.logEntry({ level: 'info', message: 'This info entry will also show' }); // info > http
487
+ entryLogger.logEntry({ level: 'info', message: 'This info entry will also show' });
492
488
 
493
- // === 6. Exception/Rejection Handling (Simulation) ===
489
+ // === 6. Obsługa wyjątków i odrzuceń (Symulacja) ===
490
+ // (Keep this section as in the previous example, it doesn't need _internalExit mock)
494
491
  console.log('\n--- 6. Exception/Rejection Handling (Simulating real events) ---');
495
- // IMPORTANT: Set this to true ONLY if you want the script to exit upon error.
496
- // Set to false for this demo to see both handlers potentially log.
497
- const exitOnHandler = false;
492
+ const handleErrorsAndExit = false;
498
493
  let errorHandlingLogger: Logger | undefined = undefined;
499
494
 
500
- console.log(`Creating logger with handleExceptions/Rejections, exitOnError: ${exitOnHandler}`);
495
+ console.log(`Creating logger with handleExceptions/Rejections, exitOnError: ${handleErrorsAndExit}`);
501
496
  errorHandlingLogger = createLogger({
502
- level: 'debug', // Log everything from the handlers
503
- transports: [new transports.Console({ format: format.defaultSimpleFormat })], // Use simple format for errors
504
- handleExceptions: true, // Enable uncaughtException handler
505
- handleRejections: true, // Enable unhandledRejection handler
506
- exitOnError: exitOnHandler // Control whether process exits
497
+ level: 'debug',
498
+ transports: [new transports.Console({ format: format.defaultSimpleFormat })],
499
+ handleExceptions: true,
500
+ handleRejections: true,
501
+ exitOnError: handleErrorsAndExit
507
502
  });
508
- errorHandlingLogger.info(`Error handlers active (exitOnError: ${exitOnHandler}).`);
509
-
510
- // --- Simulate Errors ---
511
- // NOTE: In a real app, these would happen unexpectedly.
512
- // We use setTimeout to allow the script to reach the end and potentially remove handlers if exitOnError is false.
503
+ errorHandlingLogger.info(`Error handlers active (exitOnError: ${handleErrorsAndExit}).`);
513
504
 
514
505
  console.log("Simulating unhandled rejection (will be logged)...");
515
- // Intentionally create an unhandled rejection
516
506
  Promise.reject("Simulated rejection reason (handled by logger)");
517
507
 
518
508
  console.log("Simulating uncaught exception in 100ms (will be logged)...");
519
509
  const exceptionTimer = setTimeout(() => {
520
- // Simulate an error that wasn't caught by application code
521
- throw new Error("Simulated uncaught exception (handled by logger)");
522
- // Note: If exitOnError were true, the process would exit shortly after logging this.
510
+ try { throw new Error("Simulated uncaught exception (handled by logger)"); }
511
+ catch (e) { process.emit('uncaughtException', e as Error); }
512
+ if (!handleErrorsAndExit && errorHandlingLogger && typeof (errorHandlingLogger as any).removeExceptionHandlers === 'function') {
513
+ (errorHandlingLogger as any).removeExceptionHandlers();
514
+ console.log("Error handlers removed for no-exit logger.");
515
+ }
516
+ console.log('\n--- Showcase Finished (Error Handlers Tested) ---');
523
517
  }, 100);
524
518
 
525
- // --- Wait and Cleanup (only necessary if exitOnError is false) ---
526
- if (!exitOnHandler) {
527
- console.log("Waiting briefly for handlers to potentially run...");
528
- await new Promise(resolve => setTimeout(resolve, 300)); // Wait longer than the exception timeout
529
-
530
- console.log("Attempting to remove handlers (as exitOnError was false)...");
531
- if (errorHandlingLogger && typeof (errorHandlingLogger as any).removeExceptionHandlers === 'function') {
532
- (errorHandlingLogger as any).removeExceptionHandlers();
533
- errorHandlingLogger.info('Error handlers removed.'); // Log using the same logger
534
- } else {
535
- console.warn('[Showcase] Could not remove error handlers (method not found?).');
536
- }
519
+ if (!handleErrorsAndExit) {
520
+ await new Promise(resolve => setTimeout(resolve, 300));
521
+ } else {
522
+ // If we expect exit, keep the process running briefly
523
+ // This usually isn't needed as process.exit stops everything
524
+ // await new Promise(resolve => setTimeout(resolve, 1500));
537
525
  }
538
526
 
539
- // If exitOnError was true, the script might have exited before reaching here.
540
- console.log('\n===========================================');
541
- console.log('✅ Scribelog Showcase Finished (Check logs above) ✅');
542
- console.log('===========================================');
543
-
544
527
  } // End runShowcase
545
528
 
546
- // Run the main demo function
547
529
  runShowcase().catch(e => {
548
- // This catch is unlikely to be hit if handleExceptions is true,
549
- // but good practice to have.
550
530
  console.error("!!! Unexpected error running showcase:", e);
551
531
  });
552
532
  ```
553
533
 
554
534
  </details>
535
+
555
536
  If you have any questions about scribelog or would like to know more about how to use any of the features. You can write to me on discord:
556
537
  theonlytolon
557
538
 
558
539
  ---
559
540
 
541
+ ## 📘 Documentation
542
+
543
+ For detailed documentation covering all features, configuration options, and advanced examples, please see the [**Full Documentation**](./DOCUMENTATION.md).
544
+
545
+ ---
546
+
560
547
  ## 📚 Future Work
561
548
 
562
- * **File Transport:** Adding `FileTransport` with log rotation (size, date).
563
- * **More Formatters:** `splat`/`printf`, potentially customizable color themes.
564
- * **Custom Levels:** Allowing users to define their own logging levels.
565
- * **Async Handling:** Better guarantees for transports finishing writes before `exitOnError`.
549
+ * More built-in formatters (e.g., customizable color themes).
550
+ * Ability to define custom logging levels.
551
+ * Improved handling of asynchronous operations in transports (especially for `exitOnError`).
566
552
 
567
553
  ---
568
554