nwinread
v1.2.0
Published
High-performance native Node.js module for reading Windows event logs with real-time monitoring and watermark support
Maintainers
Readme
nwinread - Windows Event Log Reader
A high-performance native Node.js module for reading Windows event logs using the Windows Event Log API with both synchronous and asynchronous support.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- API Reference
- Common Use Cases
- Performance & Best Practices
- Examples & Testing
- Error Handling
- Troubleshooting
- Contributing
- License
Features
- 🔥 Asynchronous Subscriptions: Real-time event monitoring with zero polling
- 📚 Synchronous Reading: One-time queries for batch processing
- 🎯 Smart Positioning: Read from beginning, end, or specific Record ID (watermarks)
- ⚡ Event Filtering: Filter by Event IDs for efficient processing
- 📋 Bookmark Support: Resume from exact positions using Windows Event Log bookmarks
- 🛡️ Cross-Version Compatible: Built with N-API for all Node.js versions 16+
- 🚀 Precompiled Binaries: No compilation needed for standard installations
Requirements
- Windows: Vista/7/8/10/11 or Server 2008/2012/2016/2019/2022
- Node.js: 16+ (recommended for precompiled binaries)
- Permissions: Administrator privileges required for Security log only
Installation
npm install nwinread✅ Installs instantly using precompiled binaries for Node.js 16+
Quick Start
Synchronous Reading (One-time queries)
const eventLog = require('nwinread');
// Read recent events
const recent = eventLog.readEvents(
"System", // Channel
eventLog.START_MODE.END, // From end
0, // Watermark (ignored for END mode)
10 // Max events
);
console.log(`Found ${recent.records.length} recent events`);
recent.records.forEach(event => {
console.log(`Record ID: ${event.recordId}`);
console.log(`Event XML: ${event.xml.substring(0, 100)}...`);
});Asynchronous Monitoring (Real-time)
const eventLog = require('nwinread');
// Monitor new events in real-time
const subscription = eventLog.subscribeFromEnd(
'Application',
(event) => {
console.log(`🆕 New event: ${event.recordId}`);
console.log(` XML: ${event.xml.substring(0, 150)}...`);
},
(error) => {
console.error(`❌ Error: ${error.message}`);
},
[1000, 1001] // Optional: filter by Event IDs
);
console.log(`✅ Monitoring started. Subscription ID: ${subscription.id}`);
// Stop monitoring after 30 seconds
setTimeout(() => {
subscription.unsubscribe();
console.log('✅ Monitoring stopped');
}, 30000);API Reference
Reading Modes
| Mode | Value | Description | Synchronous | Asynchronous |
|------|-------|-------------|-------------|--------------|
| BEGINNING | 0 | Start from oldest events | readEvents() | subscribeFromBeginning() |
| END | 1 | Start from newest/future events | readEvents() | subscribeFromEnd() |
| WATERMARK | 2 | Start from specific Record ID | readEvents() | subscribeFromWatermark() |
Synchronous API
readEvents(channel, mode, watermark, maxEvents, eventIds)
const eventLog = require('nwinread');
// Basic syntax
eventLog.readEvents(channel, mode, watermark, maxEvents, eventIds)
// Read from beginning
const oldest = eventLog.readEvents(
"Application",
eventLog.START_MODE.BEGINNING,
0, // Watermark ignored for BEGINNING
50 // Max events
);
// Read recent events
const recent = eventLog.readEvents(
"System",
eventLog.START_MODE.END,
0, // Watermark ignored for END
20
);
// Read from specific Record ID
const fromPoint = eventLog.readEvents(
"Application",
eventLog.START_MODE.WATERMARK,
50000, // Start from Record ID 50000
25
);
// Read with Event ID filter
const filtered = eventLog.readEvents(
"System",
eventLog.START_MODE.END,
0,
30,
[1074, 6005, 6006] // Only these Event IDs
);Parameters:
channel(string): Event log channel ("System", "Application", "Security", etc.)mode(number): Reading mode (0=BEGINNING, 1=END, 2=WATERMARK)watermark(number): Starting Record ID for WATERMARK mode (ignored for other modes)maxEvents(number): Maximum events to returneventIds(array, optional): Array of Event IDs to filter by
Returns: Object with records array and lastRecordId
Asynchronous API
subscribe(channel, watermark, onEvent, onError, eventIds, mode)
Core subscription method with precise control:
const subscription = eventLog.subscribe(
'Application', // channel
48000, // watermark (Record ID to start from)
(event) => { // onEvent callback
console.log(`Event: ${event.recordId}`);
// event.recordId - Record ID number
// event.xml - Full event XML
},
(error) => { // onError callback
console.log(`Error: ${error.message}`);
},
[1000, 1001], // eventIds filter (optional, null for all)
eventLog.START_MODE.WATERMARK // mode (0=BEGINNING, 1=END, 2=WATERMARK)
);
console.log(`Subscription ID: ${subscription.id}`);
// Always cleanup when done
subscription.unsubscribe();Helper Methods (Recommended)
subscribeFromEnd(channel, onEvent, onError, eventIds)
Monitor new events only (future events):
const subscription = eventLog.subscribeFromEnd(
'System',
(event) => console.log(`New: ${event.recordId}`),
(error) => console.error(error.message),
[1074, 6005] // Optional Event ID filter
);subscribeFromBeginning(channel, onEvent, onError, eventIds)
Process all historical events + monitor new events:
const subscription = eventLog.subscribeFromBeginning(
'Application',
(event) => console.log(`Historical/New: ${event.recordId}`),
(error) => console.error(error.message)
);subscribeFromWatermark(channel, watermark, onEvent, onError, eventIds)
Resume from specific Record ID:
const subscription = eventLog.subscribeFromWatermark(
'Application',
50000, // Start from Record ID 50000
(event) => console.log(`From 50k: ${event.recordId}`),
(error) => console.error(error.message)
);EventLogSubscription Object
All subscription methods return an EventLogSubscription object:
const subscription = eventLog.subscribeFromEnd('System', onEvent, onError);
// Properties
console.log(subscription.id); // Subscription ID (number)
console.log(subscription.channel); // Channel name (string)
console.log(subscription.watermark); // Starting watermark (number)
console.log(subscription.mode); // Reading mode (number)
// Methods
subscription.isActive(); // Returns true if subscription is active
subscription.getLastRecordId(); // Get last processed Record ID
subscription.unsubscribe(); // Stop subscription and clean upEventEmitter Pattern
const emitter = eventLog.createEventEmitter('System', {
mode: eventLog.START_MODE.END,
watermark: 0,
eventIds: [1074, 6005]
});
emitter.on('event', (event) => {
console.log(`Event: ${event.recordId}`);
});
emitter.on('error', (error) => {
console.error(`Error: ${error.message}`);
});
// Cleanup
emitter.unsubscribe();Common Use Cases
Real-time System Monitoring
const eventLog = require('nwinread');
// Monitor system shutdown/startup events
const systemMonitor = eventLog.subscribeFromEnd("System",
(event) => {
// Extract Event ID from XML
const eventIdMatch = event.xml.match(/<EventID.*?>(\d+)<\/EventID>/);
const eventId = eventIdMatch ? eventIdMatch[1] : 'unknown';
if (eventId === '1074') {
console.log(`🔄 System shutdown initiated - Record ID: ${event.recordId}`);
} else if (eventId === '6005') {
console.log(`✅ Event Log service started - Record ID: ${event.recordId}`);
}
},
(error) => console.error(`❌ Monitor error: ${error.message}`),
[1074, 6005, 6006] // Filter critical system events
);
// Keep monitoring until interrupted
process.on('SIGINT', () => {
console.log('\n🛑 Stopping system monitor...');
systemMonitor.unsubscribe();
process.exit(0);
});Application Error Monitoring
// Monitor ALL application events (historical + new)
const appMonitor = eventLog.subscribeFromBeginning("Application",
(event) => {
// Look for error patterns in the XML
if (event.xml.includes('Error') || event.xml.includes('Exception')) {
console.log(`🚨 Application error detected:`);
console.log(` Record ID: ${event.recordId}`);
console.log(` Time: ${new Date().toISOString()}`);
console.log(` Preview: ${event.xml.substring(0, 200)}...`);
}
},
(error) => console.error(`❌ App monitor error: ${error.message}`)
);
// Process for 1 minute then stop
setTimeout(() => {
appMonitor.unsubscribe();
console.log('✅ Application monitoring completed');
}, 60000);Resumable Processing with Watermarks
// Save last processed Record ID to resume later
let lastProcessedId = 48000; // Load from database/file in real usage
const processor = eventLog.subscribeFromWatermark("Application", lastProcessedId,
(event) => {
console.log(`📝 Processing event: ${event.recordId}`);
// Your business logic here
processEvent(event);
// Update watermark for next restart
lastProcessedId = event.recordId;
// Save to database/file in real usage
console.log(`✅ Processed ${event.recordId}, next watermark: ${lastProcessedId}`);
},
(error) => {
console.error(`❌ Processing error: ${error.message}`);
// Implement retry logic here
}
);
function processEvent(event) {
// Your event processing logic
// Database inserts, API calls, etc.
}Historical Analysis
const eventLog = require('nwinread');
// Analyze large batch of historical events
function analyzeEvents() {
console.log('📊 Starting historical analysis...');
const events = eventLog.readEvents(
"System",
eventLog.START_MODE.BEGINNING,
0,
1000 // Analyze last 1000 events
);
let errorCount = 0;
let warningCount = 0;
let infoCount = 0;
events.records.forEach(event => {
// Simple level detection (Windows Event Levels: 1=Critical, 2=Error, 3=Warning, 4=Info)
if (event.xml.includes('Level>1<') || event.xml.includes('Level>2<')) {
errorCount++;
} else if (event.xml.includes('Level>3<')) {
warningCount++;
} else if (event.xml.includes('Level>4<')) {
infoCount++;
}
});
console.log(`📈 Analysis Results:`);
console.log(` Total events: ${events.records.length}`);
console.log(` Errors: ${errorCount}`);
console.log(` Warnings: ${warningCount}`);
console.log(` Information: ${infoCount}`);
console.log(` Last Record ID: ${events.lastRecordId}`);
}
analyzeEvents();Event Channels & Common Event IDs
System Channel (No admin required)
const SYSTEM_EVENTS = {
SHUTDOWN: 1074, // System shutdown initiated
UNEXPECTED_SHUTDOWN: 41, // System rebooted unexpectedly
EVENTLOG_START: 6005, // Event Log service started
EVENTLOG_STOP: 6006, // Event Log service stopped
TIME_CHANGE: 4621, // System time was changed
POWER_SLEEP: 42, // System entering sleep
POWER_WAKE: 107 // System wake from sleep
};
// Monitor critical system events
const systemSub = eventLog.subscribeFromEnd("System", onEvent, onError,
[1074, 41, 6005, 6006]
);Application Channel (No admin required)
const APPLICATION_EVENTS = {
ERROR: 1000, // Application error
WARNING: 1001, // Application warning
INFORMATION: 1002 // Application information
// Note: Most applications use custom Event IDs
};
// Monitor all application events
const appSub = eventLog.subscribeFromEnd("Application", onEvent, onError);Security Channel (Admin required)
const SECURITY_EVENTS = {
LOGIN_SUCCESS: 4624, // Successful logon
LOGIN_FAILURE: 4625, // Failed logon
LOGOUT: 4634, // Account logged off
ACCOUNT_LOCKED: 4740, // Account lockout
PRIVILEGE_USE: 4673, // Privileged service called
OBJECT_ACCESS: 4656 // Handle to object requested
};
// Monitor security events (requires admin privileges)
const securitySub = eventLog.subscribeFromEnd("Security", onEvent, onError,
[4624, 4625, 4634]
);Performance & Best Practices
⚡ High Performance Tips
// 1. Use Event ID filters to reduce processing
const filtered = eventLog.subscribeFromEnd("System", onEvent, onError, [1074, 6005]);
// 2. Limit synchronous batch sizes
const limited = eventLog.readEvents("Application", eventLog.START_MODE.END, 0, 100);
// 3. Process events immediately, don't accumulate
const efficient = eventLog.subscribeFromEnd("Application",
(event) => {
// Process immediately
sendToDatabase(event);
// Don't store in arrays/collections
},
onError
);🛡️ Reliable Processing
// 1. Always handle errors gracefully
const reliable = eventLog.subscribeFromEnd("Application",
(event) => {
try {
processEvent(event);
} catch (error) {
console.error(`Event processing error: ${error.message}`);
// Log error but continue processing other events
}
},
(error) => {
console.error(`Subscription error: ${error.message}`);
// Implement automatic recovery
setTimeout(() => {
console.log('🔄 Attempting to restart subscription...');
createNewSubscription();
}, 5000);
}
);
// 2. Clean shutdown handling
process.on('SIGINT', () => {
console.log('🛑 Gracefully shutting down...');
reliable.unsubscribe();
process.exit(0);
});
// 3. Save watermarks for recovery
let lastProcessedId = loadWatermarkFromFile();
const persistent = eventLog.subscribeFromWatermark("Application", lastProcessedId,
(event) => {
processEvent(event);
lastProcessedId = event.recordId;
saveWatermarkToFile(lastProcessedId); // Persist progress
},
onError
);💾 Memory Management
// 1. Unsubscribe when done
const subscription = eventLog.subscribeFromEnd("System", onEvent, onError);
// Always cleanup
setTimeout(() => {
subscription.unsubscribe(); // Frees native resources
}, 30000);
// 2. Process events in streams, not collections
// ❌ Don't accumulate events
const events = [];
const badSub = eventLog.subscribeFromEnd("Application",
(event) => events.push(event), // Memory leak!
onError
);
// ✅ Process immediately
const goodSub = eventLog.subscribeFromEnd("Application",
(event) => {
processEventImmediately(event); // No accumulation
},
onError
);Examples & Testing
The repository includes comprehensive examples in the test/ directory:
# Test synchronous reading
node test/example_sync.js
# Test asynchronous monitoring
node test/example_async.js
# Test watermark functionality
node test/test_watermark_realistic.js
# Test all reading modes
npm testExample Files
test/example_sync.js- Synchronous reading examplestest/example_async.js- Asynchronous monitoring examplestest/test_watermark_realistic.js- Watermark/bookmark functionalitytest/test_watermark_realistic.js- Find valid Record IDs for testing
Error Handling
Common Errors
// Channel not found
try {
const sub = eventLog.subscribeFromEnd("InvalidChannel", onEvent, onError);
} catch (error) {
if (error.code === 15007) {
console.log('❌ Channel not found - check channel name');
}
}
// Permission denied (for Security channel)
try {
const sub = eventLog.subscribeFromEnd("Security", onEvent, onError);
} catch (error) {
if (error.message.includes('access')) {
console.log('❌ Admin privileges required for Security log');
}
}
// Invalid Record ID
try {
const sub = eventLog.subscribeFromWatermark("Application", 999999999, onEvent, onError);
} catch (error) {
if (error.code === 15027) {
console.log('❌ Invalid Record ID - may be beyond available range');
}
}Error Codes
| Code | Meaning | Solution | |------|---------|----------| | 15007 | Channel not found | Check channel name ("System", "Application", etc.) | | 15027 | Invalid query/Record ID | Use valid Record ID within available range | | 87 | Invalid parameter | Check parameter types and values | | 5 | Access denied | Run as administrator for Security log |
Advanced Usage
Custom Event Processing Pipeline
const eventLog = require('nwinread');
class EventProcessor {
constructor() {
this.stats = { processed: 0, errors: 0 };
this.subscription = null;
}
start() {
this.subscription = eventLog.subscribeFromEnd("Application",
(event) => this.processEvent(event),
(error) => this.handleError(error),
[1000, 1001, 1002] // Filter application events
);
console.log(`✅ Event processor started. Subscription: ${this.subscription.id}`);
}
processEvent(event) {
try {
// Parse event XML
const data = this.parseEventData(event);
// Business logic
this.handleApplicationEvent(data);
this.stats.processed++;
// Log progress
if (this.stats.processed % 100 === 0) {
console.log(`📊 Processed ${this.stats.processed} events`);
}
} catch (error) {
console.error(`❌ Processing error: ${error.message}`);
this.stats.errors++;
}
}
parseEventData(event) {
// Extract structured data from event XML
const eventIdMatch = event.xml.match(/<EventID.*?>(\d+)<\/EventID>/);
const timeMatch = event.xml.match(/<TimeCreated SystemTime='([^']+)'/);
return {
recordId: event.recordId,
eventId: eventIdMatch ? parseInt(eventIdMatch[1]) : null,
timestamp: timeMatch ? new Date(timeMatch[1]) : null,
xml: event.xml
};
}
handleApplicationEvent(data) {
switch(data.eventId) {
case 1000:
this.handleErrorEvent(data);
break;
case 1001:
this.handleWarningEvent(data);
break;
case 1002:
this.handleInfoEvent(data);
break;
default:
this.handleGenericEvent(data);
}
}
handleErrorEvent(data) {
console.log(`🚨 Application Error - Record ${data.recordId} at ${data.timestamp}`);
// Send alert, log to database, etc.
}
handleWarningEvent(data) {
console.log(`⚠️ Application Warning - Record ${data.recordId}`);
// Log to monitoring system
}
handleInfoEvent(data) {
console.log(`ℹ️ Application Info - Record ${data.recordId}`);
}
handleGenericEvent(data) {
console.log(`📄 Generic event ${data.eventId} - Record ${data.recordId}`);
}
handleError(error) {
console.error(`❌ Subscription error: ${error.message}`);
this.stats.errors++;
// Implement reconnection logic
setTimeout(() => {
console.log('🔄 Attempting to restart...');
this.start();
}, 5000);
}
getStats() {
return {
...this.stats,
isActive: this.subscription && this.subscription.isActive()
};
}
stop() {
if (this.subscription) {
this.subscription.unsubscribe();
console.log(`✅ Event processor stopped. Final stats:`, this.getStats());
}
}
}
// Usage
const processor = new EventProcessor();
processor.start();
// Stop after 60 seconds
setTimeout(() => {
processor.stop();
}, 60000);
// Graceful shutdown
process.on('SIGINT', () => {
processor.stop();
process.exit(0);
});Troubleshooting
Installation Issues
# For Node.js 16+
npm install nwinread # Should install instantly
# If compilation is needed (Node.js < 16)
npm install --build-from-source
# Force rebuild if having issues
npm rebuild nwinreadRuntime Issues
"Channel not found" error
- Verify channel name: "System", "Application", "Security"
- Check Windows Event Viewer to confirm channel exists
"Access denied" for Security log
- Run as Administrator:
Run as administratorin Command Prompt - Only Security log requires admin privileges
- Run as Administrator:
No events received in subscription
- Check if events exist: use
readEvents()first - Verify Event ID filters are correct
- For Security channel: ensure admin privileges
- Check if events exist: use
"Invalid Record ID" for watermarks
- Use
readEvents()to find valid Record ID range - Record IDs are not sequential and vary by channel
- Use
Debugging
// Enable debugging to see internal operations
const eventLog = require('nwinread');
// Test with small batch first
const test = eventLog.readEvents("Application", eventLog.START_MODE.END, 0, 5);
console.log(`Test result: ${test.records.length} events found`);
if (test.records.length > 0) {
console.log(`Record ID range: ${test.records[0].recordId} to ${test.lastRecordId}`);
// Now try subscription with known good Record ID
const sub = eventLog.subscribeFromWatermark("Application",
test.records[0].recordId,
(event) => console.log(`✅ Event: ${event.recordId}`),
(error) => console.log(`❌ Error: ${error.message}`)
);
}Contributing
This is a Windows-specific native module. Development requires:
- Windows development environment
- Visual Studio Build Tools
- Node.js 16+
- Windows Event Log for testing
# Development setup
git clone https://github.com/solzimer/nwinread.git
cd nwinread
npm install
npm run rebuild
# Run tests
npm test
# Run examples
npm run examplesSupport
- 🐛 Bug Reports: GitHub Issues
- 📖 Documentation: This README and inline code examples
- 💡 Feature Requests: GitHub Issues
Changelog
See GitHub Releases for version history and changes.
License
MIT License - see LICENSE file for details.
