@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 +81 -129
- package/module/logger.d.ts +489 -52
- package/module/logger.js +303 -17
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -4,20 +4,17 @@
|
|
|
4
4
|
[](https://jsr.io/@rabbit-company/logger)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
A
|
|
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
|
-
- **
|
|
16
|
-
- **Structured logging**: Attach metadata
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **Cross-platform**: Works in Node.js, Deno
|
|
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({
|
|
38
|
+
const logger = new Logger({
|
|
39
|
+
level: Levels.DEBUG, // Show DEBUG and above
|
|
40
|
+
});
|
|
42
41
|
|
|
43
|
-
//
|
|
44
|
-
logger.info("Application
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
### Log Levels
|
|
165
|
+
### Syslog Transport
|
|
138
166
|
|
|
139
167
|
```js
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
185
|
+
const logger = new Logger({
|
|
186
|
+
transports: [syslogTransport],
|
|
187
|
+
});
|
|
153
188
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
package/module/logger.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
/**
|
|
246
|
+
/**
|
|
247
|
+
* Required Loki server endpoint URL
|
|
248
|
+
* @example "http://loki.example.com"
|
|
249
|
+
*/
|
|
152
250
|
url: string;
|
|
153
|
-
/**
|
|
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
|
-
/**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
/**
|
|
268
|
+
/**
|
|
269
|
+
* Maximum number of labels allowed per log entry
|
|
270
|
+
* @default 50
|
|
271
|
+
*/
|
|
167
272
|
maxLabelCount?: number;
|
|
168
|
-
/**
|
|
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
|
-
*
|
|
788
|
+
* High-reliability transport for sending logs to Grafana Loki with persistent queuing.
|
|
505
789
|
*
|
|
506
790
|
* Features:
|
|
507
|
-
* -
|
|
508
|
-
* -
|
|
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
|
-
* -
|
|
796
|
+
* - Comprehensive error handling and recovery
|
|
512
797
|
*
|
|
513
|
-
* @
|
|
514
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
540
|
-
* @param config.url Required Loki server
|
|
541
|
-
* @param config.labels Base labels
|
|
542
|
-
* @param config.basicAuth Basic authentication credentials
|
|
543
|
-
* @param config.
|
|
544
|
-
* @param config.
|
|
545
|
-
* @param config.tenantID Tenant ID for multi-tenant Loki
|
|
546
|
-
* @param config.
|
|
547
|
-
* @param config.
|
|
548
|
-
* @
|
|
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
|
-
*
|
|
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
|
|
557
|
-
*
|
|
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
|
|
562
|
-
* level:
|
|
874
|
+
* message: "User login successful",
|
|
875
|
+
* level: "INFO",
|
|
563
876
|
* timestamp: Date.now(),
|
|
564
|
-
* metadata: {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
573
|
-
*
|
|
574
|
-
* -
|
|
575
|
-
* -
|
|
576
|
-
* -
|
|
577
|
-
*
|
|
578
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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, {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
255
|
-
this.
|
|
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 (
|
|
274
|
-
|
|
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("
|
|
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.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "A simple and lightweight logger",
|
|
5
|
-
"main": "./module/
|
|
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": "
|
|
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"
|