@pilaf/backends 1.0.2 → 1.2.1

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.
@@ -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 };