Comparing version 2.9.1 to 2.9.2
@@ -5,3 +5,3 @@ # Benchmarks | ||
To benchmark Errsole against Elasticsearch, we conducted tests under the following setup: | ||
To benchmark Errsole against Elasticsearch and Amazon CloudWatch, we conducted tests under the following setup: | ||
@@ -14,66 +14,58 @@ 1. **Node.js Application:** | ||
* **Specifications:** 2 CPUs, 8 GB RAM | ||
3. **Elasticsearch Server:** | ||
3. **PostgreSQL Server:** | ||
* **Instance Type:** m5.large EC2 Instance | ||
* **Specifications:** 2 CPUs, 8 GB RAM | ||
4. **Load Testing Tool:** Grafana K6 | ||
4. **MongoDB Server:** | ||
* **Instance Type:** m5.large EC2 Instance | ||
* **Specifications:** 2 CPUs, 8 GB RAM | ||
5. **Elasticsearch Server:** | ||
* **Instance Type:** m5.large EC2 Instance | ||
* **Specifications:** 2 CPUs, 8 GB RAM | ||
6. **Load Testing Tool:** Grafana K6 | ||
## Elasticsearch | ||
We conducted load testing under three different configurations to compare performance: | ||
We tested the following configurations: | ||
1. **Winston + Elasticsearch:** | ||
1. **Winston + Elasticsearch:** Configured the Node.js app with Winston for logging and Elasticsearch as the storage backend. | ||
2. **Pino + Elasticsearch:** Configured the Node.js app with Pino for logging and Elasticsearch as the storage backend. | ||
3. **Errsole + SQLite:** Configured the Node.js app with Errsole for logging and SQLite as the storage backend. | ||
4. **Errsole + MySQL:** Configured the Node.js app with Errsole for logging and MySQL as the storage backend. | ||
5. **Errsole + PostgreSQL:** Configured the Node.js app with Errsole for logging and PostgreSQL as the storage backend. | ||
6. **Errsole + MongoDB:** Configured the Node.js app with Errsole for logging and MongoDB as the storage backend. | ||
Configured the Node.js app with Winston for logging and Elasticsearch as the storage backend. | ||
2. **Pino + Elasticsearch:** | ||
Configured the Node.js app with Pino for logging and Elasticsearch as the storage backend. | ||
3. **Errsole + MySQL:** | ||
Configured the Node.js app with Errsole for logging and MySQL as the storage backend. | ||
### Results | ||
Errsole demonstrated a significant performance advantage, handling 79,000 - 85,000 more requests per minute compared to Elasticsearch configurations. | ||
Errsole demonstrated a significant performance advantage, handling 70,000 - 90,000 more requests per minute compared to Elasticsearch configurations. | ||
| **Test No.** | **Pino + Elasticsearch** | **Winston + Elasticsearch** | **Errsole + MySQL** | | ||
|-------------- |-------------------------- |----------------------------- |--------------------- | | ||
| 1 | 265363 | 268917 | 349623 | | ||
| 2 | 265160 | 273568 | 352383 | | ||
| 3 | 272862 | 274449 | 351421 | | ||
| 4 | 259297 | 270090 | 350173 | | ||
| 5 | 263454 | 271782 | 350188 | | ||
| **Average** | **265227** | **271761** | **350758** | | ||
| **Test No.** | **Pino + Elasticsearch** | **Winston + Elasticsearch** | **Errsole + MongoDB** | **Errsole + MySQL** | **Errsole + PostgreSQL** | **Errsole + SQLite** | | ||
|-------------- |-------------------------- |----------------------------- |----------------------- |--------------------- |-------------------------- |---------------------- | | ||
| 1 | 265363 | 268917 | 340490 | 349623 | 360264 | 370499 | | ||
| 2 | 265160 | 273568 | 338163 | 352383 | 360785 | 362611 | | ||
| 3 | 272862 | 274449 | 338963 | 351421 | 364411 | 364310 | | ||
| 4 | 259297 | 270090 | 337759 | 350173 | 367953 | 361347 | | ||
| 5 | 263454 | 271782 | 340265 | 350188 | 367309 | 362578 | | ||
| **Average** | **265227** | **271761** | **339128** | **350758** | **364144** | **364269** | | ||
<img src="https://github.com/user-attachments/assets/14eb3290-a2d5-4365-8926-532120e2c6c5" alt="errsole-vs-elasticsearch-benchmarks" width="800"> | ||
<img src="https://github.com/user-attachments/assets/e193e016-a14a-46c1-92af-865b3be27df4" alt="errsole-vs-elasticsearch-benchmarks" width="800"> | ||
## Amazon CloudWatch | ||
We also conducted tests comparing the performance of Errsole with Amazon CloudWatch. | ||
We also conducted tests comparing the performance of Errsole with Amazon CloudWatch: | ||
1. **Winston + CloudWatch:** | ||
1. **Winston + CloudWatch:** Configured the Node.js app with Winston for logging and CloudWatch as the storage backend. | ||
2. **Pino + CloudWatch:** Configured the Node.js app with Pino for logging and CloudWatch as the storage backend. | ||
Configured the Node.js app with Winston for logging and CloudWatch as the storage backend. | ||
2. **Pino + CloudWatch:** | ||
Configured the Node.js app with Pino for logging and CloudWatch as the storage backend. | ||
3. **Errsole + MySQL:** | ||
Configured the Node.js app with Errsole for logging and MySQL as the storage backend. | ||
### Results | ||
Errsole significantly outperformed all CloudWatch configurations in benchmark tests. It handled 296,000 more requests per minute than direct CloudWatch and 55,000 more requests per minute than Pino + CloudWatch. Notably, Winston + CloudWatch failed in all test scenarios. | ||
Errsole significantly outperformed all CloudWatch configurations in benchmark tests. It handled 280,000 - 300,000 more requests per minute than direct CloudWatch and 40,000 - 70,000 more requests per minute than Pino + CloudWatch. Notably, Winston + CloudWatch failed in all test scenarios. | ||
| **Test No.** | **CloudWatch** | **Winston + CloudWatch** | **Pino + CloudWatch** | **Errsole + MySQL** | | ||
|-------------- |---------------- |-------------------------- |----------------------- |--------------------- | | ||
| 1 | 54185 | Failed | 296752 | 349623 | | ||
| 2 | 55126 | Failed | 290988 | 352383 | | ||
| 3 | 54932 | Failed | 301431 | 351421 | | ||
| 4 | 54859 | Failed | 292222 | 350173 | | ||
| 5 | 55239 | Failed | 294272 | 350188 | | ||
| **Average** | **54868** | **Failed** | **295133** | **350758** | | ||
| **Test No.** | **CloudWatch** | **Winston + CloudWatch** | **Pino + CloudWatch** | **Errsole + MongoDB** | **Errsole + MySQL** | **Errsole + PostgreSQL** | **Errsole + SQLite** | | ||
|-------------- |---------------- |-------------------------- |----------------------- |----------------------- |--------------------- |-------------------------- |---------------------- | | ||
| 1 | 54185 | Failed | 296752 | 340490 | 349623 | 360264 | 370499 | | ||
| 2 | 55126 | Failed | 290988 | 338163 | 352383 | 360785 | 362611 | | ||
| 3 | 54932 | Failed | 301431 | 338963 | 351421 | 364411 | 364310 | | ||
| 4 | 54859 | Failed | 292222 | 337759 | 350173 | 367953 | 361347 | | ||
| 5 | 55239 | Failed | 294272 | 340265 | 350188 | 367309 | 362578 | | ||
| **Average** | **54868** | **Failed** | **295133** | **339128** | **350758** | **364144** | **364269** | | ||
@@ -80,0 +72,0 @@ ## Benchmarks Code |
'use strict'; | ||
const ErrsoleMain = require('./main'); | ||
const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware'); | ||
@@ -8,3 +8,3 @@ const koaProxies = require('koa-proxies'); | ||
const http = require('http'); | ||
const ErrsoleMain = require('./main'); | ||
const Errsole = { | ||
@@ -183,3 +183,3 @@ port: 8001 | ||
Errsole.hapiProxyMiddleware = function (basePath) { | ||
Errsole.hapiProxyMiddleware = function (basePath, auth = false) { | ||
const targetURL = 'http://localhost:' + this.port; | ||
@@ -204,2 +204,5 @@ return { | ||
} | ||
}, | ||
options: { | ||
auth | ||
} | ||
@@ -211,1 +214,2 @@ }); | ||
module.exports = Errsole; | ||
module.exports.default = Errsole; |
@@ -12,2 +12,4 @@ 'use strict'; | ||
serverName: null, | ||
pendingAlerts: [], | ||
isInitializated: false, | ||
@@ -20,2 +22,4 @@ initialize (options) { | ||
this.handleUncaughtExceptions(options.exitOnException); | ||
this.isInitializated = true; | ||
this.flushAlerts(); | ||
}, | ||
@@ -55,3 +59,3 @@ | ||
async customLogger (level, message, metadata) { | ||
Logs.customLogger(level, message, metadata); | ||
Logs.logCustomMessage(level, message, metadata); | ||
if (level === 'alert') { | ||
@@ -63,3 +67,7 @@ const messageExtraInfo = { | ||
}; | ||
await Alerts.customLoggerAlert(message, messageExtraInfo); | ||
if (this.isInitializated) { | ||
await Alerts.customLoggerAlert(message, messageExtraInfo); | ||
} else { | ||
this.pendingAlerts.push({ message, messageExtraInfo }); | ||
} | ||
} | ||
@@ -78,9 +86,26 @@ }, | ||
}; | ||
await Alerts.handleUncaughtExceptions(`${errorOrigin}\n${errorMessage}`, messageExtraInfo); | ||
await Logs.clearLogsBeforeExit(); | ||
if (this.isInitializated) { | ||
await Alerts.handleUncaughtExceptions(`${errorOrigin}\n${errorMessage}`, messageExtraInfo); | ||
await Logs.flushLogs(); | ||
} | ||
if (exitOnException) process.exit(1); | ||
}); | ||
}, | ||
async flushAlerts () { | ||
const alertsToFlush = [...this.pendingAlerts]; | ||
this.pendingAlerts = []; | ||
try { | ||
for (let i = 0; i < alertsToFlush.length; i++) { | ||
const { message, messageExtraInfo } = alertsToFlush[i]; | ||
await Alerts.customLoggerAlert(message, messageExtraInfo); | ||
} | ||
} catch (err) { | ||
console.error('Failed to flush alerts:', err); | ||
} | ||
} | ||
}; | ||
module.exports = Main; |
@@ -11,94 +11,156 @@ const stream = require('stream'); | ||
const CollectLogsHook = { | ||
const logCollector = { | ||
storage: {}, | ||
collectLogs: [], | ||
hostname: null, | ||
collectLogs: [LogLevel.INFO, LogLevel.ERROR], | ||
enableConsoleOutput: process.env.NODE_ENV !== 'production', | ||
hostname: os.hostname(), | ||
pid, | ||
enableConsoleOutput: process.env.NODE_ENV !== 'production' | ||
}; | ||
isInitializationFailed: false, | ||
originalStdoutWrite: process.stdout.write, | ||
originalStderrWrite: process.stderr.write, | ||
CollectLogsHook.initialize = function (options) { | ||
this.storage = options.storage; | ||
this.hostname = options.serverName || os.hostname(); | ||
if (options && typeof options.enableConsoleOutput !== 'undefined') { | ||
this.enableConsoleOutput = options.enableConsoleOutput; | ||
} | ||
this.collectLogs = options.collectLogs || ['info', 'error']; | ||
if (this.collectLogs.includes(LogLevel.INFO)) { | ||
this.captureLogs(LogLevel.INFO); | ||
console.log('Errsole is capturing INFO logs.'); | ||
} else { | ||
console.log('Errsole is NOT capturing INFO logs.'); | ||
} | ||
if (this.collectLogs.includes(LogLevel.ERROR)) { | ||
this.captureLogs(LogLevel.ERROR); | ||
console.log('Errsole is capturing ERROR logs.'); | ||
} else { | ||
console.log('Errsole is NOT capturing ERROR logs.'); | ||
} | ||
}; | ||
setInitializationTimeout () { | ||
this.initializationTimeoutId = setTimeout(() => { | ||
this.createEmptyLogStream(); | ||
this.isInitializationFailed = true; | ||
console.error('Error: Unable to initialize Errsole'); | ||
}, 10000); | ||
}, | ||
CollectLogsHook.captureLogs = function (level) { | ||
const self = this; | ||
if (self.storage.once) { | ||
self.storage.once('ready', function () { | ||
if (!self.enableConsoleOutput) { | ||
const writeFunction = level === LogLevel.INFO ? process.stdout : process.stderr; | ||
writeFunction.write = function () { | ||
try { | ||
const argsArray = Array.from(arguments); | ||
logBuffer.write.apply(logBuffer, argsArray); | ||
} catch (err) { } | ||
}; | ||
initialize (options = {}) { | ||
this.storage = options.storage; | ||
this.collectLogs = options.collectLogs || [LogLevel.INFO, LogLevel.ERROR]; | ||
if (typeof options.enableConsoleOutput !== 'undefined') { | ||
this.enableConsoleOutput = options.enableConsoleOutput; | ||
} | ||
this.hostname = options.serverName || os.hostname(); | ||
if (this.storage.once) { | ||
this.storage.once('ready', () => { | ||
clearTimeout(this.initializationTimeoutId); | ||
if (this.isInitializationFailed) { | ||
this.createLogStream(); | ||
this.isInitializationFailed = false; | ||
} else { | ||
this.logStream.uncork(); | ||
} | ||
}); | ||
} | ||
if (this.enableConsoleOutput) { | ||
process.stdout.write = this.originalStdoutWrite; | ||
process.stderr.write = this.originalStderrWrite; | ||
} else { | ||
process.stdout.write = (chunk, encoding, done) => done(); | ||
process.stderr.write = (chunk, encoding, done) => done(); | ||
} | ||
if (this.collectLogs.includes(LogLevel.INFO)) { | ||
this.interceptLogs(LogLevel.INFO); | ||
console.log(`Errsole is capturing ${LogLevel.INFO.toUpperCase()} logs.`); | ||
} else { | ||
console.log(`Errsole is NOT capturing ${LogLevel.INFO.toUpperCase()} logs.`); | ||
} | ||
if (this.collectLogs.includes(LogLevel.ERROR)) { | ||
this.interceptLogs(LogLevel.ERROR); | ||
console.log(`Errsole is capturing ${LogLevel.ERROR.toUpperCase()} logs.`); | ||
} else { | ||
console.log(`Errsole is NOT capturing ${LogLevel.ERROR.toUpperCase()} logs.`); | ||
} | ||
}, | ||
createLogStream () { | ||
this.logStream = new stream.Writable({ | ||
objectMode: true, | ||
write: (logEntry, encoding, callback) => { | ||
this.storage.postLogs([logEntry]); | ||
setImmediate(callback); | ||
} | ||
}); | ||
} | ||
const logBuffer = new stream.Writable(); | ||
logBuffer._write = (chunk, encoding, done) => { | ||
try { | ||
const cleanedChunk = stripAnsi(chunk.toString()); | ||
const logEntry = { timestamp: new Date().toISOString(), message: cleanedChunk, source: 'console', level, hostname: self.hostname || '', pid: self.pid || 0 }; | ||
self.logStream.write(logEntry); | ||
done(); | ||
} catch (err) { } | ||
}; | ||
}, | ||
const originalWrite = level === LogLevel.INFO ? process.stdout.write : process.stderr.write; | ||
const writeFunction = level === LogLevel.INFO ? process.stdout : process.stderr; | ||
createEmptyLogStream () { | ||
if (this.logStream) { | ||
this.logStream.destroy(); | ||
} | ||
this.logStream = new stream.Writable({ | ||
objectMode: true, | ||
write: (logEntry, encoding, callback) => { | ||
setImmediate(callback); | ||
} | ||
}); | ||
}, | ||
writeFunction.write = function () { | ||
try { | ||
const argsArray = Array.from(arguments); | ||
originalWrite.apply(writeFunction, argsArray); | ||
logBuffer.write.apply(logBuffer, argsArray); | ||
} catch (err) { } | ||
}; | ||
}; | ||
interceptLogs (level) { | ||
let logStream; | ||
let originalWrite; | ||
switch (level) { | ||
case LogLevel.INFO: | ||
logStream = process.stdout; | ||
originalWrite = this.originalStdoutWrite; | ||
break; | ||
case LogLevel.ERROR: | ||
logStream = process.stderr; | ||
originalWrite = this.originalStderrWrite; | ||
break; | ||
default: | ||
return; | ||
} | ||
CollectLogsHook.customLogger = function (level, message, metadata) { | ||
try { | ||
const logEntry = { timestamp: new Date().toISOString(), message, meta: metadata || '{}', source: 'errsole', level, hostname: this.hostname || '', pid: this.pid || 0 }; | ||
this.logStream.write(logEntry); | ||
} catch (err) { } | ||
}; | ||
logStream.write = (chunk, encoding, done) => { | ||
const cleanedChunk = stripAnsi(chunk.toString()); | ||
const logEntry = { | ||
timestamp: new Date().toISOString(), | ||
message: cleanedChunk, | ||
source: 'console', | ||
level, | ||
hostname: this.hostname, | ||
pid: this.pid | ||
}; | ||
this.logStream.write(logEntry); | ||
CollectLogsHook.logStream = new stream.Writable({ | ||
objectMode: true, | ||
write (logEntry, encoding, callback) { | ||
CollectLogsHook.storage.postLogs([logEntry]); | ||
setImmediate(() => callback()); | ||
} | ||
}); | ||
if (this.enableConsoleOutput) { | ||
originalWrite.call(logStream, chunk, encoding, done); | ||
} else if (done) { | ||
done(); | ||
} | ||
}; | ||
}, | ||
CollectLogsHook.clearLogsBeforeExit = async function (timeout = 5000) { | ||
if (typeof this.storage.flushLogs === 'function') { | ||
async flushLogs (timeout = 5000) { | ||
if (typeof this.storage.flushLogs === 'function') { | ||
try { | ||
await Promise.race([ | ||
this.storage.flushLogs(), | ||
new Promise((resolve, reject) => setTimeout(() => reject(new Error('flushLogs timed out')), timeout)) | ||
]); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
}, | ||
logCustomMessage (level, message, metadata) { | ||
const logEntry = { | ||
timestamp: new Date().toISOString(), | ||
message, | ||
meta: metadata || '{}', | ||
source: 'errsole', | ||
level, | ||
hostname: this.hostname, | ||
pid: this.pid | ||
}; | ||
try { | ||
await Promise.race([ | ||
this.storage.flushLogs(), | ||
new Promise((resolve, reject) => setTimeout(() => reject(new Error('flushLogs timed out')), timeout)) | ||
]); | ||
} catch (err) { } | ||
this.logStream.write(logEntry); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
}; | ||
module.exports = CollectLogsHook; | ||
logCollector.setInitializationTimeout(); | ||
logCollector.createLogStream(); | ||
logCollector.logStream.cork(); | ||
logCollector.interceptLogs(LogLevel.INFO); | ||
logCollector.interceptLogs(LogLevel.ERROR); | ||
module.exports = logCollector; |
@@ -209,1 +209,3 @@ const { getStorageConnection } = require('../storageConnection'); | ||
}; | ||
exports.SlackService = SlackService; | ||
exports.EmailService = EmailService; |
{ | ||
"name": "errsole", | ||
"version": "2.9.1", | ||
"version": "2.9.2", | ||
"description": "Collect, Store, and Visualize Logs with a Single Module", | ||
@@ -24,5 +24,7 @@ "keywords": [ | ||
"examples", | ||
"lib" | ||
"lib", | ||
"types" | ||
], | ||
"main": "lib/errsole", | ||
"main": "lib/errsole.js", | ||
"types": "types/errsole.d.ts", | ||
"scripts": { | ||
@@ -79,8 +81,3 @@ "build:web": "NODE_OPTIONS=--openssl-legacy-provider webpack --config lib/web/webpack/website.config.js", | ||
"webpack-merge": "^5.10.0" | ||
}, | ||
"jest": { | ||
"coveragePathIgnorePatterns": [ | ||
"/lib/main/server/utils/alerts.js" | ||
] | ||
} | ||
} |
@@ -41,5 +41,5 @@ <p align="center"> | ||
Errsole outperforms Elasticsearch by 80,000 requests per minute. [Read More](https://github.com/errsole/errsole.js/blob/master/docs/benchmarks.md) | ||
A Node.js app using Errsole Logger can handle 90,000 more requests per minute than when using Elasticsearch and 70,000 more requests per minute than when using Amazon CloudWatch. [Read More](https://github.com/errsole/errsole.js/blob/master/docs/benchmarks.md) | ||
<img src="https://github.com/user-attachments/assets/14eb3290-a2d5-4365-8926-532120e2c6c5" alt="errsole-vs-elasticsearch-benchmarks" width="800"> | ||
<img src="https://github.com/user-attachments/assets/e193e016-a14a-46c1-92af-865b3be27df4" alt="errsole-vs-elasticsearch-benchmarks" width="800"> | ||
@@ -54,2 +54,3 @@ ## Setup | ||
* [Errsole with OracleDB](https://github.com/errsole/errsole.js/blob/master/docs/oracledb-storage.md) | ||
* [Advanced Configuration](https://github.com/errsole/errsole.js/blob/master/docs/advanced-configuration.md) | ||
@@ -148,10 +149,16 @@ ## Web Dashboard Access | ||
## Contribution and Support | ||
## Useful Links | ||
**Contribution:** We welcome contributions! If you have ideas for improvements, feel free to fork the repository, make your changes, and submit a pull request. | ||
* [FAQs](https://github.com/errsole/errsole.js/discussions/categories/faqs) | ||
**Support:** Have questions, facing issues, or want to request a feature? [Open an issue](https://github.com/errsole/errsole.js/issues/new) on the GitHub repository. | ||
* **Encountering issues?** [Open an issue](https://github.com/errsole/errsole.js/issues/new) on our GitHub repository. | ||
* **Have questions?** Use our [Q&A forum](https://github.com/errsole/errsole.js/discussions/categories/q-a). | ||
* **Want to request a feature or share your ideas?** Use our [discussion forum](https://github.com/errsole/errsole.js/discussions/categories/general). | ||
* **Want to contribute?** First, share your idea with the community in our [discussion forum](https://github.com/errsole/errsole.js/discussions/categories/general) to see what others are saying. Then, fork the repository, make your changes, and submit a pull request. | ||
## License | ||
[MIT](https://github.com/errsole/errsole.js/blob/master/LICENSE) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
3357985
50
2525
162