@rabbit-company/logger 5.0.0 → 5.2.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
@@ -4,20 +4,17 @@
4
4
  [![JSR Version](https://jsr.io/badges/@rabbit-company/logger)](https://jsr.io/@rabbit-company/logger)
5
5
  [![License](https://img.shields.io/npm/l/@rabbit-company/logger)](LICENSE)
6
6
 
7
- A versatile, multi-transport logging library for Node.js and browser environments with support for:
8
-
9
- - Console output (with colors and custom formatting)
10
- - NDJSON (Newline Delimited JSON)
11
- - Grafana Loki (with batching and label management)
7
+ A high-performance, multi-transport logging library for Node.js and browser environments with first-class TypeScript support.
12
8
 
13
9
  ## Features ✨
14
10
 
15
- - **Multiple log levels**: ERROR, WARN, INFO, HTTP, VERBOSE, DEBUG, SILLY
16
- - **Structured logging**: Attach metadata objects to log entries
17
- - **Transport system**: Console, NDJSON, and Loki transports included
18
- - **Loki optimized**: Automatic label management and batching
19
- - **TypeScript ready**: Full type definitions included
20
- - **Cross-platform**: Works in Node.js, Deno, Bun and browsers
11
+ - **Multi-level logging**: 8 severity levels from ERROR to SILLY
12
+ - **Structured logging**: Attach rich metadata to log entries
13
+ - **Multiple transports**: Console, NDJSON, and Grafana Loki
14
+ - **Advanced formatting**: Customizable console output with extensive datetime options
15
+ - **Production-ready**: Batching, retries, and queue management for Loki
16
+ - **Cross-platform**: Works in Node.js, Deno, Bun and modern browsers
17
+ - **Type-safe**: Full TypeScript definitions included
21
18
 
22
19
  ## Installation 📦
23
20
 
@@ -38,14 +35,74 @@ pnpm add @rabbit-company/logger
38
35
  import { Logger, Levels } from "@rabbit-company/logger";
39
36
 
40
37
  // Create logger with default console transport
41
- const logger = new Logger({ level: Levels.DEBUG });
38
+ const logger = new Logger({
39
+ level: Levels.DEBUG, // Show DEBUG and above
40
+ });
42
41
 
43
- // Log messages with metadata
44
- logger.info("Application started", { version: "1.0.0" });
42
+ // Simple log
43
+ logger.info("Application starting...");
44
+
45
+ // Structured logging
45
46
  logger.error("Database connection failed", {
46
47
  error: "Connection timeout",
47
48
  attempt: 3,
49
+ db: "primary",
48
50
  });
51
+
52
+ // Audit logging
53
+ logger.audit("User login", {
54
+ userId: "usr_123",
55
+ ip: "192.168.1.100",
56
+ });
57
+ ```
58
+
59
+ ## Log Levels 📊
60
+
61
+ | Level | Description | Typical Use Case |
62
+ | ------- | ------------------------------- | ------------------------------------- |
63
+ | ERROR | Critical errors | System failures, unhandled exceptions |
64
+ | WARN | Potential issues | Deprecations, rate limiting |
65
+ | AUDIT | Security events | Logins, permission changes |
66
+ | INFO | Important runtime information | Service starts, config changes |
67
+ | HTTP | HTTP traffic | Request/response logging |
68
+ | DEBUG | Debug information | Flow tracing, variable dumps |
69
+ | VERBOSE | Very detailed information | Data transformations |
70
+ | SILLY | Extremely low-level information | Inner loop logging |
71
+
72
+ ## Console Formatting 🖥️
73
+
74
+ The console transport supports extensive datetime formatting:
75
+
76
+ ### Available Placeholders
77
+
78
+ #### UTC Formats:
79
+
80
+ - `{iso}`: Full ISO-8601 (2023-11-15T14:30:45.123Z)
81
+ - `{datetime}`: Simplified (2023-11-15 14:30:45)
82
+ - `{date}`: Date only (2023-11-15)
83
+ - `{time}`: Time only (14:30:45)
84
+ - `{utc}`: UTC string (Wed, 15 Nov 2023 14:30:45 GMT)
85
+ - `{ms}`: Milliseconds since epoch
86
+
87
+ #### Local Time Formats:
88
+
89
+ - `{datetime-local}`: Local datetime (2023-11-15 14:30:45)
90
+ - `{date-local}`: Local date only (2023-11-15)
91
+ - `{time-local}`: Local time only (14:30:45)
92
+ - `{full-local}`: Complete local string with timezone
93
+
94
+ #### Log Content:
95
+
96
+ - `{type}`: Log level (INFO, ERROR, etc.)
97
+ - `{message}`: The log message
98
+
99
+ ```js
100
+ import { ConsoleTransport } from "@rabbit-company/logger";
101
+
102
+ // Custom format examples
103
+ new ConsoleTransport("[{datetime-local}] {type} {message}");
104
+ new ConsoleTransport("{time} | {type} | {message}", false);
105
+ new ConsoleTransport("EPOCH:{ms} {message}");
49
106
  ```
50
107
 
51
108
  ## Transports 🚚
@@ -58,7 +115,7 @@ import { ConsoleTransport } from "@rabbit-company/logger";
58
115
  const logger = new Logger({
59
116
  transports: [
60
117
  new ConsoleTransport(
61
- "[{date}] {type} {message}", // Custom format
118
+ "[{time-local}] {type} {message}", // Custom format
62
119
  true // Enable colors
63
120
  ),
64
121
  ],
@@ -79,133 +136,35 @@ const logger = new Logger({
79
136
  console.log(ndjsonTransport.getData());
80
137
  ```
81
138
 
82
- ### Loki Transport
139
+ ### Loki Transport (Grafana)
83
140
 
84
141
  ```js
85
142
  import { LokiTransport } from "@rabbit-company/logger";
86
143
 
144
+ const lokiTransport = new LokiTransport({
145
+ url: "http://localhost:3100",
146
+ labels: {
147
+ app: "test",
148
+ env: process.env.NODE_ENV,
149
+ },
150
+ basicAuth: {
151
+ username: process.env.LOKI_USER,
152
+ password: process.env.LOKI_PASS,
153
+ },
154
+ batchSize: 50, // Send batches of 50 logs
155
+ batchTimeout: 5000, // Max 5s wait per batch
156
+ maxQueueSize: 10000, // Keep max 10,000 logs in memory
157
+ debug: true, // Log transport errors
158
+ });
159
+
87
160
  const logger = new Logger({
88
- transports: [
89
- new LokiTransport({
90
- url: "http://localhost:3100",
91
- labels: { app: "my-app", env: "production" },
92
- basicAuth: { username: "user", password: "pass" },
93
- maxLabelCount: 30,
94
- }),
95
- ],
161
+ transports: [lokiTransport],
96
162
  });
97
163
  ```
98
164
 
99
165
  ## API Reference 📚
100
166
 
101
- ### Log Levels
102
-
103
- ```js
104
- enum Levels {
105
- ERROR, // Critical errors
106
- WARN, // Warnings
107
- INFO, // Informational messages
108
- HTTP, // HTTP-related logs
109
- VERBOSE, // Verbose debugging
110
- DEBUG, // Debug messages
111
- SILLY // Very low-level logs
112
- }
113
- ```
114
-
115
- ### Logging Methods
116
-
117
- ```js
118
- logger.error(message: string, metadata?: object): void
119
- logger.warn(message: string, metadata?: object): void
120
- logger.info(message: string, metadata?: object): void
121
- logger.http(message: string, metadata?: object): void
122
- logger.verbose(message: string, metadata?: object): void
123
- logger.debug(message: string, metadata?: object): void
124
- logger.silly(message: string, metadata?: object): void
125
- ```
126
-
127
- ### Types
128
-
129
- ```ts
130
- /**
131
- * Represents a single log entry with message, severity level, timestamp, and optional metadata
132
- */
133
- export interface LogEntry {
134
- /** The log message content */
135
- message: string;
136
- /** Severity level of the log entry */
137
- level: Levels;
138
- /** Timestamp in milliseconds since epoch */
139
- timestamp: number;
140
- /** Optional structured metadata associated with the log */
141
- metadata?: Record<string, any>;
142
- }
143
-
144
- /**
145
- * Interface for log transport implementations
146
- */
147
- export interface Transport {
148
- /**
149
- * Processes and outputs a log entry
150
- * @param entry The log entry to process
151
- */
152
- log: (entry: LogEntry) => void;
153
- }
154
-
155
- /**
156
- * Configuration options for the Logger instance
157
- */
158
- export interface LoggerConfig {
159
- /** Minimum log level to output (default: INFO) */
160
- level?: Levels;
161
- /** Enable colored output (default: true) */
162
- colors?: boolean;
163
- /** Format string using {date}, {type}, {message} placeholders (default: "[{date}] {type} {message}") */
164
- format?: string;
165
- /** Array of transports to use (default: [ConsoleTransport]) */
166
- transports?: Transport[];
167
- }
168
-
169
- /**
170
- * Configuration for Loki transport
171
- */
172
- export interface LokiConfig {
173
- /** Loki server URL (e.g., "http://localhost:3100") */
174
- url: string;
175
- /** Base labels to attach to all logs */
176
- labels?: Record<string, string>;
177
- /** Basic authentication credentials */
178
- basicAuth?: {
179
- username: string;
180
- password: string;
181
- };
182
- /** Number of logs to batch before sending (default: 10) */
183
- batchSize?: number;
184
- /** Maximum time in ms to wait before sending a batch (default: 5000) */
185
- batchTimeout?: number;
186
- /** Tenant ID for multi-tenant Loki setups */
187
- tenantID?: string;
188
- /** Maximum number of labels allowed (default: 50) */
189
- maxLabelCount?: number;
190
- /** Enable debug logging for transport errors (default: false) */
191
- debug?: boolean;
192
- }
193
-
194
- /**
195
- * Represents a Loki log stream with labels and log values
196
- */
197
- export interface LokiStream {
198
- /** Key-value pairs of log labels */
199
- stream: {
200
- /** Log level label (required) */
201
- level: string;
202
- /** Additional custom labels */
203
- [key: string]: string;
204
- };
205
- /** Array of log entries with [timestamp, message] pairs */
206
- values: [[string, string]];
207
- }
208
- ```
167
+ Full API documentation is available in the [TypeScript definitions](https://github.com/Rabbit-Company/Logger-JS/blob/main/src/types.ts).
209
168
 
210
169
  ## Advanced Usage 🛠️
211
170
 
@@ -45,46 +45,88 @@ export declare const enum Colors {
45
45
  *
46
46
  * The levels are ordered from most important (ERROR) to least important (SILLY).
47
47
  * When setting a log level, only messages of that level or higher will be emitted.
48
+ *
49
+ * @example
50
+ * // Set logger to display DEBUG level and above
51
+ * logger.setLevel(Levels.DEBUG);
48
52
  */
49
53
  export declare enum Levels {
50
54
  /**
51
55
  * Error level. Indicates critical issues that require immediate attention.
52
56
  * Use for unrecoverable errors that prevent normal operation.
57
+ * @example
58
+ * logger.error("Database connection failed");
53
59
  */
54
60
  ERROR = 0,
55
61
  /**
56
62
  * Warning level. Indicates potential issues or noteworthy conditions.
57
63
  * Use for recoverable issues that don't prevent normal operation.
64
+ * @example
65
+ * logger.warn("High memory usage detected");
58
66
  */
59
67
  WARN = 1,
68
+ /**
69
+ * Audit level. For security-sensitive operations and compliance logging.
70
+ * Use for tracking authentication, authorization, and sensitive data access.
71
+ * @example
72
+ * logger.audit("User permissions changed", { user: "admin", changes: [...] });
73
+ */
74
+ AUDIT = 2,
60
75
  /**
61
76
  * Informational level. Provides general information about the application's state.
62
77
  * Use for normal operational messages that highlight progress.
78
+ * @example
79
+ * logger.info("Application started on port 3000");
63
80
  */
64
- INFO = 2,
81
+ INFO = 3,
65
82
  /**
66
83
  * HTTP-related level. Logs HTTP requests and responses.
67
84
  * Use for tracking HTTP API calls and their status.
85
+ * @example
86
+ * logger.http("GET /api/users 200 45ms");
68
87
  */
69
- HTTP = 3,
70
- /**
71
- * Verbose level. Provides detailed information for in-depth analysis.
72
- * Use for detailed operational logs that are typically only needed during debugging.
73
- */
74
- VERBOSE = 4,
88
+ HTTP = 4,
75
89
  /**
76
90
  * Debug level. Provides detailed context for debugging purposes.
77
91
  * Use for extended debugging information during development.
92
+ * @example
93
+ * logger.debug("Database query", { query: "...", duration: "120ms" });
78
94
  */
79
95
  DEBUG = 5,
96
+ /**
97
+ * Verbose level. Provides detailed information for in-depth analysis.
98
+ * Use for detailed operational logs that are typically only needed during debugging.
99
+ * @example
100
+ * logger.verbose("Cache update cycle completed", { entries: 1423 });
101
+ */
102
+ VERBOSE = 6,
80
103
  /**
81
104
  * Silly level. Logs very low-level messages.
82
105
  * Use for extremely verbose logging messages.
106
+ * @example
107
+ * logger.silly("Iteration 14563 completed");
83
108
  */
84
- SILLY = 6
109
+ SILLY = 7
85
110
  }
86
111
  /**
87
112
  * Represents a single log entry with message, severity level, timestamp, and optional metadata
113
+ *
114
+ * @interface LogEntry
115
+ * @property {string} message - The primary log message content
116
+ * @property {Levels} level - Severity level of the log entry
117
+ * @property {number} timestamp - Unix timestamp in milliseconds since epoch
118
+ * @property {Object.<string, any>} [metadata] - Optional structured metadata associated with the log
119
+ *
120
+ * @example
121
+ * {
122
+ * message: "User login successful",
123
+ * level: Levels.INFO,
124
+ * timestamp: Date.now(),
125
+ * metadata: {
126
+ * userId: "12345",
127
+ * ipAddress: "192.168.1.100"
128
+ * }
129
+ * }
88
130
  */
89
131
  export interface LogEntry {
90
132
  /** The log message content */
@@ -97,50 +139,168 @@ export interface LogEntry {
97
139
  metadata?: Record<string, any>;
98
140
  }
99
141
  /**
100
- * Interface for log transport implementations
142
+ * Interface that all log transport implementations must adhere to
143
+ *
144
+ * @interface Transport
145
+ *
146
+ * @example
147
+ * class CustomTransport implements Transport {
148
+ * log(entry: LogEntry) {
149
+ * // Custom log handling implementation
150
+ * }
151
+ * }
101
152
  */
102
153
  export interface Transport {
103
154
  /**
104
155
  * Processes and outputs a log entry
105
- * @param entry The log entry to process
156
+ * @param {LogEntry} entry - The log entry to process
157
+ * @returns {void}
106
158
  */
107
159
  log: (entry: LogEntry) => void;
108
160
  }
109
161
  /**
110
162
  * Configuration options for the Logger instance
163
+ *
164
+ * @interface LoggerConfig
165
+ * @property {Levels} [level=Levels.INFO] - Minimum log level to output
166
+ * @property {boolean} [colors=true] - Enable colored output in console
167
+ * @property {string} [format="[{datetime-local}] {type} {message}"] - Format string supporting these placeholders:
168
+ *
169
+ * ## Time/Date Placeholders
170
+ *
171
+ * ### UTC Formats
172
+ * - `{iso}`: Full ISO-8601 format with milliseconds (YYYY-MM-DDTHH:MM:SS.mmmZ)
173
+ * - `{datetime}`: Simplified ISO without milliseconds (YYYY-MM-DD HH:MM:SS)
174
+ * - `{date}`: Date component only (YYYY-MM-DD)
175
+ * - `{time}`: Time component only (HH:MM:SS)
176
+ * - `{utc}`: Complete UTC string (e.g., "Wed, 15 Nov 2023 14:30:45 GMT")
177
+ * - `{ms}`: Milliseconds since Unix epoch
178
+ *
179
+ * ### Local Time Formats
180
+ * - `{datetime-local}`: Local date and time (YYYY-MM-DD HH:MM:SS)
181
+ * - `{date-local}`: Local date only (YYYY-MM-DD)
182
+ * - `{time-local}`: Local time only (HH:MM:SS)
183
+ * - `{full-local}`: Complete local string with timezone
184
+ *
185
+ * ## Log Content Placeholders
186
+ * - `{type}`: Log level name (e.g., "INFO", "ERROR")
187
+ * - `{message}`: The actual log message content
188
+ *
189
+ * @property {Transport[]} [transports=[ConsoleTransport]] - Array of transports to use
190
+ *
191
+ * @example <caption>Default Format</caption>
192
+ * {
193
+ * format: "[{datetime-local}] {type} {message}"
194
+ * }
195
+ *
196
+ * @example <caption>UTC Time Format</caption>
197
+ * {
198
+ * format: "[{datetime} UTC] {type}: {message}"
199
+ * }
200
+ *
201
+ * @example <caption>Detailed Local Format</caption>
202
+ * {
203
+ * format: "{date-local} {time-local} [{type}] {message}"
204
+ * }
205
+ *
206
+ * @example <caption>Epoch Timestamp</caption>
207
+ * {
208
+ * format: "{ms} - {type} - {message}"
209
+ * }
111
210
  */
112
211
  export interface LoggerConfig {
113
212
  /** Minimum log level to output (default: INFO) */
114
213
  level?: Levels;
115
214
  /** Enable colored output (default: true) */
116
215
  colors?: boolean;
117
- /** Format string using {date}, {type}, {message} placeholders (default: "[{date}] {type} {message}") */
216
+ /** Format string using placeholders (default: "[{datetime-local}] {type} {message}") */
118
217
  format?: string;
119
218
  /** Array of transports to use (default: [ConsoleTransport]) */
120
219
  transports?: Transport[];
121
220
  }
122
221
  /**
123
- * Configuration for Loki transport
222
+ * Configuration options for the Loki transport
223
+ *
224
+ * @interface LokiConfig
225
+ *
226
+ * @example <caption>Basic Configuration</caption>
227
+ * {
228
+ * url: "http://localhost:3100/loki/api/v1/push",
229
+ * labels: { app: "frontend", env: "production" }
230
+ * }
231
+ *
232
+ * @example <caption>Advanced Configuration</caption>
233
+ * {
234
+ * url: "http://loki.example.com",
235
+ * basicAuth: { username: "user", password: "pass" },
236
+ * tenantID: "team-a",
237
+ * batchSize: 50,
238
+ * batchTimeout: 2000,
239
+ * maxQueueSize: 50000,
240
+ * maxRetries: 10,
241
+ * retryBaseDelay: 2000,
242
+ * debug: true
243
+ * }
124
244
  */
125
245
  export interface LokiConfig {
126
- /** Loki server URL (e.g., "http://localhost:3100") */
246
+ /**
247
+ * Required Loki server endpoint URL
248
+ * @example "http://loki.example.com"
249
+ */
127
250
  url: string;
128
- /** Base labels to attach to all logs */
251
+ /**
252
+ * Base labels attached to all log entries
253
+ * @example { app: "frontend", env: "production" }
254
+ */
129
255
  labels?: Record<string, string>;
130
256
  /** Basic authentication credentials */
131
257
  basicAuth?: {
258
+ /** Basic auth username */
132
259
  username: string;
260
+ /** Basic auth password */
133
261
  password: string;
134
262
  };
135
- /** Number of logs to batch before sending (default: 10) */
136
- batchSize?: number;
137
- /** Maximum time in ms to wait before sending a batch (default: 5000) */
138
- batchTimeout?: number;
139
- /** Tenant ID for multi-tenant Loki setups */
263
+ /**
264
+ * Tenant ID for multi-tenant Loki installations
265
+ * @description Sets the X-Scope-OrgID header
266
+ */
140
267
  tenantID?: string;
141
- /** Maximum number of labels allowed (default: 50) */
268
+ /**
269
+ * Maximum number of labels allowed per log entry
270
+ * @default 50
271
+ */
142
272
  maxLabelCount?: number;
143
- /** Enable debug logging for transport errors (default: false) */
273
+ /**
274
+ * Number of logs to accumulate before automatic sending
275
+ * @default 10
276
+ */
277
+ batchSize?: number;
278
+ /**
279
+ * Maximum time in milliseconds to wait before sending an incomplete batch
280
+ * @default 5000
281
+ */
282
+ batchTimeout?: number;
283
+ /**
284
+ * Maximum number of logs to buffer in memory during outages
285
+ * @description When reached, oldest logs are dropped
286
+ * @default 10000
287
+ */
288
+ maxQueueSize?: number;
289
+ /**
290
+ * Maximum number of attempts to send a failed batch
291
+ * @default 5
292
+ */
293
+ maxRetries?: number;
294
+ /**
295
+ * Initial retry delay in milliseconds (exponential backoff base)
296
+ * @description Delay doubles with each retry up to maximum 30s
297
+ * @default 1000
298
+ */
299
+ retryBaseDelay?: number;
300
+ /**
301
+ * Enable debug logging for transport internals
302
+ * @default false
303
+ */
144
304
  debug?: boolean;
145
305
  }
146
306
  /**
@@ -216,126 +376,382 @@ export declare class Logger {
216
376
  */
217
377
  private processEntry;
218
378
  /**
219
- * Logs an error message
379
+ * Logs an error message (highest severity)
220
380
  * @param message The error message
221
381
  * @param metadata Optional metadata object
382
+ * @example
383
+ * logger.error("Database connection failed", { error: error.stack });
222
384
  */
223
385
  error(message: string, metadata?: Record<string, any>): void;
224
386
  /**
225
387
  * Logs a warning message
226
388
  * @param message The warning message
227
389
  * @param metadata Optional metadata object
390
+ * @example
391
+ * logger.warn("High memory usage detected", { usage: "85%" });
228
392
  */
229
393
  warn(message: string, metadata?: Record<string, any>): void;
394
+ /**
395
+ * Logs security-sensitive audit events
396
+ * @param message The audit message
397
+ * @param metadata Optional metadata object
398
+ * @example
399
+ * logger.audit("User permissions modified", {
400
+ * actor: "admin@example.com",
401
+ * action: "role_change",
402
+ * target: "user:1234"
403
+ * });
404
+ */
405
+ audit(message: string, metadata?: Record<string, any>): void;
230
406
  /**
231
407
  * Logs an informational message
232
408
  * @param message The info message
233
409
  * @param metadata Optional metadata object
410
+ * @example
411
+ * logger.info("Server started", { port: 3000, env: "production" });
234
412
  */
235
413
  info(message: string, metadata?: Record<string, any>): void;
236
414
  /**
237
- * Logs an HTTP-related message
415
+ * Logs HTTP-related messages
238
416
  * @param message The HTTP message
239
417
  * @param metadata Optional metadata object
418
+ * @example
419
+ * logger.http("Request completed", {
420
+ * method: "GET",
421
+ * path: "/api/users",
422
+ * status: 200,
423
+ * duration: "45ms"
424
+ * });
240
425
  */
241
426
  http(message: string, metadata?: Record<string, any>): void;
242
427
  /**
243
- * Logs a verbose message
244
- * @param message The verbose message
428
+ * Logs debug information (for development environments)
429
+ * @param message The debug message
245
430
  * @param metadata Optional metadata object
431
+ * @example
432
+ * logger.debug("Database query", {
433
+ * query: "SELECT * FROM users",
434
+ * parameters: { limit: 50 }
435
+ * });
246
436
  */
247
- verbose(message: string, metadata?: Record<string, any>): void;
437
+ debug(message: string, metadata?: Record<string, any>): void;
248
438
  /**
249
- * Logs a debug message
250
- * @param message The debug message
439
+ * Logs verbose tracing information (very detailed)
440
+ * @param message The verbose message
251
441
  * @param metadata Optional metadata object
442
+ * @example
443
+ * logger.verbose("Cache update cycle", {
444
+ * entriesProcessed: 1423,
445
+ * memoryUsage: "1.2MB"
446
+ * });
252
447
  */
253
- debug(message: string, metadata?: Record<string, any>): void;
448
+ verbose(message: string, metadata?: Record<string, any>): void;
254
449
  /**
255
- * Logs a silly message (lowest level)
450
+ * Logs extremely low-level details (lowest severity)
256
451
  * @param message The silly message
257
- * @param metadata Optional metadata object
452
+ * @param metadata Optional metadata data
453
+ * @example
454
+ * logger.silly("Iteration complete", { iteration: 14563 });
258
455
  */
259
456
  silly(message: string, metadata?: Record<string, any>): void;
260
457
  /**
261
458
  * Adds a new transport to the logger
262
459
  * @param transport The transport to add
460
+ * @example
461
+ * logger.addTransport(new LokiTransport({ url: "http://loki:3100" }));
263
462
  */
264
463
  addTransport(transport: Transport): void;
265
464
  /**
266
465
  * Removes a transport from the logger
267
466
  * @param transport The transport to remove
467
+ * @example
468
+ * logger.removeTransport(consoleTransport);
268
469
  */
269
470
  removeTransport(transport: Transport): void;
270
471
  /**
271
472
  * Sets the minimum log level
272
473
  * @param level The new minimum log level
474
+ * @example
475
+ * // Only show errors and warnings
476
+ * logger.setLevel(Levels.WARN);
273
477
  */
274
478
  setLevel(level: Levels): void;
275
479
  }
276
480
  /**
277
- * Transport that outputs logs to the console with configurable formatting
481
+ * Transport that outputs logs to the console with configurable formatting and colors.
482
+ *
483
+ * Features:
484
+ * - Customizable output format with extensive placeholder support
485
+ * - ANSI color support (enabled by default)
486
+ * - Cross-platform compatibility (Node.js and browsers)
487
+ * - Lightweight and performant
488
+ *
489
+ * @example
490
+ * // Basic usage with default formatting
491
+ * const transport = new ConsoleTransport();
492
+ *
493
+ * @example
494
+ * // Custom format with local timestamps
495
+ * const transport = new ConsoleTransport(
496
+ * "[{datetime-local}] {type} - {message}",
497
+ * true
498
+ * );
278
499
  */
279
500
  export declare class ConsoleTransport implements Transport {
280
501
  private format;
281
502
  private colors;
282
503
  /**
283
- * Create a ConsoleTransport instance
284
- * @param format Format string using {date}, {type}, {message} placeholders
285
- * @param colors Enable colored output
504
+ * Creates a new ConsoleTransport instance
505
+ * @param format Format string supporting these placeholders:
506
+ *
507
+ * ### Time/Date Formats
508
+ * - `{iso}`: Full ISO-8601 UTC format (YYYY-MM-DDTHH:MM:SS.mmmZ)
509
+ * - `{datetime}`: Simplified UTC (YYYY-MM-DD HH:MM:SS)
510
+ * - `{date}`: UTC date only (YYYY-MM-DD)
511
+ * - `{time}`: UTC time only (HH:MM:SS)
512
+ * - `{datetime-local}`: Local datetime (YYYY-MM-DD HH:MM:SS)
513
+ * - `{date-local}`: Local date only (YYYY-MM-DD)
514
+ * - `{time-local}`: Local time only (HH:MM:SS)
515
+ * - `{ms}`: Milliseconds since epoch
516
+ *
517
+ * ### Log Content
518
+ * - `{type}`: Log level name (e.g., "INFO")
519
+ * - `{message}`: The log message content
520
+ *
521
+ * @default "[{datetime-local}] {type} {message}"
522
+ *
523
+ * @param colors Enable ANSI color output. When disabled:
524
+ * - Improves performance in non-TTY environments
525
+ * - Removes all color formatting
526
+ * @default true
527
+ *
528
+ * @example
529
+ * // UTC format example
530
+ * new ConsoleTransport("{date} {time} [{type}] {message}");
531
+ *
532
+ * @example
533
+ * // Local time with colors disabled
534
+ * new ConsoleTransport("{time-local} - {message}", false);
286
535
  */
287
536
  constructor(format?: string, colors?: boolean);
288
537
  /**
289
- * Output a log entry to the console
290
- * @param entry The log entry to output
538
+ * Formats and outputs a log entry to the console.
539
+ *
540
+ * Applies the configured format with these features:
541
+ * - All specified placeholders are replaced
542
+ * - Colors are applied to level names and messages
543
+ * - Timestamps are dimmed for better readability
544
+ *
545
+ * @param entry The log entry containing:
546
+ * - message: string - The primary log content
547
+ * - level: Levels - The severity level
548
+ * - timestamp: number - Creation time (ms since epoch)
549
+ *
550
+ * @example
551
+ * // With all placeholder types
552
+ * transport.log({
553
+ * message: "User logged in",
554
+ * level: Levels.INFO,
555
+ * timestamp: Date.now()
556
+ * });
291
557
  */
292
558
  log(entry: LogEntry): void;
293
559
  }
294
560
  /**
295
- * Transport that collects logs in NDJSON (Newline Delimited JSON) format
561
+ * Transport that collects logs in NDJSON (Newline Delimited JSON) format.
562
+ *
563
+ * This transport accumulates log entries in memory as NDJSON strings,
564
+ * which can be retrieved or cleared as needed. Useful for:
565
+ * - Log aggregation
566
+ * - Bulk exporting logs
567
+ * - Integration with log processing pipelines
568
+ *
569
+ * @example
570
+ * // Basic usage
571
+ * const transport = new NDJsonTransport();
572
+ * const logger = new Logger({ transports: [transport] });
573
+ *
574
+ * // Get logs as NDJSON string
575
+ * const logs = transport.getData();
576
+ *
577
+ * @example
578
+ * // Periodic log flushing
579
+ * setInterval(() => {
580
+ * const logs = transport.getData();
581
+ * if (logs) {
582
+ * sendToServer(logs);
583
+ * transport.reset();
584
+ * }
585
+ * }, 60000);
296
586
  */
297
587
  export declare class NDJsonTransport implements Transport {
298
588
  private data;
299
589
  /**
300
- * Append a log entry to the NDJSON buffer
301
- * @param entry The log entry to append
590
+ * Appends a log entry to the internal NDJSON buffer.
591
+ *
592
+ * Automatically adds newline separators between entries.
593
+ *
594
+ * @param entry The log entry to append. Must contain:
595
+ * - message: string
596
+ * - level: Levels
597
+ * - timestamp: number
598
+ * - metadata?: object
599
+ *
600
+ * @example
601
+ * transport.log({
602
+ * message: "System started",
603
+ * level: Levels.INFO,
604
+ * timestamp: Date.now()
605
+ * });
302
606
  */
303
607
  log(entry: LogEntry): void;
304
608
  /**
305
- * Get the accumulated NDJSON data
306
- * @returns The NDJSON formatted log data
609
+ * Retrieves all accumulated logs as an NDJSON string.
610
+ *
611
+ * The returned string will contain one log entry per line,
612
+ * with each line being a valid JSON string.
613
+ *
614
+ * @returns {string} NDJSON formatted log data. Returns empty string if no logs.
615
+ *
616
+ * @example
617
+ * // Get logs for API response
618
+ * app.get('/logs', (req, res) => {
619
+ * res.type('application/x-ndjson');
620
+ * res.send(transport.getData());
621
+ * });
307
622
  */
308
623
  getData(): string;
309
624
  /**
310
- * Clear the accumulated log data
625
+ * Clears all accumulated log data from memory.
626
+ *
627
+ * Typically called after successfully transmitting logs
628
+ * to prevent duplicate processing.
629
+ *
630
+ * @example
631
+ * // Clear after successful upload
632
+ * if (uploadLogs(transport.getData())) {
633
+ * transport.reset();
634
+ * }
311
635
  */
312
636
  reset(): void;
313
637
  }
314
638
  /**
315
- * Transport that sends logs to a Grafana Loki server
639
+ * High-reliability transport for sending logs to Grafana Loki with persistent queuing.
640
+ *
641
+ * Features:
642
+ * - Persistent in-memory queue with configurable size limits
643
+ * - Exponential backoff retry mechanism with configurable limits
644
+ * - Automatic batching for efficient network utilization
645
+ * - Label management with cardinality control
646
+ * - Multi-tenancy support via X-Scope-OrgID
647
+ * - Comprehensive error handling and recovery
648
+ *
649
+ * @implements {Transport}
650
+ *
651
+ * @example <caption>Basic Configuration</caption>
652
+ * const lokiTransport = new LokiTransport({
653
+ * url: "http://localhost:3100",
654
+ * labels: { app: "my-app", env: "production" }
655
+ * });
656
+ *
657
+ * @example <caption>Advanced Configuration</caption>
658
+ * const transport = new LokiTransport({
659
+ * url: "http://loki.example.com",
660
+ * batchSize: 50,
661
+ * batchTimeout: 2000,
662
+ * maxQueueSize: 50000,
663
+ * maxRetries: 10,
664
+ * retryBaseDelay: 2000,
665
+ * tenantID: "team-a",
666
+ * basicAuth: { username: "user", password: "pass" },
667
+ * debug: true
668
+ * });
316
669
  */
317
670
  export declare class LokiTransport implements Transport {
318
671
  private config;
319
- private batch;
672
+ /** @private Internal log queue */
673
+ private queue;
674
+ /** @private Current batch size setting */
320
675
  private batchSize;
676
+ /** @private Current batch timeout setting (ms) */
321
677
  private batchTimeout;
678
+ /** @private Handle for batch timeout */
322
679
  private timeoutHandle?;
680
+ /** @private Maximum allowed labels per entry */
323
681
  private maxLabelCount;
682
+ /** @private Debug mode flag */
324
683
  private debug;
684
+ /** @private Maximum queue size before dropping logs */
685
+ private maxQueueSize;
686
+ /** @private Current retry attempt count */
687
+ private retryCount;
688
+ /** @private Maximum allowed retry attempts */
689
+ private maxRetries;
690
+ /** @private Base delay for exponential backoff (ms) */
691
+ private retryBaseDelay;
692
+ /** @private Handle for retry timeout */
693
+ private retryTimer?;
694
+ /** @private Flag indicating active send operation */
695
+ private isSending;
325
696
  /**
326
- * Create a LokiTransport instance
327
- * @param config Configuration options for Loki
328
- * @throws {Error} If URL is not provided
697
+ * Creates a new LokiTransport instance
698
+ * @param {LokiConfig} config - Configuration options
699
+ * @param {string} config.url - Required Loki server endpoint (e.g., "http://localhost:3100")
700
+ * @param {Object.<string, string>} [config.labels] - Base labels attached to all log entries
701
+ * @param {Object} [config.basicAuth] - Basic authentication credentials
702
+ * @param {string} config.basicAuth.username - Username for basic auth
703
+ * @param {string} config.basicAuth.password - Password for basic auth
704
+ * @param {string} [config.tenantID] - Tenant ID for multi-tenant Loki installations
705
+ * @param {number} [config.batchSize=10] - Number of logs to accumulate before automatic sending
706
+ * @param {number} [config.batchTimeout=5000] - Maximum time (ms) to wait before sending incomplete batch
707
+ * @param {number} [config.maxLabelCount=50] - Maximum number of labels allowed per log entry
708
+ * @param {number} [config.maxQueueSize=10000] - Maximum number of logs to buffer in memory during outages
709
+ * @param {number} [config.maxRetries=5] - Maximum number of attempts to send a failed batch
710
+ * @param {number} [config.retryBaseDelay=1000] - Initial retry delay in ms (exponential backoff base)
711
+ * @param {boolean} [config.debug=false] - Enable debug logging for transport internals
329
712
  */
330
713
  constructor(config: LokiConfig);
331
714
  /**
332
- * Add a log entry to the batch (may trigger send if batch size is reached)
333
- * @param entry The log entry to send
715
+ * Queues a log entry for delivery to Loki
716
+ *
717
+ * @param {LogEntry} entry - The log entry to process
718
+ * @param {string} entry.message - Primary log message content
719
+ * @param {string} entry.level - Log severity level (e.g., "INFO", "ERROR")
720
+ * @param {number} entry.timestamp - Unix timestamp in milliseconds
721
+ * @param {Object} [entry.metadata] - Additional log metadata (will be converted to Loki labels)
722
+ *
723
+ * @example
724
+ * transport.log({
725
+ * message: "User login successful",
726
+ * level: "INFO",
727
+ * timestamp: Date.now(),
728
+ * metadata: {
729
+ * userId: "12345",
730
+ * sourceIP: "192.168.1.100",
731
+ * device: "mobile"
732
+ * }
733
+ * });
334
734
  */
335
735
  log(entry: LogEntry): void;
336
736
  /**
337
- * Send the current batch of logs to Loki
737
+ * Schedules the next batch send operation
738
+ * @private
739
+ * @param {boolean} [immediate=false] - Whether to send immediately without waiting for timeout
740
+ */
741
+ private scheduleSend;
742
+ /**
743
+ * Sends the current batch to Loki with retry logic
338
744
  * @private
745
+ * @async
746
+ * @returns {Promise<void>}
747
+ *
748
+ * @description
749
+ * Handles the complete send operation including:
750
+ * - Preparing HTTP request with proper headers
751
+ * - Executing the fetch request
752
+ * - Managing retries with exponential backoff
753
+ * - Queue cleanup on success/failure
754
+ * - Automatic scheduling of next batch
339
755
  */
340
756
  private sendBatch;
341
757
  }
package/module/logger.js CHANGED
@@ -26,38 +26,68 @@ var Levels;
26
26
  ((Levels2) => {
27
27
  Levels2[Levels2["ERROR"] = 0] = "ERROR";
28
28
  Levels2[Levels2["WARN"] = 1] = "WARN";
29
- Levels2[Levels2["INFO"] = 2] = "INFO";
30
- Levels2[Levels2["HTTP"] = 3] = "HTTP";
31
- Levels2[Levels2["VERBOSE"] = 4] = "VERBOSE";
29
+ Levels2[Levels2["AUDIT"] = 2] = "AUDIT";
30
+ Levels2[Levels2["INFO"] = 3] = "INFO";
31
+ Levels2[Levels2["HTTP"] = 4] = "HTTP";
32
32
  Levels2[Levels2["DEBUG"] = 5] = "DEBUG";
33
- Levels2[Levels2["SILLY"] = 6] = "SILLY";
33
+ Levels2[Levels2["VERBOSE"] = 6] = "VERBOSE";
34
+ Levels2[Levels2["SILLY"] = 7] = "SILLY";
34
35
  })(Levels ||= {});
35
36
  var LevelColors = {
36
37
  [0 /* ERROR */]: "\x1B[31m" /* RED */,
37
38
  [1 /* WARN */]: "\x1B[93m" /* BRIGHT_YELLOW */,
38
- [2 /* INFO */]: "\x1B[36m" /* CYAN */,
39
- [3 /* HTTP */]: "\x1B[34m" /* BLUE */,
40
- [4 /* VERBOSE */]: "\x1B[34m" /* BLUE */,
41
- [5 /* DEBUG */]: "\x1B[90m" /* BRIGHT_BLACK */,
42
- [6 /* SILLY */]: "\x1B[90m" /* BRIGHT_BLACK */
39
+ [2 /* AUDIT */]: "\x1B[35m" /* MAGENTA */,
40
+ [3 /* INFO */]: "\x1B[36m" /* CYAN */,
41
+ [4 /* HTTP */]: "\x1B[34m" /* BLUE */,
42
+ [5 /* DEBUG */]: "\x1B[34m" /* BLUE */,
43
+ [6 /* VERBOSE */]: "\x1B[90m" /* BRIGHT_BLACK */,
44
+ [7 /* SILLY */]: "\x1B[90m" /* BRIGHT_BLACK */
43
45
  };
44
46
  // src/formatters/consoleFormatter.ts
45
47
  function formatConsoleMessage(message, logLevel, format, colorsEnabled) {
46
- let type = Levels[logLevel];
47
- let date = new Date().toISOString().split(".")[0].replace("T", " ");
48
+ const now = new Date;
49
+ const type = Levels[logLevel];
50
+ const utcFormats = {
51
+ "{iso}": now.toISOString(),
52
+ "{datetime}": now.toISOString().split(".")[0].replace("T", " "),
53
+ "{time}": now.toISOString().split("T")[1].split(".")[0],
54
+ "{date}": now.toISOString().split("T")[0],
55
+ "{utc}": now.toUTCString(),
56
+ "{ms}": now.getTime().toString()
57
+ };
58
+ const localFormats = {
59
+ "{datetime-local}": now.toLocaleString("sv-SE").replace(" ", "T").split(".")[0].replace("T", " "),
60
+ "{time-local}": now.toLocaleTimeString("sv-SE"),
61
+ "{date-local}": now.toLocaleDateString("sv-SE"),
62
+ "{full-local}": now.toString()
63
+ };
64
+ let coloredType = type;
65
+ let coloredMessage = message;
48
66
  if (colorsEnabled) {
49
- date = "\x1B[90m" /* BRIGHT_BLACK */ + date + "\x1B[0m" /* RESET */;
50
- type = "\x1B[1m" /* BOLD */ + LevelColors[logLevel] + type + "\x1B[0m" /* RESET */;
51
- message = LevelColors[logLevel] + message + "\x1B[0m" /* RESET */;
67
+ const color = LevelColors[logLevel];
68
+ const colorize = (text) => "\x1B[90m" /* BRIGHT_BLACK */ + text + "\x1B[0m" /* RESET */;
69
+ Object.keys(utcFormats).forEach((key) => {
70
+ utcFormats[key] = colorize(utcFormats[key]);
71
+ });
72
+ Object.keys(localFormats).forEach((key) => {
73
+ localFormats[key] = colorize(localFormats[key]);
74
+ });
75
+ coloredType = "\x1B[1m" /* BOLD */ + color + type + "\x1B[0m" /* RESET */;
76
+ coloredMessage = color + message + "\x1B[0m" /* RESET */;
77
+ }
78
+ let output = format;
79
+ const allFormats = { ...utcFormats, ...localFormats };
80
+ for (const [placeholder, value] of Object.entries(allFormats)) {
81
+ output = output.replace(new RegExp(placeholder, "g"), value);
52
82
  }
53
- return format.replace("{date}", date).replace("{type}", type).replace("{message}", message);
83
+ return output.replace(/{type}/g, coloredType).replace(/{message}/g, coloredMessage);
54
84
  }
55
85
 
56
86
  // src/transports/consoleTransport.ts
57
87
  class ConsoleTransport {
58
88
  format;
59
89
  colors;
60
- constructor(format = "[{date}] {type} {message}", colors = true) {
90
+ constructor(format = "[{datetime-local}] {type} {message}", colors = true) {
61
91
  this.format = format;
62
92
  this.colors = colors;
63
93
  }
@@ -68,7 +98,7 @@ class ConsoleTransport {
68
98
 
69
99
  // src/logger.ts
70
100
  class Logger {
71
- level = 2 /* INFO */;
101
+ level = 3 /* INFO */;
72
102
  transports = [new ConsoleTransport];
73
103
  constructor(config) {
74
104
  if (config?.level !== undefined) {
@@ -102,20 +132,23 @@ class Logger {
102
132
  warn(message, metadata) {
103
133
  this.processEntry(this.createLogEntry(message, 1 /* WARN */, metadata));
104
134
  }
135
+ audit(message, metadata) {
136
+ this.processEntry(this.createLogEntry(message, 2 /* AUDIT */, metadata));
137
+ }
105
138
  info(message, metadata) {
106
- this.processEntry(this.createLogEntry(message, 2 /* INFO */, metadata));
139
+ this.processEntry(this.createLogEntry(message, 3 /* INFO */, metadata));
107
140
  }
108
141
  http(message, metadata) {
109
- this.processEntry(this.createLogEntry(message, 3 /* HTTP */, metadata));
110
- }
111
- verbose(message, metadata) {
112
- this.processEntry(this.createLogEntry(message, 4 /* VERBOSE */, metadata));
142
+ this.processEntry(this.createLogEntry(message, 4 /* HTTP */, metadata));
113
143
  }
114
144
  debug(message, metadata) {
115
145
  this.processEntry(this.createLogEntry(message, 5 /* DEBUG */, metadata));
116
146
  }
147
+ verbose(message, metadata) {
148
+ this.processEntry(this.createLogEntry(message, 6 /* VERBOSE */, metadata));
149
+ }
117
150
  silly(message, metadata) {
118
- this.processEntry(this.createLogEntry(message, 6 /* SILLY */, metadata));
151
+ this.processEntry(this.createLogEntry(message, 7 /* SILLY */, metadata));
119
152
  }
120
153
  addTransport(transport) {
121
154
  this.transports.push(transport);
@@ -186,40 +219,59 @@ function formatLokiMessage(entry, maxLabelCount, labels = {}) {
186
219
  // src/transports/lokiTransport.ts
187
220
  class LokiTransport {
188
221
  config;
189
- batch = [];
222
+ queue = [];
190
223
  batchSize;
191
224
  batchTimeout;
192
225
  timeoutHandle;
193
226
  maxLabelCount;
194
227
  debug;
228
+ maxQueueSize;
229
+ retryCount = 0;
230
+ maxRetries;
231
+ retryBaseDelay;
232
+ retryTimer;
233
+ isSending = false;
195
234
  constructor(config) {
196
235
  this.config = config;
197
236
  this.batchSize = config.batchSize || 10;
198
237
  this.batchTimeout = config.batchTimeout || 5000;
199
238
  this.maxLabelCount = config.maxLabelCount || 50;
200
239
  this.debug = config.debug || false;
201
- if (!config.url) {
202
- throw new Error("Loki URL is required");
203
- }
240
+ this.maxQueueSize = config.maxQueueSize || 1e4;
241
+ this.maxRetries = config.maxRetries || 5;
242
+ this.retryBaseDelay = config.retryBaseDelay || 1000;
204
243
  }
205
244
  log(entry) {
206
- const lokiMessage = formatLokiMessage(entry, this.maxLabelCount, { ...this.config.labels, ...entry.metadata });
207
- this.batch.push(lokiMessage);
208
- if (this.batch.length >= this.batchSize) {
209
- this.sendBatch();
210
- } else if (!this.timeoutHandle) {
211
- this.timeoutHandle = setTimeout(() => this.sendBatch(), this.batchTimeout);
245
+ const lokiMessage = formatLokiMessage(entry, this.maxLabelCount, {
246
+ ...this.config.labels,
247
+ ...entry.metadata
248
+ });
249
+ if (this.queue.length >= this.maxQueueSize) {
250
+ if (this.debug)
251
+ console.warn("Loki queue full - dropping oldest log entry");
252
+ this.queue.shift();
212
253
  }
254
+ this.queue.push(lokiMessage);
255
+ this.scheduleSend();
213
256
  }
214
- async sendBatch() {
257
+ scheduleSend(immediate = false) {
258
+ if (this.isSending)
259
+ return;
215
260
  if (this.timeoutHandle) {
216
261
  clearTimeout(this.timeoutHandle);
217
262
  this.timeoutHandle = undefined;
218
263
  }
219
- if (this.batch.length === 0)
264
+ if (this.queue.length > 0 && (immediate || this.queue.length >= this.batchSize)) {
265
+ this.sendBatch();
266
+ } else if (this.queue.length > 0) {
267
+ this.timeoutHandle = setTimeout(() => this.sendBatch(), this.batchTimeout);
268
+ }
269
+ }
270
+ async sendBatch() {
271
+ if (this.queue.length === 0 || this.isSending)
220
272
  return;
221
- const batchToSend = this.batch;
222
- this.batch = [];
273
+ this.isSending = true;
274
+ const batchToSend = this.queue.slice(0, this.batchSize);
223
275
  try {
224
276
  const headers = {
225
277
  "Content-Type": "application/json"
@@ -237,12 +289,38 @@ class LokiTransport {
237
289
  streams: batchToSend.flatMap((entry) => entry.streams)
238
290
  })
239
291
  });
240
- if (!response.ok && this.debug) {
241
- console.error("Failed to send logs to Loki: ", await response.text());
292
+ if (response.ok) {
293
+ this.queue = this.queue.slice(batchToSend.length);
294
+ this.retryCount = 0;
295
+ if (this.retryTimer) {
296
+ clearTimeout(this.retryTimer);
297
+ this.retryTimer = undefined;
298
+ }
299
+ } else {
300
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
242
301
  }
243
302
  } catch (error) {
244
303
  if (this.debug)
245
- console.error("Error sending logs to Loki: ", error);
304
+ console.error("Loki transmission error: ", error);
305
+ this.retryCount++;
306
+ if (this.retryCount <= this.maxRetries) {
307
+ const delay = Math.min(this.retryBaseDelay * Math.pow(2, this.retryCount - 1), 30000);
308
+ if (this.debug)
309
+ console.log(`Scheduling retry #${this.retryCount} in ${delay}ms`);
310
+ this.retryTimer = setTimeout(() => {
311
+ this.scheduleSend(true);
312
+ }, delay);
313
+ } else {
314
+ if (this.debug)
315
+ console.warn(`Max retries (${this.maxRetries}) reached. Dropping batch.`);
316
+ this.queue = this.queue.slice(batchToSend.length);
317
+ this.retryCount = 0;
318
+ }
319
+ } finally {
320
+ this.isSending = false;
321
+ if (this.queue.length > 0 && this.retryCount === 0) {
322
+ this.scheduleSend();
323
+ }
246
324
  }
247
325
  }
248
326
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabbit-company/logger",
3
- "version": "5.0.0",
3
+ "version": "5.2.0",
4
4
  "description": "A simple and lightweight logger",
5
5
  "main": "./module/index.js",
6
6
  "type": "module",
@@ -31,6 +31,7 @@
31
31
  "logger",
32
32
  "console",
33
33
  "ndjson",
34
+ "grafana",
34
35
  "loki"
35
36
  ],
36
37
  "devDependencies": {