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