@pilaf/backends 1.0.2 → 1.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 +1170 -10
- package/lib/collectors/DockerLogCollector.js +334 -0
- package/lib/collectors/index.js +9 -0
- package/lib/core/CommandRouter.js +154 -0
- package/lib/core/CorrelationStrategy.js +172 -0
- package/lib/core/LogCollector.js +194 -0
- package/lib/core/LogParser.js +125 -0
- package/lib/core/index.js +26 -0
- package/lib/errors/index.js +363 -0
- package/lib/helpers/EventObserver.js +267 -0
- package/lib/helpers/QueryHelper.js +279 -0
- package/lib/helpers/index.js +13 -0
- package/lib/index.js +72 -1
- package/lib/mineflayer-backend.js +266 -1
- package/lib/monitoring/CircularBuffer.js +202 -0
- package/lib/monitoring/LogMonitor.js +303 -0
- package/lib/monitoring/correlations/TagCorrelationStrategy.js +214 -0
- package/lib/monitoring/correlations/UsernameCorrelationStrategy.js +233 -0
- package/lib/monitoring/correlations/index.js +13 -0
- package/lib/monitoring/index.js +16 -0
- package/lib/parsers/MinecraftLogParser.js +512 -0
- package/lib/parsers/PatternRegistry.js +366 -0
- package/lib/parsers/fixtures/minecraft-logs.js +188 -0
- package/lib/parsers/index.js +13 -0
- package/lib/rcon-backend.js +42 -26
- package/package.json +2 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogMonitor - Continuous log monitoring and event processing
|
|
3
|
+
*
|
|
4
|
+
* Aggregates LogCollector, LogParser, and CorrelationStrategy to provide
|
|
5
|
+
* continuous log monitoring with real-time event processing and correlation.
|
|
6
|
+
*
|
|
7
|
+
* The monitor polls the log collector at regular intervals, parses each line,
|
|
8
|
+
* maintains a circular buffer of recent events, and applies correlation strategies.
|
|
9
|
+
*
|
|
10
|
+
* Responsibilities (MECE):
|
|
11
|
+
* - ONLY: Orchestrate log collection, parsing, and correlation
|
|
12
|
+
* - NOT: Collect logs (LogCollector's responsibility)
|
|
13
|
+
* - NOT: Parse logs (LogParser's responsibility)
|
|
14
|
+
* - NOT: Correlate events (CorrelationStrategy's responsibility)
|
|
15
|
+
*
|
|
16
|
+
* Usage Example:
|
|
17
|
+
* const monitor = new LogMonitor({
|
|
18
|
+
* collector: new DockerLogCollector({ container: 'mc-server' }),
|
|
19
|
+
* parser: new MinecraftLogParser(),
|
|
20
|
+
* correlation: new UsernameCorrelationStrategy(),
|
|
21
|
+
* bufferSize: 1000,
|
|
22
|
+
* pollInterval: 100
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* monitor.on('event', (event) => console.log('Event:', event.type));
|
|
26
|
+
* monitor.on('correlation', (corr) => console.log('Correlation:', corr));
|
|
27
|
+
* await monitor.start();
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const { EventEmitter } = require('events');
|
|
31
|
+
const { CircularBuffer } = require('./CircularBuffer.js');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* LogMonitor class for continuous log monitoring
|
|
35
|
+
* @extends EventEmitter
|
|
36
|
+
*/
|
|
37
|
+
class LogMonitor extends EventEmitter {
|
|
38
|
+
/**
|
|
39
|
+
* Create a LogMonitor
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} options - Monitor options
|
|
42
|
+
* @param {Object} options.collector - LogCollector instance
|
|
43
|
+
* @param {Object} options.parser - LogParser instance
|
|
44
|
+
* @param {Object} options.correlation - CorrelationStrategy instance
|
|
45
|
+
* @param {number} [options.bufferSize=1000] - Size of circular event buffer
|
|
46
|
+
* @param {number} [options.pollInterval=100] - Poll interval in milliseconds
|
|
47
|
+
* @throws {Error} If required dependencies are not provided
|
|
48
|
+
*/
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
super();
|
|
51
|
+
|
|
52
|
+
if (!options.collector) {
|
|
53
|
+
throw new Error('LogCollector is required');
|
|
54
|
+
}
|
|
55
|
+
if (!options.parser) {
|
|
56
|
+
throw new Error('LogParser is required');
|
|
57
|
+
}
|
|
58
|
+
if (!options.correlation) {
|
|
59
|
+
throw new Error('CorrelationStrategy is required');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Log collector for fetching log lines
|
|
64
|
+
* @private
|
|
65
|
+
* @type {Object}
|
|
66
|
+
*/
|
|
67
|
+
this._collector = options.collector;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Log parser for converting lines to events
|
|
71
|
+
* @private
|
|
72
|
+
* @type {Object}
|
|
73
|
+
*/
|
|
74
|
+
this._parser = options.parser;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Correlation strategy for linking related events
|
|
78
|
+
* @private
|
|
79
|
+
* @type {Object}
|
|
80
|
+
*/
|
|
81
|
+
this._correlation = options.correlation;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Circular buffer for storing recent events
|
|
85
|
+
* @private
|
|
86
|
+
* @type {CircularBuffer}
|
|
87
|
+
*/
|
|
88
|
+
this._buffer = new CircularBuffer(options.bufferSize || 1000);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Poll interval in milliseconds
|
|
92
|
+
* @private
|
|
93
|
+
* @type {number}
|
|
94
|
+
*/
|
|
95
|
+
this._pollInterval = options.pollInterval || 100;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether the monitor is currently running
|
|
99
|
+
* @private
|
|
100
|
+
* @type {boolean}
|
|
101
|
+
*/
|
|
102
|
+
this._isRunning = false;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether the monitor is currently paused
|
|
106
|
+
* @private
|
|
107
|
+
* @type {boolean}
|
|
108
|
+
*/
|
|
109
|
+
this._isPaused = false;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Interval ID for the polling timer
|
|
113
|
+
* @private
|
|
114
|
+
* @type {NodeJS.Timeout|null}
|
|
115
|
+
*/
|
|
116
|
+
this._intervalId = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Start monitoring logs
|
|
121
|
+
*
|
|
122
|
+
* @returns {Promise<void>}
|
|
123
|
+
* @throws {Error} If monitor is already running
|
|
124
|
+
*/
|
|
125
|
+
async start() {
|
|
126
|
+
if (this._isRunning) {
|
|
127
|
+
throw new Error('LogMonitor is already running');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this._isRunning = true;
|
|
131
|
+
this.emit('start');
|
|
132
|
+
|
|
133
|
+
this._intervalId = setInterval(async () => {
|
|
134
|
+
// Skip polling if paused
|
|
135
|
+
if (this._isPaused) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await this._processLogs();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.emit('error', error);
|
|
143
|
+
}
|
|
144
|
+
}, this._pollInterval);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Stop monitoring logs
|
|
149
|
+
*
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
stop() {
|
|
153
|
+
if (!this._isRunning) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this._isRunning = false;
|
|
158
|
+
this._isPaused = false;
|
|
159
|
+
|
|
160
|
+
if (this._intervalId) {
|
|
161
|
+
clearInterval(this._intervalId);
|
|
162
|
+
this._intervalId = null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.emit('stop');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Pause monitoring (without stopping)
|
|
170
|
+
*
|
|
171
|
+
* @returns {void}
|
|
172
|
+
*/
|
|
173
|
+
pause() {
|
|
174
|
+
if (!this._isRunning) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this._isPaused = true;
|
|
179
|
+
this.emit('pause');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resume monitoring after pause
|
|
184
|
+
*
|
|
185
|
+
* @returns {void}
|
|
186
|
+
*/
|
|
187
|
+
resume() {
|
|
188
|
+
if (!this._isRunning || !this._isPaused) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this._isPaused = false;
|
|
193
|
+
this.emit('resume');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get all events from the buffer
|
|
198
|
+
*
|
|
199
|
+
* @returns {Array} - Array of all events (oldest to newest)
|
|
200
|
+
*/
|
|
201
|
+
getEvents() {
|
|
202
|
+
return this._buffer.toArray();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get recent events from the buffer
|
|
207
|
+
*
|
|
208
|
+
* @param {number} count - Number of recent events to return
|
|
209
|
+
* @returns {Array} - Array of recent events
|
|
210
|
+
*/
|
|
211
|
+
getRecentEvents(count = 10) {
|
|
212
|
+
const start = Math.max(0, this._buffer.size - count);
|
|
213
|
+
return this._buffer.slice(start);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get all active correlations
|
|
218
|
+
*
|
|
219
|
+
* @returns {Array} - Array of active correlations
|
|
220
|
+
*/
|
|
221
|
+
getCorrelations() {
|
|
222
|
+
return this._correlation.getActiveCorrelations();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Clear all events from the buffer
|
|
227
|
+
*
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
clear() {
|
|
231
|
+
this._buffer.clear();
|
|
232
|
+
this.emit('clear');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if the monitor is running
|
|
237
|
+
*
|
|
238
|
+
* @type {boolean}
|
|
239
|
+
*/
|
|
240
|
+
get isRunning() {
|
|
241
|
+
return this._isRunning;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Check if the monitor is paused
|
|
246
|
+
*
|
|
247
|
+
* @type {boolean}
|
|
248
|
+
*/
|
|
249
|
+
get isPaused() {
|
|
250
|
+
return this._isPaused;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get the current buffer size
|
|
255
|
+
*
|
|
256
|
+
* @type {number}
|
|
257
|
+
*/
|
|
258
|
+
get bufferSize() {
|
|
259
|
+
return this._buffer.size;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get the buffer capacity
|
|
264
|
+
*
|
|
265
|
+
* @type {number}
|
|
266
|
+
*/
|
|
267
|
+
get bufferCapacity() {
|
|
268
|
+
return this._buffer.capacity;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Process logs from collector
|
|
273
|
+
*
|
|
274
|
+
* @private
|
|
275
|
+
* @returns {Promise<void>}
|
|
276
|
+
*/
|
|
277
|
+
async _processLogs() {
|
|
278
|
+
const logs = await this._collector.collect();
|
|
279
|
+
|
|
280
|
+
for (const log of logs) {
|
|
281
|
+
const event = this._parser.parse(log);
|
|
282
|
+
|
|
283
|
+
// Skip null events or unknown patterns (when type is null)
|
|
284
|
+
if (!event || event.type === null) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Add event to buffer
|
|
289
|
+
this._buffer.push(event);
|
|
290
|
+
|
|
291
|
+
// Emit parsed event
|
|
292
|
+
this.emit('event', event);
|
|
293
|
+
|
|
294
|
+
// Apply correlation strategy
|
|
295
|
+
const correlation = this._correlation.correlate(event);
|
|
296
|
+
if (correlation) {
|
|
297
|
+
this.emit('correlation', correlation);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = { LogMonitor };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TagCorrelationStrategy - Correlates events by unique tag/ID
|
|
3
|
+
*
|
|
4
|
+
* This correlation strategy groups events by a unique tag or identifier
|
|
5
|
+
* extracted from each event. Useful for tracking request/response cycles,
|
|
6
|
+
* command executions, or any scenario where events share a common identifier.
|
|
7
|
+
*
|
|
8
|
+
* Example use case:
|
|
9
|
+
* - RCON command returns request ID: "req-12345"
|
|
10
|
+
* - Subsequent log lines reference: "req-12345 completed"
|
|
11
|
+
* - Strategy groups all events with tag "req-12345"
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Configurable tag extraction function
|
|
15
|
+
* - Automatic correlation expiration (timeout)
|
|
16
|
+
* - Efficient Map-based storage
|
|
17
|
+
* - Thread-safe correlation tracking
|
|
18
|
+
*
|
|
19
|
+
* Usage Example:
|
|
20
|
+
* const strategy = new TagCorrelationStrategy({
|
|
21
|
+
* tagExtractor: (event) => event.data?.requestId,
|
|
22
|
+
* timeout: 5000 // 5 seconds
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const event = { type: 'command.start', data: { requestId: 'req-123' } };
|
|
26
|
+
* const correlation = strategy.correlate(event);
|
|
27
|
+
* // correlation: { tag: 'req-123', events: [event], timestamp: Date.now() }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const { EventEmitter } = require('events');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* TagCorrelationStrategy class for tag-based event correlation
|
|
34
|
+
* @extends EventEmitter
|
|
35
|
+
*/
|
|
36
|
+
class TagCorrelationStrategy extends EventEmitter {
|
|
37
|
+
/**
|
|
38
|
+
* Create a TagCorrelationStrategy
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} options - Strategy options
|
|
41
|
+
* @param {Function} [options.tagExtractor] - Function to extract tag from event
|
|
42
|
+
* Default: (event) => event.data?.tag
|
|
43
|
+
* @param {number} [options.timeout=5000] - Auto-expire correlations after N ms
|
|
44
|
+
* @param {number} [options.cleanupInterval=60000] - Cleanup interval in ms
|
|
45
|
+
*/
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
super();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Function to extract tag from event
|
|
51
|
+
* @private
|
|
52
|
+
* @type {Function}
|
|
53
|
+
*/
|
|
54
|
+
this._tagExtractor = options.tagExtractor || ((event) => event.data?.tag);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Correlation timeout in milliseconds
|
|
58
|
+
* @private
|
|
59
|
+
* @type {number}
|
|
60
|
+
*/
|
|
61
|
+
this._timeout = options.timeout || 5000;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Cleanup interval for expired correlations
|
|
65
|
+
* @private
|
|
66
|
+
* @type {number}
|
|
67
|
+
*/
|
|
68
|
+
this._cleanupInterval = options.cleanupInterval || 60000;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Map of active correlations
|
|
72
|
+
* @private
|
|
73
|
+
* @type {Map<string, Object>}
|
|
74
|
+
*/
|
|
75
|
+
this._correlations = new Map();
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Interval ID for cleanup timer
|
|
79
|
+
* @private
|
|
80
|
+
* @type {NodeJS.Timeout|null}
|
|
81
|
+
*/
|
|
82
|
+
this._cleanupTimer = null;
|
|
83
|
+
|
|
84
|
+
// Start cleanup interval if timeout is set
|
|
85
|
+
if (this._timeout > 0) {
|
|
86
|
+
this._startCleanup();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Correlate an event by its tag
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} event - Parsed event object
|
|
94
|
+
* @returns {Object|null} - Correlation object with tag, events, timestamp, or null if no tag
|
|
95
|
+
*/
|
|
96
|
+
correlate(event) {
|
|
97
|
+
if (!event) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const tag = this._tagExtractor(event);
|
|
102
|
+
if (!tag) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
|
|
108
|
+
if (!this._correlations.has(tag)) {
|
|
109
|
+
this._correlations.set(tag, {
|
|
110
|
+
tag,
|
|
111
|
+
events: [],
|
|
112
|
+
timestamp: now
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const correlation = this._correlations.get(tag);
|
|
117
|
+
correlation.events.push(event);
|
|
118
|
+
correlation.timestamp = now;
|
|
119
|
+
|
|
120
|
+
return correlation;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get all active correlations
|
|
125
|
+
*
|
|
126
|
+
* @returns {Array<Object>} - Array of correlation objects
|
|
127
|
+
*/
|
|
128
|
+
getActiveCorrelations() {
|
|
129
|
+
return Array.from(this._correlations.values());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get correlation by tag
|
|
134
|
+
*
|
|
135
|
+
* @param {string} tag - Tag to look up
|
|
136
|
+
* @returns {Object|undefined} - Correlation object or undefined
|
|
137
|
+
*/
|
|
138
|
+
getCorrelation(tag) {
|
|
139
|
+
return this._correlations.get(tag);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reset all correlation state
|
|
144
|
+
*
|
|
145
|
+
* @returns {void}
|
|
146
|
+
*/
|
|
147
|
+
reset() {
|
|
148
|
+
this._correlations.clear();
|
|
149
|
+
this.emit('reset');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove expired correlations
|
|
154
|
+
*
|
|
155
|
+
* @returns {number} - Number of correlations removed
|
|
156
|
+
*/
|
|
157
|
+
cleanup() {
|
|
158
|
+
if (this._timeout <= 0) {
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
const expiredThreshold = now - this._timeout;
|
|
164
|
+
let removed = 0;
|
|
165
|
+
|
|
166
|
+
for (const [tag, correlation] of this._correlations.entries()) {
|
|
167
|
+
if (correlation.timestamp < expiredThreshold) {
|
|
168
|
+
this._correlations.delete(tag);
|
|
169
|
+
removed++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (removed > 0) {
|
|
174
|
+
this.emit('cleanup', removed);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return removed;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Stop the cleanup timer
|
|
182
|
+
*
|
|
183
|
+
* @returns {void}
|
|
184
|
+
*/
|
|
185
|
+
destroy() {
|
|
186
|
+
if (this._cleanupTimer) {
|
|
187
|
+
clearInterval(this._cleanupTimer);
|
|
188
|
+
this._cleanupTimer = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Start the cleanup interval
|
|
194
|
+
*
|
|
195
|
+
* @private
|
|
196
|
+
* @returns {void}
|
|
197
|
+
*/
|
|
198
|
+
_startCleanup() {
|
|
199
|
+
this._cleanupTimer = setInterval(() => {
|
|
200
|
+
this.cleanup();
|
|
201
|
+
}, this._cleanupInterval);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get the number of active correlations
|
|
206
|
+
*
|
|
207
|
+
* @type {number}
|
|
208
|
+
*/
|
|
209
|
+
get size() {
|
|
210
|
+
return this._correlations.size;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { TagCorrelationStrategy };
|