@rabbit-company/logger 5.1.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, 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, 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,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,35 @@ 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
165
  ## API Reference 📚
136
166
 
137
- ### Log Levels
138
-
139
- ```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
- ```
151
-
152
- ### Logging Methods
153
-
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
163
- ```
164
-
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
- ```
167
+ Full API documentation is available in the [TypeScript definitions](https://github.com/Rabbit-Company/Logger-JS/blob/main/src/types.ts).
247
168
 
248
169
  ## Advanced Usage 🛠️
249
170
 
@@ -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
  /**
@@ -501,82 +636,122 @@ export declare class NDJsonTransport implements Transport {
501
636
  reset(): void;
502
637
  }
503
638
  /**
504
- * Transport that sends logs to a Grafana Loki server with batching and retry support.
639
+ * High-reliability transport for sending logs to Grafana Loki with persistent queuing.
505
640
  *
506
641
  * Features:
507
- * - Automatic batching of logs for efficient transmission
508
- * - Configurable batch size and timeout
642
+ * - Persistent in-memory queue with configurable size limits
643
+ * - Exponential backoff retry mechanism with configurable limits
644
+ * - Automatic batching for efficient network utilization
509
645
  * - Label management with cardinality control
510
646
  * - Multi-tenancy support via X-Scope-OrgID
511
- * - Basic authentication support
647
+ * - Comprehensive error handling and recovery
512
648
  *
513
- * @example
514
- * // Basic configuration
649
+ * @implements {Transport}
650
+ *
651
+ * @example <caption>Basic Configuration</caption>
515
652
  * const lokiTransport = new LokiTransport({
516
653
  * url: "http://localhost:3100",
517
654
  * labels: { app: "my-app", env: "production" }
518
655
  * });
519
656
  *
520
- * @example
521
- * // With authentication and custom batching
522
- * const securedTransport = new LokiTransport({
657
+ * @example <caption>Advanced Configuration</caption>
658
+ * const transport = new LokiTransport({
523
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",
524
666
  * basicAuth: { username: "user", password: "pass" },
525
- * batchSize: 20,
526
- * batchTimeout: 10000 // 10 seconds
667
+ * debug: true
527
668
  * });
528
669
  */
529
670
  export declare class LokiTransport implements Transport {
530
671
  private config;
531
- private batch;
672
+ /** @private Internal log queue */
673
+ private queue;
674
+ /** @private Current batch size setting */
532
675
  private batchSize;
676
+ /** @private Current batch timeout setting (ms) */
533
677
  private batchTimeout;
678
+ /** @private Handle for batch timeout */
534
679
  private timeoutHandle?;
680
+ /** @private Maximum allowed labels per entry */
535
681
  private maxLabelCount;
682
+ /** @private Debug mode flag */
536
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;
537
696
  /**
538
697
  * 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
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
549
712
  */
550
713
  constructor(config: LokiConfig);
551
714
  /**
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
715
+ * Queues a log entry for delivery to Loki
555
716
  *
556
- * @param entry The log entry to send. Metadata will be converted to Loki labels
557
- * following the configured maxLabelCount rules.
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)
558
722
  *
559
723
  * @example
560
724
  * transport.log({
561
- * message: "User logged in",
562
- * level: Levels.INFO,
725
+ * message: "User login successful",
726
+ * level: "INFO",
563
727
  * timestamp: Date.now(),
564
- * metadata: { userId: "123", device: "mobile" }
728
+ * metadata: {
729
+ * userId: "12345",
730
+ * sourceIP: "192.168.1.100",
731
+ * device: "mobile"
732
+ * }
565
733
  * });
566
734
  */
567
735
  log(entry: LogEntry): void;
568
736
  /**
569
- * Immediately sends the current batch of logs to Loki.
737
+ * Schedules the next batch send operation
570
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
744
+ * @private
745
+ * @async
746
+ * @returns {Promise<void>}
571
747
  *
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.
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
580
755
  */
581
756
  private sendBatch;
582
757
  }
package/module/logger.js CHANGED
@@ -219,40 +219,59 @@ function formatLokiMessage(entry, maxLabelCount, labels = {}) {
219
219
  // src/transports/lokiTransport.ts
220
220
  class LokiTransport {
221
221
  config;
222
- batch = [];
222
+ queue = [];
223
223
  batchSize;
224
224
  batchTimeout;
225
225
  timeoutHandle;
226
226
  maxLabelCount;
227
227
  debug;
228
+ maxQueueSize;
229
+ retryCount = 0;
230
+ maxRetries;
231
+ retryBaseDelay;
232
+ retryTimer;
233
+ isSending = false;
228
234
  constructor(config) {
229
235
  this.config = config;
230
236
  this.batchSize = config.batchSize || 10;
231
237
  this.batchTimeout = config.batchTimeout || 5000;
232
238
  this.maxLabelCount = config.maxLabelCount || 50;
233
239
  this.debug = config.debug || false;
234
- if (!config.url) {
235
- throw new Error("Loki URL is required");
236
- }
240
+ this.maxQueueSize = config.maxQueueSize || 1e4;
241
+ this.maxRetries = config.maxRetries || 5;
242
+ this.retryBaseDelay = config.retryBaseDelay || 1000;
237
243
  }
238
244
  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);
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();
245
253
  }
254
+ this.queue.push(lokiMessage);
255
+ this.scheduleSend();
246
256
  }
247
- async sendBatch() {
257
+ scheduleSend(immediate = false) {
258
+ if (this.isSending)
259
+ return;
248
260
  if (this.timeoutHandle) {
249
261
  clearTimeout(this.timeoutHandle);
250
262
  this.timeoutHandle = undefined;
251
263
  }
252
- 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)
253
272
  return;
254
- const batchToSend = this.batch;
255
- this.batch = [];
273
+ this.isSending = true;
274
+ const batchToSend = this.queue.slice(0, this.batchSize);
256
275
  try {
257
276
  const headers = {
258
277
  "Content-Type": "application/json"
@@ -270,12 +289,38 @@ class LokiTransport {
270
289
  streams: batchToSend.flatMap((entry) => entry.streams)
271
290
  })
272
291
  });
273
- if (!response.ok && this.debug) {
274
- 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()}`);
275
301
  }
276
302
  } catch (error) {
277
303
  if (this.debug)
278
- 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
+ }
279
324
  }
280
325
  }
281
326
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabbit-company/logger",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "A simple and lightweight logger",
5
5
  "main": "./module/index.js",
6
6
  "type": "module",