nwinread 1.1.0 â 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 +779 -96
- package/binding.gyp +30 -0
- package/build/binding.sln +6 -0
- package/build/eventlogasync.vcxproj +148 -0
- package/build/eventlogasync.vcxproj.filters +43 -0
- package/doc/ASYNC_STATUS.md +104 -0
- package/doc/COHERENCIA_APIS.md +154 -0
- package/doc/CONTRIBUTING.md +64 -0
- package/doc/CORRECCION_NAPI.md +74 -0
- package/doc/CPU_EFFICIENCY_GUIDE.md +199 -0
- package/doc/NAPI-SETUP.md +294 -0
- package/doc/PREBUILDS.md +180 -0
- package/doc/README_eventlogasync.md +134 -0
- package/doc/RESUMABLE_READER.md +250 -0
- package/doc/USAGE.md +527 -0
- package/index.js +202 -25
- package/native/eventlogasync.cc +687 -0
- package/package.json +37 -9
- package/prebuilds/metadata.json +24 -0
- package/prebuilds/win32-x64/eventlog.node +0 -0
- package/prebuilds/win32-x64/eventlogasync.node +0 -0
- package/prebuilds/win32-x64/meta.json +20 -0
- package/prebuilds/win32-x64/nwinread.node +0 -0
- package/scripts/generate-prebuilds-advanced.js +186 -0
- package/scripts/generate-prebuilds.js +86 -0
- package/scripts/prebuilds/win32-x64/meta.json +20 -0
- package/scripts/verify-setup.js +2 -1
- package/test/README.md +105 -0
- package/test/example_async.js +107 -0
- package/test/example_sync.js +76 -0
- package/test/test_beginning_mode.js +40 -0
- package/test/test_build_version.js +46 -0
- package/test/test_callback_simple.js +46 -0
- package/test/test_modes_comparison.js +74 -0
- package/test/test_watermark_realistic.js +75 -0
- package/test/test_watermark_specific.js +88 -0
- package/test/test_wrapper_vs_native.js +58 -0
- package/test/verify_sync_events.js +19 -0
- package/CHANGES.md +0 -120
- package/NAPI-SETUP.md +0 -142
- package/test.js +0 -34
package/README.md
CHANGED
|
@@ -1,152 +1,835 @@
|
|
|
1
1
|
# nwinread - Windows Event Log Reader
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/nwinread)
|
|
4
|
+
[](https://www.npmjs.com/package/nwinread)
|
|
5
|
+
[](https://github.com/solzimer/nwinread/blob/main/LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/nwinread)
|
|
7
|
+
|
|
8
|
+
A high-performance native Node.js module for reading Windows event logs using the Windows Event Log API with both synchronous and asynchronous support.
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Requirements](#requirements)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [API Reference](#api-reference)
|
|
17
|
+
- [Common Use Cases](#common-use-cases)
|
|
18
|
+
- [Performance & Best Practices](#performance--best-practices)
|
|
19
|
+
- [Examples & Testing](#examples--testing)
|
|
20
|
+
- [Error Handling](#error-handling)
|
|
21
|
+
- [Troubleshooting](#troubleshooting)
|
|
22
|
+
- [Contributing](#contributing)
|
|
23
|
+
- [License](#license)
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **đĨ Asynchronous Subscriptions**: Real-time event monitoring with zero polling
|
|
28
|
+
- **đ Synchronous Reading**: One-time queries for batch processing
|
|
29
|
+
- **đ¯ Smart Positioning**: Read from beginning, end, or specific Record ID (watermarks)
|
|
30
|
+
- **⥠Event Filtering**: Filter by Event IDs for efficient processing
|
|
31
|
+
- **đ Bookmark Support**: Resume from exact positions using Windows Event Log bookmarks
|
|
32
|
+
- **đĄī¸ Cross-Version Compatible**: Built with N-API for all Node.js versions 16+
|
|
33
|
+
- **đ Precompiled Binaries**: No compilation needed for standard installations
|
|
11
34
|
|
|
12
35
|
## Requirements
|
|
13
36
|
|
|
14
|
-
- Windows Vista/7/8/10/11 or
|
|
15
|
-
- Node.js (
|
|
16
|
-
|
|
17
|
-
**Build requirements (only if compiling from source):**
|
|
18
|
-
- Visual Studio Build Tools or Visual Studio Community
|
|
19
|
-
- Python (for node-gyp)
|
|
37
|
+
- **Windows**: Vista/7/8/10/11 or Server 2008/2012/2016/2019/2022
|
|
38
|
+
- **Node.js**: 16+ (recommended for precompiled binaries)
|
|
39
|
+
- **Permissions**: Administrator privileges required for Security log only
|
|
20
40
|
|
|
21
41
|
## Installation
|
|
22
42
|
|
|
23
|
-
### From npm (recommended - includes precompiled binaries)
|
|
24
|
-
|
|
25
43
|
```bash
|
|
26
44
|
npm install nwinread
|
|
27
45
|
```
|
|
28
46
|
|
|
29
|
-
|
|
47
|
+
â
**Installs instantly** using precompiled binaries for Node.js 16+
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Synchronous Reading (One-time queries)
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
const eventLog = require('nwinread');
|
|
55
|
+
|
|
56
|
+
// Read recent events
|
|
57
|
+
const recent = eventLog.readEvents(
|
|
58
|
+
"System", // Channel
|
|
59
|
+
eventLog.START_MODE.END, // From end
|
|
60
|
+
0, // Watermark (ignored for END mode)
|
|
61
|
+
10 // Max events
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
console.log(`Found ${recent.records.length} recent events`);
|
|
65
|
+
recent.records.forEach(event => {
|
|
66
|
+
console.log(`Record ID: ${event.recordId}`);
|
|
67
|
+
console.log(`Event XML: ${event.xml.substring(0, 100)}...`);
|
|
68
|
+
});
|
|
36
69
|
```
|
|
37
70
|
|
|
38
|
-
###
|
|
71
|
+
### Asynchronous Monitoring (Real-time)
|
|
39
72
|
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
|
|
73
|
+
```javascript
|
|
74
|
+
const eventLog = require('nwinread');
|
|
75
|
+
|
|
76
|
+
// Monitor new events in real-time
|
|
77
|
+
const subscription = eventLog.subscribeFromEnd(
|
|
78
|
+
'Application',
|
|
79
|
+
(event) => {
|
|
80
|
+
console.log(`đ New event: ${event.recordId}`);
|
|
81
|
+
console.log(` XML: ${event.xml.substring(0, 150)}...`);
|
|
82
|
+
},
|
|
83
|
+
(error) => {
|
|
84
|
+
console.error(`â Error: ${error.message}`);
|
|
85
|
+
},
|
|
86
|
+
[1000, 1001] // Optional: filter by Event IDs
|
|
87
|
+
);
|
|
43
88
|
|
|
44
|
-
|
|
45
|
-
npm run build
|
|
89
|
+
console.log(`â
Monitoring started. Subscription ID: ${subscription.id}`);
|
|
46
90
|
|
|
47
|
-
|
|
48
|
-
|
|
91
|
+
// Stop monitoring after 30 seconds
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
subscription.unsubscribe();
|
|
94
|
+
console.log('â
Monitoring stopped');
|
|
95
|
+
}, 30000);
|
|
49
96
|
```
|
|
50
97
|
|
|
51
|
-
##
|
|
98
|
+
## API Reference
|
|
99
|
+
|
|
100
|
+
### Reading Modes
|
|
101
|
+
|
|
102
|
+
| Mode | Value | Description | Synchronous | Asynchronous |
|
|
103
|
+
|------|-------|-------------|-------------|--------------|
|
|
104
|
+
| **BEGINNING** | `0` | Start from oldest events | `readEvents()` | `subscribeFromBeginning()` |
|
|
105
|
+
| **END** | `1` | Start from newest/future events | `readEvents()` | `subscribeFromEnd()` |
|
|
106
|
+
| **WATERMARK** | `2` | Start from specific Record ID | `readEvents()` | `subscribeFromWatermark()` |
|
|
107
|
+
|
|
108
|
+
### Synchronous API
|
|
109
|
+
|
|
110
|
+
#### `readEvents(channel, mode, watermark, maxEvents, eventIds)`
|
|
52
111
|
|
|
53
112
|
```javascript
|
|
54
|
-
const
|
|
113
|
+
const eventLog = require('nwinread');
|
|
55
114
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
115
|
+
// Basic syntax
|
|
116
|
+
eventLog.readEvents(channel, mode, watermark, maxEvents, eventIds)
|
|
117
|
+
|
|
118
|
+
// Read from beginning
|
|
119
|
+
const oldest = eventLog.readEvents(
|
|
120
|
+
"Application",
|
|
121
|
+
eventLog.START_MODE.BEGINNING,
|
|
122
|
+
0, // Watermark ignored for BEGINNING
|
|
123
|
+
50 // Max events
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Read recent events
|
|
127
|
+
const recent = eventLog.readEvents(
|
|
128
|
+
"System",
|
|
129
|
+
eventLog.START_MODE.END,
|
|
130
|
+
0, // Watermark ignored for END
|
|
131
|
+
20
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Read from specific Record ID
|
|
135
|
+
const fromPoint = eventLog.readEvents(
|
|
136
|
+
"Application",
|
|
137
|
+
eventLog.START_MODE.WATERMARK,
|
|
138
|
+
50000, // Start from Record ID 50000
|
|
139
|
+
25
|
|
62
140
|
);
|
|
63
141
|
|
|
64
|
-
// Read
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
142
|
+
// Read with Event ID filter
|
|
143
|
+
const filtered = eventLog.readEvents(
|
|
144
|
+
"System",
|
|
145
|
+
eventLog.START_MODE.END,
|
|
146
|
+
0,
|
|
147
|
+
30,
|
|
148
|
+
[1074, 6005, 6006] // Only these Event IDs
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
- `channel` (string): Event log channel ("System", "Application", "Security", etc.)
|
|
154
|
+
- `mode` (number): Reading mode (0=BEGINNING, 1=END, 2=WATERMARK)
|
|
155
|
+
- `watermark` (number): Starting Record ID for WATERMARK mode (ignored for other modes)
|
|
156
|
+
- `maxEvents` (number): Maximum events to return
|
|
157
|
+
- `eventIds` (array, optional): Array of Event IDs to filter by
|
|
158
|
+
|
|
159
|
+
**Returns:** Object with `records` array and `lastRecordId`
|
|
160
|
+
|
|
161
|
+
### Asynchronous API
|
|
162
|
+
|
|
163
|
+
#### `subscribe(channel, watermark, onEvent, onError, eventIds, mode)`
|
|
164
|
+
|
|
165
|
+
**Core subscription method with precise control:**
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
const subscription = eventLog.subscribe(
|
|
169
|
+
'Application', // channel
|
|
170
|
+
48000, // watermark (Record ID to start from)
|
|
171
|
+
(event) => { // onEvent callback
|
|
172
|
+
console.log(`Event: ${event.recordId}`);
|
|
173
|
+
// event.recordId - Record ID number
|
|
174
|
+
// event.xml - Full event XML
|
|
175
|
+
},
|
|
176
|
+
(error) => { // onError callback
|
|
177
|
+
console.log(`Error: ${error.message}`);
|
|
178
|
+
},
|
|
179
|
+
[1000, 1001], // eventIds filter (optional, null for all)
|
|
180
|
+
eventLog.START_MODE.WATERMARK // mode (0=BEGINNING, 1=END, 2=WATERMARK)
|
|
71
181
|
);
|
|
72
182
|
|
|
73
|
-
console.log(`
|
|
74
|
-
|
|
183
|
+
console.log(`Subscription ID: ${subscription.id}`);
|
|
184
|
+
|
|
185
|
+
// Always cleanup when done
|
|
186
|
+
subscription.unsubscribe();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Helper Methods (Recommended)
|
|
190
|
+
|
|
191
|
+
**`subscribeFromEnd(channel, onEvent, onError, eventIds)`**
|
|
75
192
|
|
|
76
|
-
|
|
193
|
+
Monitor new events only (future events):
|
|
77
194
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
195
|
+
```javascript
|
|
196
|
+
const subscription = eventLog.subscribeFromEnd(
|
|
197
|
+
'System',
|
|
198
|
+
(event) => console.log(`New: ${event.recordId}`),
|
|
199
|
+
(error) => console.error(error.message),
|
|
200
|
+
[1074, 6005] // Optional Event ID filter
|
|
201
|
+
);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**`subscribeFromBeginning(channel, onEvent, onError, eventIds)`**
|
|
205
|
+
|
|
206
|
+
Process all historical events + monitor new events:
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
const subscription = eventLog.subscribeFromBeginning(
|
|
210
|
+
'Application',
|
|
211
|
+
(event) => console.log(`Historical/New: ${event.recordId}`),
|
|
212
|
+
(error) => console.error(error.message)
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**`subscribeFromWatermark(channel, watermark, onEvent, onError, eventIds)`**
|
|
217
|
+
|
|
218
|
+
Resume from specific Record ID:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const subscription = eventLog.subscribeFromWatermark(
|
|
222
|
+
'Application',
|
|
223
|
+
50000, // Start from Record ID 50000
|
|
224
|
+
(event) => console.log(`From 50k: ${event.recordId}`),
|
|
225
|
+
(error) => console.error(error.message)
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### EventLogSubscription Object
|
|
230
|
+
|
|
231
|
+
All subscription methods return an `EventLogSubscription` object:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const subscription = eventLog.subscribeFromEnd('System', onEvent, onError);
|
|
235
|
+
|
|
236
|
+
// Properties
|
|
237
|
+
console.log(subscription.id); // Subscription ID (number)
|
|
238
|
+
console.log(subscription.channel); // Channel name (string)
|
|
239
|
+
console.log(subscription.watermark); // Starting watermark (number)
|
|
240
|
+
console.log(subscription.mode); // Reading mode (number)
|
|
241
|
+
|
|
242
|
+
// Methods
|
|
243
|
+
subscription.isActive(); // Returns true if subscription is active
|
|
244
|
+
subscription.getLastRecordId(); // Get last processed Record ID
|
|
245
|
+
subscription.unsubscribe(); // Stop subscription and clean up
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### EventEmitter Pattern
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
const emitter = eventLog.createEventEmitter('System', {
|
|
252
|
+
mode: eventLog.START_MODE.END,
|
|
253
|
+
watermark: 0,
|
|
254
|
+
eventIds: [1074, 6005]
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
emitter.on('event', (event) => {
|
|
258
|
+
console.log(`Event: ${event.recordId}`);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
emitter.on('error', (error) => {
|
|
262
|
+
console.error(`Error: ${error.message}`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Cleanup
|
|
266
|
+
emitter.unsubscribe();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Common Use Cases
|
|
270
|
+
|
|
271
|
+
### Real-time System Monitoring
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const eventLog = require('nwinread');
|
|
275
|
+
|
|
276
|
+
// Monitor system shutdown/startup events
|
|
277
|
+
const systemMonitor = eventLog.subscribeFromEnd("System",
|
|
278
|
+
(event) => {
|
|
279
|
+
// Extract Event ID from XML
|
|
280
|
+
const eventIdMatch = event.xml.match(/<EventID.*?>(\d+)<\/EventID>/);
|
|
281
|
+
const eventId = eventIdMatch ? eventIdMatch[1] : 'unknown';
|
|
282
|
+
|
|
283
|
+
if (eventId === '1074') {
|
|
284
|
+
console.log(`đ System shutdown initiated - Record ID: ${event.recordId}`);
|
|
285
|
+
} else if (eventId === '6005') {
|
|
286
|
+
console.log(`â
Event Log service started - Record ID: ${event.recordId}`);
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
(error) => console.error(`â Monitor error: ${error.message}`),
|
|
290
|
+
[1074, 6005, 6006] // Filter critical system events
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// Keep monitoring until interrupted
|
|
294
|
+
process.on('SIGINT', () => {
|
|
295
|
+
console.log('\nđ Stopping system monitor...');
|
|
296
|
+
systemMonitor.unsubscribe();
|
|
297
|
+
process.exit(0);
|
|
83
298
|
});
|
|
84
299
|
```
|
|
85
300
|
|
|
86
|
-
|
|
301
|
+
### Application Error Monitoring
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// Monitor ALL application events (historical + new)
|
|
305
|
+
const appMonitor = eventLog.subscribeFromBeginning("Application",
|
|
306
|
+
(event) => {
|
|
307
|
+
// Look for error patterns in the XML
|
|
308
|
+
if (event.xml.includes('Error') || event.xml.includes('Exception')) {
|
|
309
|
+
console.log(`đ¨ Application error detected:`);
|
|
310
|
+
console.log(` Record ID: ${event.recordId}`);
|
|
311
|
+
console.log(` Time: ${new Date().toISOString()}`);
|
|
312
|
+
console.log(` Preview: ${event.xml.substring(0, 200)}...`);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
(error) => console.error(`â App monitor error: ${error.message}`)
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Process for 1 minute then stop
|
|
319
|
+
setTimeout(() => {
|
|
320
|
+
appMonitor.unsubscribe();
|
|
321
|
+
console.log('â
Application monitoring completed');
|
|
322
|
+
}, 60000);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Resumable Processing with Watermarks
|
|
87
326
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
327
|
+
```javascript
|
|
328
|
+
// Save last processed Record ID to resume later
|
|
329
|
+
let lastProcessedId = 48000; // Load from database/file in real usage
|
|
330
|
+
|
|
331
|
+
const processor = eventLog.subscribeFromWatermark("Application", lastProcessedId,
|
|
332
|
+
(event) => {
|
|
333
|
+
console.log(`đ Processing event: ${event.recordId}`);
|
|
334
|
+
|
|
335
|
+
// Your business logic here
|
|
336
|
+
processEvent(event);
|
|
337
|
+
|
|
338
|
+
// Update watermark for next restart
|
|
339
|
+
lastProcessedId = event.recordId;
|
|
340
|
+
// Save to database/file in real usage
|
|
341
|
+
|
|
342
|
+
console.log(`â
Processed ${event.recordId}, next watermark: ${lastProcessedId}`);
|
|
343
|
+
},
|
|
344
|
+
(error) => {
|
|
345
|
+
console.error(`â Processing error: ${error.message}`);
|
|
346
|
+
// Implement retry logic here
|
|
347
|
+
}
|
|
348
|
+
);
|
|
91
349
|
|
|
92
|
-
|
|
350
|
+
function processEvent(event) {
|
|
351
|
+
// Your event processing logic
|
|
352
|
+
// Database inserts, API calls, etc.
|
|
353
|
+
}
|
|
354
|
+
```
|
|
93
355
|
|
|
94
|
-
###
|
|
356
|
+
### Historical Analysis
|
|
95
357
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
358
|
+
```javascript
|
|
359
|
+
const eventLog = require('nwinread');
|
|
360
|
+
|
|
361
|
+
// Analyze large batch of historical events
|
|
362
|
+
function analyzeEvents() {
|
|
363
|
+
console.log('đ Starting historical analysis...');
|
|
364
|
+
|
|
365
|
+
const events = eventLog.readEvents(
|
|
366
|
+
"System",
|
|
367
|
+
eventLog.START_MODE.BEGINNING,
|
|
368
|
+
0,
|
|
369
|
+
1000 // Analyze last 1000 events
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
let errorCount = 0;
|
|
373
|
+
let warningCount = 0;
|
|
374
|
+
let infoCount = 0;
|
|
375
|
+
|
|
376
|
+
events.records.forEach(event => {
|
|
377
|
+
// Simple level detection (Windows Event Levels: 1=Critical, 2=Error, 3=Warning, 4=Info)
|
|
378
|
+
if (event.xml.includes('Level>1<') || event.xml.includes('Level>2<')) {
|
|
379
|
+
errorCount++;
|
|
380
|
+
} else if (event.xml.includes('Level>3<')) {
|
|
381
|
+
warningCount++;
|
|
382
|
+
} else if (event.xml.includes('Level>4<')) {
|
|
383
|
+
infoCount++;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
console.log(`đ Analysis Results:`);
|
|
388
|
+
console.log(` Total events: ${events.records.length}`);
|
|
389
|
+
console.log(` Errors: ${errorCount}`);
|
|
390
|
+
console.log(` Warnings: ${warningCount}`);
|
|
391
|
+
console.log(` Information: ${infoCount}`);
|
|
392
|
+
console.log(` Last Record ID: ${events.lastRecordId}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
analyzeEvents();
|
|
396
|
+
```
|
|
101
397
|
|
|
102
|
-
|
|
103
|
-
- `records`: Array of events with `xml` and `recordId` properties
|
|
104
|
-
- `lastRecordId`: ID of the last processed record
|
|
398
|
+
## Event Channels & Common Event IDs
|
|
105
399
|
|
|
106
|
-
###
|
|
400
|
+
### System Channel (No admin required)
|
|
107
401
|
|
|
108
402
|
```javascript
|
|
109
|
-
|
|
110
|
-
|
|
403
|
+
const SYSTEM_EVENTS = {
|
|
404
|
+
SHUTDOWN: 1074, // System shutdown initiated
|
|
405
|
+
UNEXPECTED_SHUTDOWN: 41, // System rebooted unexpectedly
|
|
406
|
+
EVENTLOG_START: 6005, // Event Log service started
|
|
407
|
+
EVENTLOG_STOP: 6006, // Event Log service stopped
|
|
408
|
+
TIME_CHANGE: 4621, // System time was changed
|
|
409
|
+
POWER_SLEEP: 42, // System entering sleep
|
|
410
|
+
POWER_WAKE: 107 // System wake from sleep
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// Monitor critical system events
|
|
414
|
+
const systemSub = eventLog.subscribeFromEnd("System", onEvent, onError,
|
|
415
|
+
[1074, 41, 6005, 6006]
|
|
416
|
+
);
|
|
417
|
+
```
|
|
111
418
|
|
|
112
|
-
|
|
113
|
-
const serviceEvents = nwinread.readEvents('System', nwinread.START_MODE.BEGINNING, 0, 10, [7034, 7035, 7036, 7040, 7045]);
|
|
419
|
+
### Application Channel (No admin required)
|
|
114
420
|
|
|
115
|
-
|
|
116
|
-
const
|
|
421
|
+
```javascript
|
|
422
|
+
const APPLICATION_EVENTS = {
|
|
423
|
+
ERROR: 1000, // Application error
|
|
424
|
+
WARNING: 1001, // Application warning
|
|
425
|
+
INFORMATION: 1002 // Application information
|
|
426
|
+
// Note: Most applications use custom Event IDs
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// Monitor all application events
|
|
430
|
+
const appSub = eventLog.subscribeFromEnd("Application", onEvent, onError);
|
|
431
|
+
```
|
|
117
432
|
|
|
118
|
-
|
|
119
|
-
|
|
433
|
+
### Security Channel (Admin required)
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
const SECURITY_EVENTS = {
|
|
437
|
+
LOGIN_SUCCESS: 4624, // Successful logon
|
|
438
|
+
LOGIN_FAILURE: 4625, // Failed logon
|
|
439
|
+
LOGOUT: 4634, // Account logged off
|
|
440
|
+
ACCOUNT_LOCKED: 4740, // Account lockout
|
|
441
|
+
PRIVILEGE_USE: 4673, // Privileged service called
|
|
442
|
+
OBJECT_ACCESS: 4656 // Handle to object requested
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Monitor security events (requires admin privileges)
|
|
446
|
+
const securitySub = eventLog.subscribeFromEnd("Security", onEvent, onError,
|
|
447
|
+
[4624, 4625, 4634]
|
|
448
|
+
);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Performance & Best Practices
|
|
452
|
+
|
|
453
|
+
### ⥠High Performance Tips
|
|
454
|
+
|
|
455
|
+
```javascript
|
|
456
|
+
// 1. Use Event ID filters to reduce processing
|
|
457
|
+
const filtered = eventLog.subscribeFromEnd("System", onEvent, onError, [1074, 6005]);
|
|
458
|
+
|
|
459
|
+
// 2. Limit synchronous batch sizes
|
|
460
|
+
const limited = eventLog.readEvents("Application", eventLog.START_MODE.END, 0, 100);
|
|
461
|
+
|
|
462
|
+
// 3. Process events immediately, don't accumulate
|
|
463
|
+
const efficient = eventLog.subscribeFromEnd("Application",
|
|
464
|
+
(event) => {
|
|
465
|
+
// Process immediately
|
|
466
|
+
sendToDatabase(event);
|
|
467
|
+
// Don't store in arrays/collections
|
|
468
|
+
},
|
|
469
|
+
onError
|
|
470
|
+
);
|
|
120
471
|
```
|
|
121
472
|
|
|
122
|
-
###
|
|
473
|
+
### đĄī¸ Reliable Processing
|
|
123
474
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
475
|
+
```javascript
|
|
476
|
+
// 1. Always handle errors gracefully
|
|
477
|
+
const reliable = eventLog.subscribeFromEnd("Application",
|
|
478
|
+
(event) => {
|
|
479
|
+
try {
|
|
480
|
+
processEvent(event);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error(`Event processing error: ${error.message}`);
|
|
483
|
+
// Log error but continue processing other events
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
(error) => {
|
|
487
|
+
console.error(`Subscription error: ${error.message}`);
|
|
488
|
+
|
|
489
|
+
// Implement automatic recovery
|
|
490
|
+
setTimeout(() => {
|
|
491
|
+
console.log('đ Attempting to restart subscription...');
|
|
492
|
+
createNewSubscription();
|
|
493
|
+
}, 5000);
|
|
494
|
+
}
|
|
495
|
+
);
|
|
133
496
|
|
|
134
|
-
|
|
497
|
+
// 2. Clean shutdown handling
|
|
498
|
+
process.on('SIGINT', () => {
|
|
499
|
+
console.log('đ Gracefully shutting down...');
|
|
500
|
+
reliable.unsubscribe();
|
|
501
|
+
process.exit(0);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// 3. Save watermarks for recovery
|
|
505
|
+
let lastProcessedId = loadWatermarkFromFile();
|
|
506
|
+
const persistent = eventLog.subscribeFromWatermark("Application", lastProcessedId,
|
|
507
|
+
(event) => {
|
|
508
|
+
processEvent(event);
|
|
509
|
+
lastProcessedId = event.recordId;
|
|
510
|
+
saveWatermarkToFile(lastProcessedId); // Persist progress
|
|
511
|
+
},
|
|
512
|
+
onError
|
|
513
|
+
);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### đž Memory Management
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
// 1. Unsubscribe when done
|
|
520
|
+
const subscription = eventLog.subscribeFromEnd("System", onEvent, onError);
|
|
521
|
+
|
|
522
|
+
// Always cleanup
|
|
523
|
+
setTimeout(() => {
|
|
524
|
+
subscription.unsubscribe(); // Frees native resources
|
|
525
|
+
}, 30000);
|
|
526
|
+
|
|
527
|
+
// 2. Process events in streams, not collections
|
|
528
|
+
// â Don't accumulate events
|
|
529
|
+
const events = [];
|
|
530
|
+
const badSub = eventLog.subscribeFromEnd("Application",
|
|
531
|
+
(event) => events.push(event), // Memory leak!
|
|
532
|
+
onError
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
// â
Process immediately
|
|
536
|
+
const goodSub = eventLog.subscribeFromEnd("Application",
|
|
537
|
+
(event) => {
|
|
538
|
+
processEventImmediately(event); // No accumulation
|
|
539
|
+
},
|
|
540
|
+
onError
|
|
541
|
+
);
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Examples & Testing
|
|
545
|
+
|
|
546
|
+
The repository includes comprehensive examples in the `test/` directory:
|
|
135
547
|
|
|
136
548
|
```bash
|
|
549
|
+
# Test synchronous reading
|
|
550
|
+
node test/example_sync.js
|
|
551
|
+
|
|
552
|
+
# Test asynchronous monitoring
|
|
553
|
+
node test/example_async.js
|
|
554
|
+
|
|
555
|
+
# Test watermark functionality
|
|
556
|
+
node test/test_watermark_realistic.js
|
|
557
|
+
|
|
558
|
+
# Test all reading modes
|
|
137
559
|
npm test
|
|
138
560
|
```
|
|
139
561
|
|
|
562
|
+
### Example Files
|
|
563
|
+
|
|
564
|
+
- `test/example_sync.js` - Synchronous reading examples
|
|
565
|
+
- `test/example_async.js` - Asynchronous monitoring examples
|
|
566
|
+
- `test/test_watermark_realistic.js` - Watermark/bookmark functionality
|
|
567
|
+
- `test/test_watermark_realistic.js` - Find valid Record IDs for testing
|
|
568
|
+
|
|
569
|
+
## Error Handling
|
|
570
|
+
|
|
571
|
+
### Common Errors
|
|
572
|
+
|
|
573
|
+
```javascript
|
|
574
|
+
// Channel not found
|
|
575
|
+
try {
|
|
576
|
+
const sub = eventLog.subscribeFromEnd("InvalidChannel", onEvent, onError);
|
|
577
|
+
} catch (error) {
|
|
578
|
+
if (error.code === 15007) {
|
|
579
|
+
console.log('â Channel not found - check channel name');
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Permission denied (for Security channel)
|
|
584
|
+
try {
|
|
585
|
+
const sub = eventLog.subscribeFromEnd("Security", onEvent, onError);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (error.message.includes('access')) {
|
|
588
|
+
console.log('â Admin privileges required for Security log');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Invalid Record ID
|
|
593
|
+
try {
|
|
594
|
+
const sub = eventLog.subscribeFromWatermark("Application", 999999999, onEvent, onError);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (error.code === 15027) {
|
|
597
|
+
console.log('â Invalid Record ID - may be beyond available range');
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Error Codes
|
|
603
|
+
|
|
604
|
+
| Code | Meaning | Solution |
|
|
605
|
+
|------|---------|----------|
|
|
606
|
+
| 15007 | Channel not found | Check channel name ("System", "Application", etc.) |
|
|
607
|
+
| 15027 | Invalid query/Record ID | Use valid Record ID within available range |
|
|
608
|
+
| 87 | Invalid parameter | Check parameter types and values |
|
|
609
|
+
| 5 | Access denied | Run as administrator for Security log |
|
|
610
|
+
|
|
611
|
+
## Advanced Usage
|
|
612
|
+
|
|
613
|
+
### Custom Event Processing Pipeline
|
|
614
|
+
|
|
615
|
+
```javascript
|
|
616
|
+
const eventLog = require('nwinread');
|
|
617
|
+
|
|
618
|
+
class EventProcessor {
|
|
619
|
+
constructor() {
|
|
620
|
+
this.stats = { processed: 0, errors: 0 };
|
|
621
|
+
this.subscription = null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
start() {
|
|
625
|
+
this.subscription = eventLog.subscribeFromEnd("Application",
|
|
626
|
+
(event) => this.processEvent(event),
|
|
627
|
+
(error) => this.handleError(error),
|
|
628
|
+
[1000, 1001, 1002] // Filter application events
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
console.log(`â
Event processor started. Subscription: ${this.subscription.id}`);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
processEvent(event) {
|
|
635
|
+
try {
|
|
636
|
+
// Parse event XML
|
|
637
|
+
const data = this.parseEventData(event);
|
|
638
|
+
|
|
639
|
+
// Business logic
|
|
640
|
+
this.handleApplicationEvent(data);
|
|
641
|
+
|
|
642
|
+
this.stats.processed++;
|
|
643
|
+
|
|
644
|
+
// Log progress
|
|
645
|
+
if (this.stats.processed % 100 === 0) {
|
|
646
|
+
console.log(`đ Processed ${this.stats.processed} events`);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.error(`â Processing error: ${error.message}`);
|
|
651
|
+
this.stats.errors++;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
parseEventData(event) {
|
|
656
|
+
// Extract structured data from event XML
|
|
657
|
+
const eventIdMatch = event.xml.match(/<EventID.*?>(\d+)<\/EventID>/);
|
|
658
|
+
const timeMatch = event.xml.match(/<TimeCreated SystemTime='([^']+)'/);
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
recordId: event.recordId,
|
|
662
|
+
eventId: eventIdMatch ? parseInt(eventIdMatch[1]) : null,
|
|
663
|
+
timestamp: timeMatch ? new Date(timeMatch[1]) : null,
|
|
664
|
+
xml: event.xml
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
handleApplicationEvent(data) {
|
|
669
|
+
switch(data.eventId) {
|
|
670
|
+
case 1000:
|
|
671
|
+
this.handleErrorEvent(data);
|
|
672
|
+
break;
|
|
673
|
+
case 1001:
|
|
674
|
+
this.handleWarningEvent(data);
|
|
675
|
+
break;
|
|
676
|
+
case 1002:
|
|
677
|
+
this.handleInfoEvent(data);
|
|
678
|
+
break;
|
|
679
|
+
default:
|
|
680
|
+
this.handleGenericEvent(data);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
handleErrorEvent(data) {
|
|
685
|
+
console.log(`đ¨ Application Error - Record ${data.recordId} at ${data.timestamp}`);
|
|
686
|
+
// Send alert, log to database, etc.
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
handleWarningEvent(data) {
|
|
690
|
+
console.log(`â ī¸ Application Warning - Record ${data.recordId}`);
|
|
691
|
+
// Log to monitoring system
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
handleInfoEvent(data) {
|
|
695
|
+
console.log(`âšī¸ Application Info - Record ${data.recordId}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
handleGenericEvent(data) {
|
|
699
|
+
console.log(`đ Generic event ${data.eventId} - Record ${data.recordId}`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
handleError(error) {
|
|
703
|
+
console.error(`â Subscription error: ${error.message}`);
|
|
704
|
+
this.stats.errors++;
|
|
705
|
+
|
|
706
|
+
// Implement reconnection logic
|
|
707
|
+
setTimeout(() => {
|
|
708
|
+
console.log('đ Attempting to restart...');
|
|
709
|
+
this.start();
|
|
710
|
+
}, 5000);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
getStats() {
|
|
714
|
+
return {
|
|
715
|
+
...this.stats,
|
|
716
|
+
isActive: this.subscription && this.subscription.isActive()
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
stop() {
|
|
721
|
+
if (this.subscription) {
|
|
722
|
+
this.subscription.unsubscribe();
|
|
723
|
+
console.log(`â
Event processor stopped. Final stats:`, this.getStats());
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Usage
|
|
729
|
+
const processor = new EventProcessor();
|
|
730
|
+
processor.start();
|
|
731
|
+
|
|
732
|
+
// Stop after 60 seconds
|
|
733
|
+
setTimeout(() => {
|
|
734
|
+
processor.stop();
|
|
735
|
+
}, 60000);
|
|
736
|
+
|
|
737
|
+
// Graceful shutdown
|
|
738
|
+
process.on('SIGINT', () => {
|
|
739
|
+
processor.stop();
|
|
740
|
+
process.exit(0);
|
|
741
|
+
});
|
|
742
|
+
```
|
|
743
|
+
|
|
140
744
|
## Troubleshooting
|
|
141
745
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
746
|
+
### Installation Issues
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
# For Node.js 16+
|
|
750
|
+
npm install nwinread # Should install instantly
|
|
751
|
+
|
|
752
|
+
# If compilation is needed (Node.js < 16)
|
|
753
|
+
npm install --build-from-source
|
|
754
|
+
|
|
755
|
+
# Force rebuild if having issues
|
|
756
|
+
npm rebuild nwinread
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### Runtime Issues
|
|
760
|
+
|
|
761
|
+
1. **"Channel not found" error**
|
|
762
|
+
- Verify channel name: "System", "Application", "Security"
|
|
763
|
+
- Check Windows Event Viewer to confirm channel exists
|
|
764
|
+
|
|
765
|
+
2. **"Access denied" for Security log**
|
|
766
|
+
- Run as Administrator: `Run as administrator` in Command Prompt
|
|
767
|
+
- Only Security log requires admin privileges
|
|
768
|
+
|
|
769
|
+
3. **No events received in subscription**
|
|
770
|
+
- Check if events exist: use `readEvents()` first
|
|
771
|
+
- Verify Event ID filters are correct
|
|
772
|
+
- For Security channel: ensure admin privileges
|
|
773
|
+
|
|
774
|
+
4. **"Invalid Record ID" for watermarks**
|
|
775
|
+
- Use `readEvents()` to find valid Record ID range
|
|
776
|
+
- Record IDs are not sequential and vary by channel
|
|
777
|
+
|
|
778
|
+
### Debugging
|
|
779
|
+
|
|
780
|
+
```javascript
|
|
781
|
+
// Enable debugging to see internal operations
|
|
782
|
+
const eventLog = require('nwinread');
|
|
783
|
+
|
|
784
|
+
// Test with small batch first
|
|
785
|
+
const test = eventLog.readEvents("Application", eventLog.START_MODE.END, 0, 5);
|
|
786
|
+
console.log(`Test result: ${test.records.length} events found`);
|
|
787
|
+
|
|
788
|
+
if (test.records.length > 0) {
|
|
789
|
+
console.log(`Record ID range: ${test.records[0].recordId} to ${test.lastRecordId}`);
|
|
790
|
+
|
|
791
|
+
// Now try subscription with known good Record ID
|
|
792
|
+
const sub = eventLog.subscribeFromWatermark("Application",
|
|
793
|
+
test.records[0].recordId,
|
|
794
|
+
(event) => console.log(`â
Event: ${event.recordId}`),
|
|
795
|
+
(error) => console.log(`â Error: ${error.message}`)
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
## Contributing
|
|
801
|
+
|
|
802
|
+
This is a Windows-specific native module. Development requires:
|
|
803
|
+
|
|
804
|
+
- Windows development environment
|
|
805
|
+
- Visual Studio Build Tools
|
|
806
|
+
- Node.js 16+
|
|
807
|
+
- Windows Event Log for testing
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
# Development setup
|
|
811
|
+
git clone https://github.com/solzimer/nwinread.git
|
|
812
|
+
cd nwinread
|
|
813
|
+
npm install
|
|
814
|
+
npm run rebuild
|
|
815
|
+
|
|
816
|
+
# Run tests
|
|
817
|
+
npm test
|
|
818
|
+
|
|
819
|
+
# Run examples
|
|
820
|
+
npm run examples
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
## Support
|
|
824
|
+
|
|
825
|
+
- đ **Bug Reports**: [GitHub Issues](https://github.com/solzimer/nwinread/issues)
|
|
826
|
+
- đ **Documentation**: This README and inline code examples
|
|
827
|
+
- đĄ **Feature Requests**: [GitHub Issues](https://github.com/solzimer/nwinread/issues)
|
|
828
|
+
|
|
829
|
+
## Changelog
|
|
830
|
+
|
|
831
|
+
See [GitHub Releases](https://github.com/solzimer/nwinread/releases) for version history and changes.
|
|
145
832
|
|
|
146
|
-
##
|
|
833
|
+
## License
|
|
147
834
|
|
|
148
|
-
-
|
|
149
|
-
- `Application`: Application events
|
|
150
|
-
- `Security`: Security events (requires admin permissions)
|
|
151
|
-
- `Setup`: Installation events
|
|
152
|
-
- `Microsoft-Windows-PowerShell/Operational`: PowerShell events
|
|
835
|
+
MIT License - see LICENSE file for details.
|