@rabbit-company/logger 5.1.0 → 5.3.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, AUDIT, INFO, HTTP, DEBUG, VERBOSE, 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, Grafana Loki and Syslog
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 and Bun
17
+ - **Type-safe**: Full TypeScript definitions included
21
18
 
22
19
  ## Installation 📦
23
20
 
@@ -38,16 +35,40 @@ 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",
50
+ });
51
+
52
+ // Audit logging
53
+ logger.audit("User login", {
54
+ userId: "usr_123",
55
+ ip: "192.168.1.100",
48
56
  });
49
57
  ```
50
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
+
51
72
  ## Console Formatting 🖥️
52
73
 
53
74
  The console transport supports extensive datetime formatting:
@@ -115,135 +136,66 @@ const logger = new Logger({
115
136
  console.log(ndjsonTransport.getData());
116
137
  ```
117
138
 
118
- ### Loki Transport
139
+ ### Loki Transport (Grafana)
119
140
 
120
141
  ```js
121
142
  import { LokiTransport } from "@rabbit-company/logger";
122
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
+
123
160
  const logger = new Logger({
124
- transports: [
125
- new LokiTransport({
126
- url: "http://localhost:3100",
127
- labels: { app: "my-app", env: "production" },
128
- basicAuth: { username: "user", password: "pass" },
129
- maxLabelCount: 30,
130
- }),
131
- ],
161
+ transports: [lokiTransport],
132
162
  });
133
163
  ```
134
164
 
135
- ## API Reference 📚
136
-
137
- ### Log Levels
165
+ ### Syslog Transport
138
166
 
139
167
  ```js
140
- enum Levels {
141
- ERROR, // Critical errors
142
- WARN, // Warnings
143
- AUDIT, // Security audits
144
- INFO, // Informational
145
- HTTP, // HTTP traffic
146
- DEBUG, // Debugging
147
- VERBOSE, // Detailed tracing
148
- SILLY // Very low-level
149
- }
150
- ```
168
+ import { SyslogTransport } from "@rabbit-company/logger";
169
+
170
+ const syslogTransport = new SyslogTransport({
171
+ host: "syslog.example.com",
172
+ port: 514,
173
+ protocol: "udp", // 'udp', 'tcp', or 'tcp-tls'
174
+ facility: 16, // local0 facility
175
+ appName: "my-app",
176
+ protocolVersion: 5424, // 3164 (BSD) or 5424 (modern)
177
+ tlsOptions: {
178
+ ca: fs.readFileSync("ca.pem"),
179
+ rejectUnauthorized: true,
180
+ },
181
+ maxQueueSize: 2000, // Max queued messages during outages
182
+ debug: true, // Log connection status
183
+ });
151
184
 
152
- ### Logging Methods
185
+ const logger = new Logger({
186
+ transports: [syslogTransport],
187
+ });
153
188
 
154
- ```js
155
- logger.error(message: string, metadata?: object): void
156
- logger.warn(message: string, metadata?: object): void
157
- logger.audit(message: string, metadata?: object): void
158
- logger.info(message: string, metadata?: object): void
159
- logger.http(message: string, metadata?: object): void
160
- logger.verbose(message: string, metadata?: object): void
161
- logger.debug(message: string, metadata?: object): void
162
- logger.silly(message: string, metadata?: object): void
189
+ // Features:
190
+ // - Automatic reconnection with exponential backoff
191
+ // - Message queuing during network issues
192
+ // - Supports UDP, TCP, and TLS encryption
193
+ // - Compliant with RFC 3164 and RFC 5424
163
194
  ```
164
195
 
165
- ### Types
166
-
167
- ```ts
168
- /**
169
- * Represents a single log entry with message, severity level, timestamp, and optional metadata
170
- */
171
- export interface LogEntry {
172
- /** The log message content */
173
- message: string;
174
- /** Severity level of the log entry */
175
- level: Levels;
176
- /** Timestamp in milliseconds since epoch */
177
- timestamp: number;
178
- /** Optional structured metadata associated with the log */
179
- metadata?: Record<string, any>;
180
- }
181
-
182
- /**
183
- * Interface for log transport implementations
184
- */
185
- export interface Transport {
186
- /**
187
- * Processes and outputs a log entry
188
- * @param entry The log entry to process
189
- */
190
- log: (entry: LogEntry) => void;
191
- }
192
-
193
- /**
194
- * Configuration options for the Logger instance
195
- */
196
- export interface LoggerConfig {
197
- /** Minimum log level to output (default: INFO) */
198
- level?: Levels;
199
- /** Enable colored output (default: true) */
200
- colors?: boolean;
201
- /** Format string using {date}, {type}, {message} placeholders (default: "[{date}] {type} {message}") */
202
- format?: string;
203
- /** Array of transports to use (default: [ConsoleTransport]) */
204
- transports?: Transport[];
205
- }
206
-
207
- /**
208
- * Configuration for Loki transport
209
- */
210
- export interface LokiConfig {
211
- /** Loki server URL (e.g., "http://localhost:3100") */
212
- url: string;
213
- /** Base labels to attach to all logs */
214
- labels?: Record<string, string>;
215
- /** Basic authentication credentials */
216
- basicAuth?: {
217
- username: string;
218
- password: string;
219
- };
220
- /** Number of logs to batch before sending (default: 10) */
221
- batchSize?: number;
222
- /** Maximum time in ms to wait before sending a batch (default: 5000) */
223
- batchTimeout?: number;
224
- /** Tenant ID for multi-tenant Loki setups */
225
- tenantID?: string;
226
- /** Maximum number of labels allowed (default: 50) */
227
- maxLabelCount?: number;
228
- /** Enable debug logging for transport errors (default: false) */
229
- debug?: boolean;
230
- }
231
-
232
- /**
233
- * Represents a Loki log stream with labels and log values
234
- */
235
- export interface LokiStream {
236
- /** Key-value pairs of log labels */
237
- stream: {
238
- /** Log level label (required) */
239
- level: string;
240
- /** Additional custom labels */
241
- [key: string]: string;
242
- };
243
- /** Array of log entries with [timestamp, message] pairs */
244
- values: [[string, string]];
245
- }
246
- ```
196
+ ## API Reference 📚
197
+
198
+ Full API documentation is available in the [TypeScript definitions](https://github.com/Rabbit-Company/Logger-JS/blob/main/src/types.ts).
247
199
 
248
200
  ## Advanced Usage 🛠️
249
201
 
@@ -110,6 +110,23 @@ export declare enum Levels {
110
110
  }
111
111
  /**
112
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
+ * }
113
130
  */
114
131
  export interface LogEntry {
115
132
  /** The log message content */
@@ -122,50 +139,168 @@ export interface LogEntry {
122
139
  metadata?: Record<string, any>;
123
140
  }
124
141
  /**
125
- * 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
+ * }
126
152
  */
127
153
  export interface Transport {
128
154
  /**
129
155
  * Processes and outputs a log entry
130
- * @param entry The log entry to process
156
+ * @param {LogEntry} entry - The log entry to process
157
+ * @returns {void}
131
158
  */
132
159
  log: (entry: LogEntry) => void;
133
160
  }
134
161
  /**
135
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
+ * }
136
210
  */
137
211
  export interface LoggerConfig {
138
212
  /** Minimum log level to output (default: INFO) */
139
213
  level?: Levels;
140
214
  /** Enable colored output (default: true) */
141
215
  colors?: boolean;
142
- /** Format string using {date}, {type}, {message} placeholders (default: "[{date}] {type} {message}") */
216
+ /** Format string using placeholders (default: "[{datetime-local}] {type} {message}") */
143
217
  format?: string;
144
218
  /** Array of transports to use (default: [ConsoleTransport]) */
145
219
  transports?: Transport[];
146
220
  }
147
221
  /**
148
- * 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
+ * }
149
244
  */
150
245
  export interface LokiConfig {
151
- /** Loki server URL (e.g., "http://localhost:3100") */
246
+ /**
247
+ * Required Loki server endpoint URL
248
+ * @example "http://loki.example.com"
249
+ */
152
250
  url: string;
153
- /** Base labels to attach to all logs */
251
+ /**
252
+ * Base labels attached to all log entries
253
+ * @example { app: "frontend", env: "production" }
254
+ */
154
255
  labels?: Record<string, string>;
155
256
  /** Basic authentication credentials */
156
257
  basicAuth?: {
258
+ /** Basic auth username */
157
259
  username: string;
260
+ /** Basic auth password */
158
261
  password: string;
159
262
  };
160
- /** Number of logs to batch before sending (default: 10) */
161
- batchSize?: number;
162
- /** Maximum time in ms to wait before sending a batch (default: 5000) */
163
- batchTimeout?: number;
164
- /** 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
+ */
165
267
  tenantID?: string;
166
- /** Maximum number of labels allowed (default: 50) */
268
+ /**
269
+ * Maximum number of labels allowed per log entry
270
+ * @default 50
271
+ */
167
272
  maxLabelCount?: number;
168
- /** 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
+ */
169
304
  debug?: boolean;
170
305
  }
171
306
  /**
@@ -187,6 +322,155 @@ export interface LokiStream {
187
322
  ]
188
323
  ];
189
324
  }
325
+ /**
326
+ * Configuration options for Syslog transport
327
+ * @interface SyslogConfig
328
+ * @description Defines the configuration parameters for establishing a connection
329
+ * to a syslog server and customizing log message formatting.
330
+ *
331
+ * @example
332
+ * // Basic UDP configuration
333
+ * const config: SyslogConfig = {
334
+ * host: 'logs.example.com',
335
+ * port: 514,
336
+ * protocol: 'udp'
337
+ * };
338
+ *
339
+ * @example
340
+ * // Secure TCP with TLS configuration
341
+ * const secureConfig: SyslogConfig = {
342
+ * host: 'secure-logs.example.com',
343
+ * port: 6514,
344
+ * protocol: 'tcp-tls',
345
+ * tlsOptions: {
346
+ * ca: fs.readFileSync('ca.pem'),
347
+ * cert: fs.readFileSync('client-cert.pem'),
348
+ * key: fs.readFileSync('client-key.pem'),
349
+ * rejectUnauthorized: true
350
+ * },
351
+ * appName: 'my-service',
352
+ * facility: 16 // local0
353
+ * };
354
+ */
355
+ export interface SyslogConfig {
356
+ /**
357
+ * Syslog server hostname or IP address
358
+ * @type {string}
359
+ * @default 'localhost'
360
+ * @example 'logs.example.com'
361
+ * @example '192.168.1.100'
362
+ */
363
+ host?: string;
364
+ /**
365
+ * Syslog server port number
366
+ * @type {number}
367
+ * @default 514
368
+ * @example 514 // Standard syslog port
369
+ * @example 6514 // Common port for syslog over TLS
370
+ */
371
+ port?: number;
372
+ /**
373
+ * Network protocol to use for syslog transmission
374
+ * @type {'udp' | 'tcp' | 'tcp-tls'}
375
+ * @default 'udp'
376
+ * @description
377
+ * - 'udp': Unreliable but fast (RFC 3164 compatible)
378
+ * - 'tcp': Reliable connection (RFC 6587)
379
+ * - 'tcp-tls': Encrypted connection (RFC 5425)
380
+ */
381
+ protocol?: "udp" | "tcp" | "tcp-tls";
382
+ /**
383
+ * Syslog facility code
384
+ * @type {number}
385
+ * @range 0-23
386
+ * @default 1 // USER
387
+ * @description
388
+ * Standard syslog facilities:
389
+ * - 0: kern - Kernel messages
390
+ * - 1: user - User-level messages
391
+ * - 2: mail - Mail system
392
+ * - 3: daemon - System daemons
393
+ * - 4: auth - Security/authentication
394
+ * - 5: syslog - Internal syslog messages
395
+ * - 6: lpr - Line printer subsystem
396
+ * - 7: news - Network news subsystem
397
+ * - 8: uucp - UUCP subsystem
398
+ * - 9: cron - Clock daemon
399
+ * - 10: authpriv - Security/authentication
400
+ * - 11: ftp - FTP daemon
401
+ * - 16-23: local0-local7 - Locally used facilities
402
+ */
403
+ facility?: number;
404
+ /**
405
+ * Application name identifier included in syslog messages
406
+ * @type {string}
407
+ * @default 'node'
408
+ * @description
409
+ * Should be a short string (typically <= 32 chars) identifying the application.
410
+ * @example 'auth-service'
411
+ * @example 'payment-processor'
412
+ */
413
+ appName?: string;
414
+ /**
415
+ * Process ID included in syslog messages
416
+ * @type {number}
417
+ * @default process.pid
418
+ * @description
419
+ * Used to identify the specific process generating the log message.
420
+ */
421
+ pid?: number;
422
+ /**
423
+ * Syslog protocol version specification
424
+ * @type {3164 | 5424}
425
+ * @default 5424
426
+ * @description
427
+ * - 3164: Traditional BSD syslog format (RFC 3164)
428
+ * - 5424: Modern structured syslog format (RFC 5424)
429
+ */
430
+ protocolVersion?: 3164 | 5424;
431
+ /**
432
+ * TLS configuration options for secure connections
433
+ * @type {Object}
434
+ * @description Required when protocol is 'tcp-tls'
435
+ * @property {string} [ca] - PEM encoded CA certificate
436
+ * @property {string} [cert] - PEM encoded client certificate
437
+ * @property {string} [key] - PEM encoded client private key
438
+ * @property {boolean} [rejectUnauthorized=true] - Verify server certificate
439
+ */
440
+ tlsOptions?: {
441
+ /** CA certificate */
442
+ ca?: string;
443
+ /** Client certificate */
444
+ cert?: string;
445
+ /** Client private key */
446
+ key?: string;
447
+ /** Whether to reject unauthorized certificates (default: true) */
448
+ rejectUnauthorized?: boolean;
449
+ };
450
+ /**
451
+ * Maximum number of log messages to buffer in memory
452
+ * @type {number}
453
+ * @default 1000
454
+ * @description
455
+ * When the queue reaches this size, oldest messages will be dropped.
456
+ * Set to 0 for unlimited (not recommended in production).
457
+ */
458
+ maxQueueSize?: number;
459
+ /**
460
+ * Initial retry delay in milliseconds (exponential backoff base)
461
+ * @description Delay doubles with each retry up to maximum 30s
462
+ * @default 1000
463
+ */
464
+ retryBaseDelay?: number;
465
+ /**
466
+ * Enable debug output for transport operations
467
+ * @type {boolean}
468
+ * @default false
469
+ * @description
470
+ * When true, outputs connection status and error details to console.
471
+ */
472
+ debug?: boolean;
473
+ }
190
474
  /**
191
475
  * Main Logger class that handles all logging functionality.
192
476
  *
@@ -501,84 +785,237 @@ export declare class NDJsonTransport implements Transport {
501
785
  reset(): void;
502
786
  }
503
787
  /**
504
- * Transport that sends logs to a Grafana Loki server with batching and retry support.
788
+ * High-reliability transport for sending logs to Grafana Loki with persistent queuing.
505
789
  *
506
790
  * Features:
507
- * - Automatic batching of logs for efficient transmission
508
- * - Configurable batch size and timeout
791
+ * - Persistent in-memory queue with configurable size limits
792
+ * - Exponential backoff retry mechanism with configurable limits
793
+ * - Automatic batching for efficient network utilization
509
794
  * - Label management with cardinality control
510
795
  * - Multi-tenancy support via X-Scope-OrgID
511
- * - Basic authentication support
796
+ * - Comprehensive error handling and recovery
512
797
  *
513
- * @example
514
- * // Basic configuration
798
+ * @implements {Transport}
799
+ *
800
+ * @example <caption>Basic Configuration</caption>
515
801
  * const lokiTransport = new LokiTransport({
516
802
  * url: "http://localhost:3100",
517
803
  * labels: { app: "my-app", env: "production" }
518
804
  * });
519
805
  *
520
- * @example
521
- * // With authentication and custom batching
522
- * const securedTransport = new LokiTransport({
806
+ * @example <caption>Advanced Configuration</caption>
807
+ * const transport = new LokiTransport({
523
808
  * url: "http://loki.example.com",
809
+ * batchSize: 50,
810
+ * batchTimeout: 2000,
811
+ * maxQueueSize: 50000,
812
+ * maxRetries: 10,
813
+ * retryBaseDelay: 2000,
814
+ * tenantID: "team-a",
524
815
  * basicAuth: { username: "user", password: "pass" },
525
- * batchSize: 20,
526
- * batchTimeout: 10000 // 10 seconds
816
+ * debug: true
527
817
  * });
528
818
  */
529
819
  export declare class LokiTransport implements Transport {
530
820
  private config;
531
- private batch;
821
+ /** @private Internal log queue */
822
+ private queue;
823
+ /** @private Current batch size setting */
532
824
  private batchSize;
825
+ /** @private Current batch timeout setting (ms) */
533
826
  private batchTimeout;
827
+ /** @private Handle for batch timeout */
534
828
  private timeoutHandle?;
829
+ /** @private Maximum allowed labels per entry */
535
830
  private maxLabelCount;
831
+ /** @private Debug mode flag */
536
832
  private debug;
833
+ /** @private Maximum queue size before dropping logs */
834
+ private maxQueueSize;
835
+ /** @private Current retry attempt count */
836
+ private retryCount;
837
+ /** @private Maximum allowed retry attempts */
838
+ private maxRetries;
839
+ /** @private Base delay for exponential backoff (ms) */
840
+ private retryBaseDelay;
841
+ /** @private Handle for retry timeout */
842
+ private retryTimer?;
843
+ /** @private Flag indicating active send operation */
844
+ private isSending;
537
845
  /**
538
846
  * Creates a new LokiTransport instance
539
- * @param config Configuration options for Loki
540
- * @param config.url Required Loki server URL (e.g., "http://localhost:3100")
541
- * @param config.labels Base labels to attach to all log entries
542
- * @param config.basicAuth Basic authentication credentials
543
- * @param config.batchSize Maximum number of logs to batch before sending (default: 10)
544
- * @param config.batchTimeout Maximum time (ms) to wait before sending a batch (default: 5000)
545
- * @param config.tenantID Tenant ID for multi-tenant Loki setups
546
- * @param config.maxLabelCount Maximum number of labels allowed (default: 50)
547
- * @param config.debug Enable debug logging for transport errors (default: false)
548
- * @throws {Error} If URL is not provided
847
+ * @param {LokiConfig} config - Configuration options
848
+ * @param {string} config.url - Required Loki server endpoint (e.g., "http://localhost:3100")
849
+ * @param {Object.<string, string>} [config.labels] - Base labels attached to all log entries
850
+ * @param {Object} [config.basicAuth] - Basic authentication credentials
851
+ * @param {string} config.basicAuth.username - Username for basic auth
852
+ * @param {string} config.basicAuth.password - Password for basic auth
853
+ * @param {string} [config.tenantID] - Tenant ID for multi-tenant Loki installations
854
+ * @param {number} [config.batchSize=10] - Number of logs to accumulate before automatic sending
855
+ * @param {number} [config.batchTimeout=5000] - Maximum time (ms) to wait before sending incomplete batch
856
+ * @param {number} [config.maxLabelCount=50] - Maximum number of labels allowed per log entry
857
+ * @param {number} [config.maxQueueSize=10000] - Maximum number of logs to buffer in memory during outages
858
+ * @param {number} [config.maxRetries=5] - Maximum number of attempts to send a failed batch
859
+ * @param {number} [config.retryBaseDelay=1000] - Initial retry delay in ms (exponential backoff base)
860
+ * @param {boolean} [config.debug=false] - Enable debug logging for transport internals
549
861
  */
550
862
  constructor(config: LokiConfig);
551
863
  /**
552
- * Adds a log entry to the current batch. Automatically sends the batch when:
553
- * - The batch reaches the configured size, OR
554
- * - The batch timeout is reached
864
+ * Queues a log entry for delivery to Loki
555
865
  *
556
- * @param entry The log entry to send. Metadata will be converted to Loki labels
557
- * following the configured maxLabelCount rules.
866
+ * @param {LogEntry} entry - The log entry to process
867
+ * @param {string} entry.message - Primary log message content
868
+ * @param {string} entry.level - Log severity level (e.g., "INFO", "ERROR")
869
+ * @param {number} entry.timestamp - Unix timestamp in milliseconds
870
+ * @param {Object} [entry.metadata] - Additional log metadata (will be converted to Loki labels)
558
871
  *
559
872
  * @example
560
873
  * transport.log({
561
- * message: "User logged in",
562
- * level: Levels.INFO,
874
+ * message: "User login successful",
875
+ * level: "INFO",
563
876
  * timestamp: Date.now(),
564
- * metadata: { userId: "123", device: "mobile" }
877
+ * metadata: {
878
+ * userId: "12345",
879
+ * sourceIP: "192.168.1.100",
880
+ * device: "mobile"
881
+ * }
565
882
  * });
566
883
  */
567
884
  log(entry: LogEntry): void;
568
885
  /**
569
- * Immediately sends the current batch of logs to Loki.
886
+ * Schedules the next batch send operation
570
887
  * @private
888
+ * @param {boolean} [immediate=false] - Whether to send immediately without waiting for timeout
889
+ */
890
+ private scheduleSend;
891
+ /**
892
+ * Sends the current batch to Loki with retry logic
893
+ * @private
894
+ * @async
895
+ * @returns {Promise<void>}
571
896
  *
572
- * Handles:
573
- * - HTTP headers including auth and tenant ID
574
- * - Batch timeout clearing
575
- * - Error logging (when debug enabled)
576
- * - Batch management
577
- *
578
- * Note: This method is called automatically by the transport
579
- * and typically doesn't need to be called directly.
897
+ * @description
898
+ * Handles the complete send operation including:
899
+ * - Preparing HTTP request with proper headers
900
+ * - Executing the fetch request
901
+ * - Managing retries with exponential backoff
902
+ * - Queue cleanup on success/failure
903
+ * - Automatic scheduling of next batch
580
904
  */
581
905
  private sendBatch;
582
906
  }
907
+ /**
908
+ * Syslog transport implementation for the logger library
909
+ * @class SyslogTransport
910
+ * @implements {Transport}
911
+ * @description A robust syslog client that supports UDP, TCP, and TLS-encrypted TCP connections
912
+ * with automatic reconnection and message queuing capabilities.
913
+ *
914
+ * @example
915
+ * // Basic UDP configuration
916
+ * const transport = new SyslogTransport({
917
+ * host: 'logs.example.com',
918
+ * port: 514,
919
+ * protocol: 'udp'
920
+ * });
921
+ *
922
+ * @example
923
+ * // Secure TLS configuration
924
+ * const secureTransport = new SyslogTransport({
925
+ * host: 'secure-logs.example.com',
926
+ * port: 6514,
927
+ * protocol: 'tcp-tls',
928
+ * tlsOptions: {
929
+ * ca: fs.readFileSync('ca.pem'),
930
+ * rejectUnauthorized: true
931
+ * },
932
+ * maxQueueSize: 5000
933
+ * });
934
+ */
935
+ export declare class SyslogTransport implements Transport {
936
+ private socket;
937
+ private queue;
938
+ private isConnecting;
939
+ private retryCount;
940
+ private retryBaseDelay;
941
+ private maxQueueSize;
942
+ private debug;
943
+ private reconnectTimer;
944
+ private config;
945
+ /**
946
+ * Creates a new SyslogTransport instance
947
+ * @constructor
948
+ * @param {SyslogConfig} [config={}] - Configuration options for the transport
949
+ */
950
+ constructor(config?: SyslogConfig);
951
+ /**
952
+ * Initializes the appropriate socket based on configured protocol
953
+ * @private
954
+ * @returns {void}
955
+ */
956
+ private initializeSocket;
957
+ /**
958
+ * Initializes a UDP socket for syslog transmission
959
+ * @private
960
+ * @returns {void}
961
+ */
962
+ private initializeUdpSocket;
963
+ /**
964
+ * Initializes a TCP socket for syslog transmission
965
+ * @private
966
+ * @returns {void}
967
+ */
968
+ private initializeTcpSocket;
969
+ /**
970
+ * Initializes a TLS-secured TCP socket for syslog transmission
971
+ * @private
972
+ * @returns {void}
973
+ */
974
+ private initializeTlsSocket;
975
+ /**
976
+ * Sets up common event handlers for TCP/TLS sockets
977
+ * @private
978
+ * @returns {void}
979
+ */
980
+ private setupTcpSocketEvents;
981
+ /**
982
+ * Establishes a TCP connection to the syslog server
983
+ * @private
984
+ * @returns {void}
985
+ */
986
+ private connectTcpSocket;
987
+ /**
988
+ * Handles socket errors and initiates reconnection if needed
989
+ * @private
990
+ * @returns {void}
991
+ */
992
+ private handleSocketError;
993
+ /**
994
+ * Sends all queued messages to the syslog server
995
+ * @private
996
+ * @returns {void}
997
+ */
998
+ private flushQueue;
999
+ /**
1000
+ * Sends a single message to the syslog server
1001
+ * @private
1002
+ * @param {string} message - The formatted syslog message to send
1003
+ * @returns {void}
1004
+ */
1005
+ private sendMessage;
1006
+ /**
1007
+ * Processes a log entry by formatting and queueing it for transmission
1008
+ * @public
1009
+ * @param {LogEntry} entry - The log entry to process
1010
+ * @returns {void}
1011
+ */
1012
+ log(entry: LogEntry): void;
1013
+ /**
1014
+ * Gracefully closes the transport connection
1015
+ * @public
1016
+ * @returns {Promise<void>} A promise that resolves when the connection is closed
1017
+ */
1018
+ close(): Promise<void>;
1019
+ }
583
1020
 
584
1021
  export {};
package/module/logger.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
1
4
  // src/constants/colors.ts
2
5
  var Colors;
3
6
  ((Colors2) => {
@@ -219,40 +222,59 @@ function formatLokiMessage(entry, maxLabelCount, labels = {}) {
219
222
  // src/transports/lokiTransport.ts
220
223
  class LokiTransport {
221
224
  config;
222
- batch = [];
225
+ queue = [];
223
226
  batchSize;
224
227
  batchTimeout;
225
228
  timeoutHandle;
226
229
  maxLabelCount;
227
230
  debug;
231
+ maxQueueSize;
232
+ retryCount = 0;
233
+ maxRetries;
234
+ retryBaseDelay;
235
+ retryTimer;
236
+ isSending = false;
228
237
  constructor(config) {
229
238
  this.config = config;
230
239
  this.batchSize = config.batchSize || 10;
231
240
  this.batchTimeout = config.batchTimeout || 5000;
232
241
  this.maxLabelCount = config.maxLabelCount || 50;
233
242
  this.debug = config.debug || false;
234
- if (!config.url) {
235
- throw new Error("Loki URL is required");
236
- }
243
+ this.maxQueueSize = config.maxQueueSize || 1e4;
244
+ this.maxRetries = config.maxRetries || 5;
245
+ this.retryBaseDelay = config.retryBaseDelay || 1000;
237
246
  }
238
247
  log(entry) {
239
- const lokiMessage = formatLokiMessage(entry, this.maxLabelCount, { ...this.config.labels, ...entry.metadata });
240
- this.batch.push(lokiMessage);
241
- if (this.batch.length >= this.batchSize) {
242
- this.sendBatch();
243
- } else if (!this.timeoutHandle) {
244
- this.timeoutHandle = setTimeout(() => this.sendBatch(), this.batchTimeout);
248
+ const lokiMessage = formatLokiMessage(entry, this.maxLabelCount, {
249
+ ...this.config.labels,
250
+ ...entry.metadata
251
+ });
252
+ if (this.queue.length >= this.maxQueueSize) {
253
+ if (this.debug)
254
+ console.warn("Loki queue full - dropping oldest log entry");
255
+ this.queue.shift();
245
256
  }
257
+ this.queue.push(lokiMessage);
258
+ this.scheduleSend();
246
259
  }
247
- async sendBatch() {
260
+ scheduleSend(immediate = false) {
261
+ if (this.isSending)
262
+ return;
248
263
  if (this.timeoutHandle) {
249
264
  clearTimeout(this.timeoutHandle);
250
265
  this.timeoutHandle = undefined;
251
266
  }
252
- if (this.batch.length === 0)
267
+ if (this.queue.length > 0 && (immediate || this.queue.length >= this.batchSize)) {
268
+ this.sendBatch();
269
+ } else if (this.queue.length > 0) {
270
+ this.timeoutHandle = setTimeout(() => this.sendBatch(), this.batchTimeout);
271
+ }
272
+ }
273
+ async sendBatch() {
274
+ if (this.queue.length === 0 || this.isSending)
253
275
  return;
254
- const batchToSend = this.batch;
255
- this.batch = [];
276
+ this.isSending = true;
277
+ const batchToSend = this.queue.slice(0, this.batchSize);
256
278
  try {
257
279
  const headers = {
258
280
  "Content-Type": "application/json"
@@ -270,16 +292,280 @@ class LokiTransport {
270
292
  streams: batchToSend.flatMap((entry) => entry.streams)
271
293
  })
272
294
  });
273
- if (!response.ok && this.debug) {
274
- console.error("Failed to send logs to Loki: ", await response.text());
295
+ if (response.ok) {
296
+ this.queue = this.queue.slice(batchToSend.length);
297
+ this.retryCount = 0;
298
+ if (this.retryTimer) {
299
+ clearTimeout(this.retryTimer);
300
+ this.retryTimer = undefined;
301
+ }
302
+ } else {
303
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
275
304
  }
276
305
  } catch (error) {
277
306
  if (this.debug)
278
- console.error("Error sending logs to Loki: ", error);
307
+ console.error("Loki transmission error: ", error);
308
+ this.retryCount++;
309
+ if (this.retryCount <= this.maxRetries) {
310
+ const delay = Math.min(this.retryBaseDelay * Math.pow(2, this.retryCount - 1), 30000);
311
+ if (this.debug)
312
+ console.log(`Scheduling retry #${this.retryCount} in ${delay}ms`);
313
+ this.retryTimer = setTimeout(() => {
314
+ this.scheduleSend(true);
315
+ }, delay);
316
+ } else {
317
+ if (this.debug)
318
+ console.warn(`Max retries (${this.maxRetries}) reached. Dropping batch.`);
319
+ this.queue = this.queue.slice(batchToSend.length);
320
+ this.retryCount = 0;
321
+ }
322
+ } finally {
323
+ this.isSending = false;
324
+ if (this.queue.length > 0 && this.retryCount === 0) {
325
+ this.scheduleSend();
326
+ }
327
+ }
328
+ }
329
+ }
330
+ // src/formatters/syslogFormatter.ts
331
+ var SYSLOG_SEVERITY = {
332
+ [0 /* ERROR */]: 3,
333
+ [1 /* WARN */]: 4,
334
+ [2 /* AUDIT */]: 5,
335
+ [3 /* INFO */]: 6,
336
+ [4 /* HTTP */]: 6,
337
+ [5 /* DEBUG */]: 7,
338
+ [6 /* VERBOSE */]: 7,
339
+ [7 /* SILLY */]: 7
340
+ };
341
+ function formatRFC3164(entry, facility, appName, pid) {
342
+ const severity = SYSLOG_SEVERITY[entry.level];
343
+ const priority = facility << 3 | severity;
344
+ const timestamp = new Date(entry.timestamp).toLocaleString("en-US", {
345
+ month: "short",
346
+ day: "2-digit",
347
+ hour: "2-digit",
348
+ minute: "2-digit",
349
+ second: "2-digit",
350
+ hour12: false
351
+ }).replace(/,/, "").replace(" at ", " ");
352
+ const hostname = __require("os").hostname();
353
+ const msg = entry.metadata ? `${entry.message} ${JSON.stringify(entry.metadata)}` : entry.message;
354
+ return `<${priority}>${timestamp} ${hostname} ${appName}[${pid}]: ${msg}`;
355
+ }
356
+ function formatRFC5424(entry, facility, appName, pid) {
357
+ const severity = SYSLOG_SEVERITY[entry.level];
358
+ const priority = facility << 3 | severity;
359
+ const timestamp = new Date(entry.timestamp).toISOString();
360
+ const hostname = __require("os").hostname();
361
+ const msgId = "-";
362
+ const structuredData = entry.metadata ? `[example@1 ${Object.entries(entry.metadata).map(([key, val]) => `${key}="${val}"`).join(" ")}]` : "-";
363
+ return `<${priority}>1 ${timestamp} ${hostname} ${appName} ${pid} ${msgId} ${structuredData} ${entry.message}`;
364
+ }
365
+ function formatSyslogMessage(entry, config) {
366
+ const facility = config.facility ?? 1;
367
+ const appName = config.appName ?? "node";
368
+ const pid = config.pid ?? process.pid;
369
+ const protocolVersion = config.protocolVersion ?? 5424;
370
+ return protocolVersion === 3164 ? formatRFC3164(entry, facility, appName, pid) : formatRFC5424(entry, facility, appName, pid);
371
+ }
372
+
373
+ // src/transports/syslogTransport.ts
374
+ import { createSocket, Socket } from "dgram";
375
+ import { Socket as NetSocket } from "net";
376
+ import { connect as tlsConnect } from "tls";
377
+
378
+ class SyslogTransport {
379
+ socket = null;
380
+ queue = [];
381
+ isConnecting = false;
382
+ retryCount = 0;
383
+ retryBaseDelay;
384
+ maxQueueSize;
385
+ debug;
386
+ reconnectTimer = null;
387
+ config;
388
+ constructor(config = {}) {
389
+ this.maxQueueSize = config.maxQueueSize ?? 1000;
390
+ this.retryBaseDelay = config.retryBaseDelay ?? 1000;
391
+ this.debug = config.debug ?? false;
392
+ this.config = {
393
+ host: config.host ?? "localhost",
394
+ port: config.port ?? 514,
395
+ protocol: config.protocol ?? "udp",
396
+ facility: config.facility ?? 1,
397
+ appName: config.appName ?? "node",
398
+ pid: config.pid ?? process.pid,
399
+ protocolVersion: config.protocolVersion ?? 5424,
400
+ tlsOptions: config.tlsOptions || {}
401
+ };
402
+ this.initializeSocket();
403
+ }
404
+ initializeSocket() {
405
+ if (this.reconnectTimer) {
406
+ clearTimeout(this.reconnectTimer);
407
+ this.reconnectTimer = null;
408
+ }
409
+ if (this.socket) {
410
+ this.socket.removeAllListeners();
411
+ if (!("destroy" in this.socket)) {
412
+ this.socket.close();
413
+ } else {
414
+ this.socket.destroy();
415
+ }
416
+ }
417
+ if (this.config.protocol === "udp") {
418
+ this.initializeUdpSocket();
419
+ } else if (this.config.protocol === "tcp") {
420
+ this.initializeTcpSocket();
421
+ } else if (this.config.protocol === "tcp-tls") {
422
+ this.initializeTlsSocket();
423
+ }
424
+ }
425
+ initializeUdpSocket() {
426
+ this.socket = createSocket("udp4");
427
+ this.socket.on("error", (err) => {
428
+ if (this.debug)
429
+ console.error("Syslog UDP error:", err);
430
+ this.handleSocketError();
431
+ });
432
+ this.socket.on("close", () => {
433
+ if (this.debug)
434
+ console.log("Syslog UDP socket closed");
435
+ });
436
+ }
437
+ initializeTcpSocket() {
438
+ this.socket = new NetSocket;
439
+ this.setupTcpSocketEvents();
440
+ this.connectTcpSocket();
441
+ }
442
+ initializeTlsSocket() {
443
+ const tlsOptions = {
444
+ host: this.config.host,
445
+ port: this.config.port,
446
+ ...this.config.tlsOptions
447
+ };
448
+ this.socket = tlsConnect(tlsOptions, () => {
449
+ if (this.debug)
450
+ console.log("Syslog TLS connection established");
451
+ this.retryCount = 0;
452
+ this.flushQueue();
453
+ });
454
+ this.setupTcpSocketEvents();
455
+ }
456
+ setupTcpSocketEvents() {
457
+ if (!this.socket)
458
+ return;
459
+ this.socket.on("error", (err) => {
460
+ if (this.debug)
461
+ console.error("Syslog TCP/TLS error:", err);
462
+ this.handleSocketError();
463
+ });
464
+ this.socket.on("close", () => {
465
+ if (this.debug)
466
+ console.log("Syslog TCP/TLS connection closed");
467
+ this.handleSocketError();
468
+ });
469
+ this.socket.on("end", () => {
470
+ if (this.debug)
471
+ console.log("Syslog TCP/TLS connection ended");
472
+ });
473
+ }
474
+ connectTcpSocket() {
475
+ if (this.isConnecting || !(this.socket instanceof NetSocket))
476
+ return;
477
+ this.isConnecting = true;
478
+ this.socket.connect(this.config.port, this.config.host, () => {
479
+ if (this.debug)
480
+ console.log("Syslog TCP connection established");
481
+ this.isConnecting = false;
482
+ this.retryCount = 0;
483
+ this.flushQueue();
484
+ });
485
+ }
486
+ handleSocketError() {
487
+ if (this.reconnectTimer)
488
+ return;
489
+ this.socket = null;
490
+ this.isConnecting = false;
491
+ this.retryCount++;
492
+ const delay = Math.min(this.retryBaseDelay * Math.pow(2, this.retryCount - 1), 30000);
493
+ if (this.debug)
494
+ console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.retryCount})`);
495
+ this.reconnectTimer = setTimeout(() => {
496
+ this.initializeSocket();
497
+ }, delay);
498
+ }
499
+ flushQueue() {
500
+ if (!this.socket || this.queue.length === 0)
501
+ return;
502
+ while (this.queue.length > 0) {
503
+ const message = this.queue.shift();
504
+ if (message) {
505
+ this.sendMessage(message);
506
+ }
507
+ }
508
+ }
509
+ sendMessage(message) {
510
+ if (!this.socket) {
511
+ this.queue.unshift(message);
512
+ return;
513
+ }
514
+ try {
515
+ if (this.socket instanceof Socket) {
516
+ this.socket.send(message, this.config.port, this.config.host, (err) => {
517
+ if (err && this.debug)
518
+ console.error("Syslog UDP send error:", err);
519
+ });
520
+ } else {
521
+ this.socket.write(message + `
522
+ `, (err) => {
523
+ if (err && this.debug)
524
+ console.error("Syslog TCP/TLS send error:", err);
525
+ });
526
+ }
527
+ } catch (err) {
528
+ if (this.debug)
529
+ console.error("Syslog send error:", err);
530
+ this.queue.unshift(message);
279
531
  }
280
532
  }
533
+ log(entry) {
534
+ const message = formatSyslogMessage(entry, this.config);
535
+ if (this.queue.length >= this.maxQueueSize) {
536
+ if (this.debug)
537
+ console.warn("Syslog queue full - dropping oldest message");
538
+ this.queue.shift();
539
+ }
540
+ this.queue.push(message);
541
+ if (this.socket && !this.isConnecting) {
542
+ this.flushQueue();
543
+ } else if (!this.socket && !this.isConnecting) {}
544
+ }
545
+ close() {
546
+ return new Promise((resolve) => {
547
+ if (this.reconnectTimer) {
548
+ clearTimeout(this.reconnectTimer);
549
+ this.reconnectTimer = null;
550
+ }
551
+ if (!this.socket) {
552
+ resolve();
553
+ return;
554
+ }
555
+ const handleClose = () => {
556
+ resolve();
557
+ };
558
+ if ("destroy" in this.socket) {
559
+ this.socket.destroy();
560
+ process.nextTick(handleClose);
561
+ } else {
562
+ this.socket.close(handleClose);
563
+ }
564
+ });
565
+ }
281
566
  }
282
567
  export {
568
+ SyslogTransport,
283
569
  NDJsonTransport,
284
570
  LokiTransport,
285
571
  Logger,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@rabbit-company/logger",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "description": "A simple and lightweight logger",
5
- "main": "./module/index.js",
5
+ "main": "./module/logger.js",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/Rabbit-Company/Logger-JS",
8
8
  "funding": "https://rabbit-company.com/donation",
@@ -35,9 +35,8 @@
35
35
  "loki"
36
36
  ],
37
37
  "devDependencies": {
38
- "@types/bun": "^1.2.10",
39
- "bun-plugin-dts": "^0.3.0",
40
- "@rabbit-company/logger": "^4.0.0"
38
+ "@types/bun": "latest",
39
+ "bun-plugin-dts": "^0.3.0"
41
40
  },
42
41
  "peerDependencies": {
43
42
  "typescript": "^5.5.4"