errsole-sqlite
Advanced tools
Comparing version 0.0.0 to 1.0.0
416
lib/index.js
@@ -7,36 +7,55 @@ const sqlite3 = require('sqlite3'); | ||
class ErrsoleSQLite extends EventEmitter { | ||
constructor (options = {}) { | ||
constructor (filename) { | ||
super(); | ||
if (!options.logging) options.logging = false; | ||
this.name = require('../package.json').name; | ||
this.version = require('../package.json').version || '0.0.0'; | ||
this.isConnectionInProgress = true; | ||
this.db = new sqlite3.Database(filename, err => { | ||
if (err) throw err; | ||
this.initialize(); | ||
}); | ||
} | ||
this.db = new sqlite3.Database(options.database || ':memory:', (err) => { | ||
if (err) { | ||
console.error('Failed to connect to SQLite database:', err); | ||
} else { | ||
this.initialize(); | ||
} | ||
async initialize () { | ||
await this.setCacheSize(); | ||
await this.createTables(); | ||
await this.ensureLogsTTL(); | ||
this.emit('ready'); | ||
cron.schedule('0 * * * *', () => { | ||
this.deleteExpiredLogs(); | ||
}); | ||
} | ||
async initialize () { | ||
try { | ||
await this.defineTables(); | ||
this.ensureLogsTTL(); | ||
this.setCacheSize(); | ||
this.emit('ready'); | ||
cron.schedule('0 * * * *', () => { | ||
this.deleteExpiredLogs(); | ||
async setCacheSize () { | ||
const desiredSize = 8 * 1024; // Desired cache size in pages, where each page is approximately 1.5 KB (12 MB total) | ||
const currentSize = await this.getCacheSize(); | ||
if (currentSize >= desiredSize) { | ||
return Promise.resolve(); // No need to update cache size | ||
} | ||
const query = `PRAGMA cache_size = ${desiredSize}`; | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, err => { | ||
if (err) return reject(err); | ||
resolve(); | ||
}); | ||
} catch (err) { | ||
console.error('Failed to initialize:', err); | ||
} | ||
}); | ||
} | ||
defineTables () { | ||
async getCacheSize () { | ||
const query = 'PRAGMA cache_size'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, (err, row) => { | ||
if (err) return reject(err); | ||
resolve(row.cache_size); | ||
}); | ||
}); | ||
} | ||
async createTables () { | ||
const queries = [ | ||
`CREATE TABLE IF NOT EXISTS errsole_logs ( | ||
`CREATE TABLE IF NOT EXISTS errsole_logs_v1 ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
@@ -65,27 +84,49 @@ hostname TEXT, | ||
return new Promise((resolve, reject) => { | ||
let remainingQueries = queries.length; | ||
queries.forEach((query) => { | ||
this.db.run(query, (err) => { | ||
if (err) { | ||
console.error('Failed to execute query:', query, err); | ||
return reject(err); | ||
} | ||
remainingQueries--; | ||
if (remainingQueries === 0) { | ||
this.isConnectionInProgress = false; | ||
resolve(); | ||
} | ||
await Promise.all(queries.map(query => { | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, err => { | ||
if (err) return reject(err); | ||
resolve(); | ||
}); | ||
}); | ||
}); | ||
})); | ||
this.isConnectionInProgress = false; | ||
} | ||
/** | ||
* Ensures that the Time To Live (TTL) configuration for logs is set. | ||
* | ||
* @async | ||
* @function ensureLogsTTL | ||
* @returns {Promise<{}>} - A promise that resolves with an empty object once the TTL configuration is confirmed or updated. | ||
*/ | ||
async ensureLogsTTL () { | ||
const defaultTTL = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds | ||
try { | ||
const configResult = await this.getConfig('logsTTL'); | ||
if (!configResult.item) { | ||
await this.setConfig('logsTTL', defaultTTL.toString()); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
return {}; | ||
} | ||
/** | ||
* Retrieves a configuration entry from the database. | ||
* | ||
* @async | ||
* @function getConfig | ||
* @param {string} key - The key of the configuration entry to retrieve. | ||
* @returns {Promise<{item: Config}>} - A promise that resolves with an object containing the configuration item. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async getConfig (key) { | ||
const query = 'SELECT * FROM errsole_config WHERE `key` = ?'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, [key], (err, result) => { | ||
this.db.get(query, [key], (err, row) => { | ||
if (err) return reject(err); | ||
resolve({ item: result }); | ||
resolve({ item: row }); | ||
}); | ||
@@ -95,6 +136,16 @@ }); | ||
/** | ||
* Updates or adds a configuration entry in the database. | ||
* | ||
* @async | ||
* @function setConfig | ||
* @param {string} key - The key of the configuration entry. | ||
* @param {string} value - The value to be stored for the configuration entry. | ||
* @returns {Promise<{item: Config}>} - A promise that resolves with an object containing the updated or added configuration item. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async setConfig (key, value) { | ||
const query = 'INSERT INTO errsole_config (`key`, `value`) VALUES (?, ?) ON CONFLICT(`key`) DO UPDATE SET `value` = excluded.value'; | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, [key, value], (err) => { | ||
this.db.run(query, [key, value], err => { | ||
if (err) return reject(err); | ||
@@ -106,8 +157,17 @@ this.getConfig(key).then(resolve).catch(reject); | ||
/** | ||
* Deletes a configuration entry from the database. | ||
* | ||
* @async | ||
* @function deleteConfig | ||
* @param {string} key - The key of the configuration entry to be deleted. | ||
* @returns {Promise<{}>} - A Promise that resolves with an empty object upon successful deletion of the configuration. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async deleteConfig (key) { | ||
const query = 'DELETE FROM errsole_config WHERE `key` = ?'; | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, [key], (err, result) => { | ||
this.db.run(query, [key], function (err) { // You must use an old-school function () { ... } style callback rather than a lambda function, otherwise this.lastID and this.changes will be undefined. | ||
if (err) return reject(err); | ||
if (result.changes === 0) return reject(new Error('Configuration not found.')); | ||
if (this.changes === 0) return reject(new Error('Configuration not found.')); | ||
resolve({}); | ||
@@ -118,2 +178,11 @@ }); | ||
/** | ||
* Adds log entries to the database. | ||
* | ||
* @async | ||
* @function postLogs | ||
* @param {Log[]} logEntries - An array of log entries to be added to the database. | ||
* @returns {Promise<{}>} - A Promise that resolves with an empty object. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async postLogs (logEntries) { | ||
@@ -135,3 +204,3 @@ while (this.isConnectionInProgress) { | ||
const query = `INSERT INTO errsole_logs (hostname, pid, source, level, message, meta) VALUES ${placeholders}`; | ||
const query = `INSERT INTO errsole_logs_v1 (hostname, pid, source, level, message, meta) VALUES ${placeholders}`; | ||
@@ -141,5 +210,4 @@ const flattenedValues = values.flat(); | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, flattenedValues, (err) => { | ||
if (err) return reject(err); | ||
resolve(); | ||
this.db.run(query, flattenedValues, () => { | ||
resolve({}); | ||
}); | ||
@@ -149,2 +217,11 @@ }); | ||
/** | ||
* Retrieves log entries from the database based on specified filters. | ||
* | ||
* @async | ||
* @function getLogs | ||
* @param {LogFilter} [filters] - Filters to apply for log retrieval. | ||
* @returns {Promise<{items: Log[]}>} - A Promise that resolves with an object containing log items. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async getLogs (filters = {}) { | ||
@@ -208,10 +285,10 @@ const whereClauses = []; | ||
const whereClause = whereClauses.length ? `WHERE ${whereClauses.join(' AND ')}` : ''; | ||
const query = `SELECT id, hostname, pid, source, timestamp, level, message FROM errsole_logs ${whereClause} ORDER BY id ${sortOrder} LIMIT ?`; | ||
const query = `SELECT id, hostname, pid, source, timestamp, level, message FROM errsole_logs_v1 ${whereClause} ORDER BY id ${sortOrder} LIMIT ?`; | ||
values.push(filters.limit); | ||
return new Promise((resolve, reject) => { | ||
this.db.all(query, values, (err, results) => { | ||
this.db.all(query, values, (err, rows) => { | ||
if (err) return reject(err); | ||
if (shouldReverse) results.reverse(); | ||
resolve({ items: results }); | ||
if (shouldReverse) rows.reverse(); | ||
resolve({ items: rows }); | ||
}); | ||
@@ -221,2 +298,12 @@ }); | ||
/** | ||
* Retrieves log entries from the database based on specified search terms and filters. | ||
* | ||
* @async | ||
* @function searchLogs | ||
* @param {string[]} searchTerms - An array of search terms. | ||
* @param {LogFilter} [filters] - Filters to refine the search. | ||
* @returns {Promise<{items: Log[]}>} - A promise that resolves with an object containing an array of log items. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async searchLogs (searchTerms, filters = {}) { | ||
@@ -267,9 +354,9 @@ const whereClauses = searchTerms.map(() => 'message LIKE ?'); | ||
const whereClause = whereClauses.length ? `WHERE ${whereClauses.join(' AND ')}` : ''; | ||
const query = `SELECT id, hostname, pid, source, timestamp, level, message FROM errsole_logs ${whereClause} ORDER BY id DESC LIMIT ?`; | ||
const query = `SELECT id, hostname, pid, source, timestamp, level, message FROM errsole_logs_v1 ${whereClause} ORDER BY id DESC LIMIT ?`; | ||
values.push(filters.limit); | ||
return new Promise((resolve, reject) => { | ||
this.db.all(query, values, (err, results) => { | ||
this.db.all(query, values, (err, rows) => { | ||
if (err) return reject(err); | ||
resolve({ items: results }); | ||
resolve({ items: rows }); | ||
}); | ||
@@ -279,9 +366,18 @@ }); | ||
/** | ||
* Retrieves the meta data of a log entry. | ||
* | ||
* @async | ||
* @function getMeta | ||
* @param {number} id - The unique ID of the log entry. | ||
* @returns {Promise<{item: id, meta}>} - A Promise that resolves with an object containing the log ID and its associated metadata. | ||
* @throws {Error} - Throws an error if the log entry is not found or the operation fails. | ||
*/ | ||
async getMeta (id) { | ||
const query = 'SELECT id, meta FROM errsole_logs WHERE id = ?'; | ||
const query = 'SELECT id, meta FROM errsole_logs_v1 WHERE id = ?'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, [id], (err, result) => { | ||
this.db.get(query, [id], (err, row) => { | ||
if (err) return reject(err); | ||
if (!result) return reject(new Error('Log entry not found.')); | ||
resolve({ item: result }); | ||
if (!row) return reject(new Error('Log entry not found.')); | ||
resolve({ item: row }); | ||
}); | ||
@@ -291,12 +387,2 @@ }); | ||
async ensureLogsTTL () { | ||
const DEFAULT_TTL = 2592000000; | ||
try { | ||
const configResult = await this.getConfig('logsTTL'); | ||
if (!configResult.item) { | ||
await this.setConfig('logsTTL', DEFAULT_TTL.toString()); | ||
} | ||
} catch {} | ||
} | ||
/** | ||
@@ -308,3 +394,2 @@ * Deletes expired logs based on TTL configuration. | ||
*/ | ||
async deleteExpiredLogs () { | ||
@@ -315,22 +400,21 @@ if (this.deleteExpiredLogsRunning) return; | ||
const logsTTLDefault = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds | ||
const defaultLogsTTL = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds | ||
try { | ||
let logsTTL = logsTTLDefault; | ||
let logsTTL = defaultLogsTTL; | ||
const configResult = await this.getConfig('logsTTL'); | ||
if (configResult && configResult.item && configResult.item.value) { | ||
logsTTL = parseInt(configResult.item.value, 10); | ||
if (configResult.item) { | ||
const parsedTTL = parseInt(configResult.item.value, 10); | ||
logsTTL = isNaN(parsedTTL) ? defaultLogsTTL : parsedTTL; | ||
} | ||
const expirationTime = new Date(Date.now() - logsTTL); | ||
let expirationTime = new Date(Date.now() - logsTTL); | ||
expirationTime = new Date(expirationTime).toISOString(); | ||
let deletedRowCount; | ||
do { | ||
const result = await new Promise((resolve, reject) => { | ||
deletedRowCount = await new Promise((resolve, reject) => { | ||
this.db.run( | ||
'DELETE FROM errsole_logs WHERE timestamp < ?', | ||
'DELETE FROM errsole_logs_v1 WHERE id IN (SELECT id FROM errsole_logs_v1 WHERE timestamp < ? LIMIT 1000)', | ||
[expirationTime], | ||
function (err) { | ||
if (err) { | ||
return reject(err); | ||
} | ||
function (err) { // You must use an old-school function () { ... } style callback rather than a lambda function, otherwise this.lastID and this.changes will be undefined. | ||
if (err) return reject(err); | ||
resolve(this.changes); | ||
@@ -340,3 +424,2 @@ } | ||
}); | ||
deletedRowCount = result; | ||
await new Promise(resolve => setTimeout(resolve, 10000)); | ||
@@ -351,6 +434,15 @@ } while (deletedRowCount > 0); | ||
async delay (ms) { | ||
return new Promise(resolve => setTimeout(resolve, ms)); | ||
} | ||
/** | ||
* Creates a new user record in the database. | ||
* | ||
* @async | ||
* @function createUser | ||
* @param {Object} user - The user data. | ||
* @param {string} user.name - The name of the user. | ||
* @param {string} user.email - The email address of the user. | ||
* @param {string} user.password - The password of the user. | ||
* @param {string} user.role - The role of the user. | ||
* @returns {Promise<{item: User}>} - A promise that resolves with an object containing the new user item. | ||
* @throws {Error} - Throws an error if the user creation fails due to duplicate email or other database issues. | ||
*/ | ||
async createUser (user) { | ||
@@ -361,5 +453,7 @@ const SALT_ROUNDS = 10; | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, [user.name, user.email, hashedPassword, user.role], function (err) { | ||
this.db.run(query, [user.name, user.email, hashedPassword, user.role], function (err) { // You must use an old-school function () { ... } style callback rather than a lambda function, otherwise this.lastID and this.changes will be undefined. | ||
if (err) { | ||
if (err.code === 'SQLITE_CONSTRAINT') return reject(new Error('A user with the provided email already exists.')); | ||
if (err.code === 'SQLITE_CONSTRAINT') { | ||
return reject(new Error('A user with the provided email already exists.')); | ||
} | ||
return reject(err); | ||
@@ -372,16 +466,28 @@ } | ||
/** | ||
* Verifies a user's credentials against stored records. | ||
* | ||
* @async | ||
* @function verifyUser | ||
* @param {string} email - The email address of the user. | ||
* @param {string} password - The password of the user | ||
* @returns {Promise<{item: User}>} - A promise that resolves with an object containing the user item upon successful verification. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async verifyUser (email, password) { | ||
if (!email || !password) throw new Error('Both email and password are required for verification.'); | ||
if (!email || !password) { | ||
throw new Error('Both email and password are required for verification.'); | ||
} | ||
const query = 'SELECT * FROM errsole_users WHERE email = ?'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, [email], async (err, user) => { | ||
this.db.get(query, [email], async (err, row) => { | ||
if (err) return reject(err); | ||
if (!user) return reject(new Error('User not found.')); | ||
if (!row) return reject(new Error('User not found.')); | ||
const isPasswordCorrect = await bcrypt.compare(password, user.hashed_password); | ||
const isPasswordCorrect = await bcrypt.compare(password, row.hashed_password); | ||
if (!isPasswordCorrect) return reject(new Error('Incorrect password.')); | ||
delete user.hashed_password; | ||
resolve({ item: user }); | ||
delete row.hashed_password; | ||
resolve({ item: row }); | ||
}); | ||
@@ -391,8 +497,16 @@ }); | ||
/** | ||
* Retrieves the total count of users from the database. | ||
* | ||
* @async | ||
* @function getUserCount | ||
* @returns {Promise<{count: number}>} - A promise that resolves with an object containing the count of users. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async getUserCount () { | ||
const query = 'SELECT COUNT(*) as count FROM errsole_users'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, (err, result) => { | ||
this.db.get(query, (err, row) => { | ||
if (err) return reject(err); | ||
resolve({ count: result.count }); | ||
resolve({ count: row.count }); | ||
}); | ||
@@ -402,8 +516,16 @@ }); | ||
/** | ||
* Retrieves all user records from the database. | ||
* | ||
* @async | ||
* @function getAllUsers | ||
* @returns {Promise<{items: User[]}>} - A promise that resolves with an object containing an array of user items. | ||
* @throws {Error} - Throws an error if the operation fails. | ||
*/ | ||
async getAllUsers () { | ||
const query = 'SELECT id, name, email, role FROM errsole_users'; | ||
return new Promise((resolve, reject) => { | ||
this.db.all(query, (err, results) => { | ||
this.db.all(query, (err, rows) => { | ||
if (err) return reject(err); | ||
resolve({ items: results }); | ||
resolve({ items: rows }); | ||
}); | ||
@@ -413,2 +535,11 @@ }); | ||
/** | ||
* Retrieves a user record from the database based on the provided email. | ||
* | ||
* @async | ||
* @function getUserByEmail | ||
* @param {string} email - The email address of the user. | ||
* @returns {Promise<{item: User}>} - A Promise that resolves with an object containing the user item. | ||
* @throws {Error} - Throws an error if no user matches the email address. | ||
*/ | ||
async getUserByEmail (email) { | ||
@@ -419,6 +550,6 @@ if (!email) throw new Error('Email is required.'); | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, [email], (err, result) => { | ||
this.db.get(query, [email], (err, row) => { | ||
if (err) return reject(err); | ||
if (!result) return reject(new Error('User not found.')); | ||
resolve({ item: result }); | ||
if (!row) return reject(new Error('User not found.')); | ||
resolve({ item: row }); | ||
}); | ||
@@ -428,2 +559,12 @@ }); | ||
/** | ||
* Updates a user's record in the database based on the provided email. | ||
* | ||
* @async | ||
* @function updateUserByEmail | ||
* @param {string} email - The email address of the user to be updated. | ||
* @param {Object} updates - The updates to be applied to the user record. | ||
* @returns {Promise<{item: User}>} - A Promise that resolves with an object containing the updated user item. | ||
* @throws {Error} - Throws an error if no updates could be applied or the user is not found. | ||
*/ | ||
async updateUserByEmail (email, updates) { | ||
@@ -441,3 +582,3 @@ if (!email) throw new Error('Email is required.'); | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, values, async (err) => { | ||
this.db.run(query, values, err => { | ||
if (err) return reject(err); | ||
@@ -449,12 +590,25 @@ this.getUserByEmail(email).then(resolve).catch(reject); | ||
/** | ||
* Updates a user's password in the database. | ||
* | ||
* @async | ||
* @function updatePassword | ||
* @param {string} email - The email address of the user whose password is to be updated. | ||
* @param {string} currentPassword - The current password of the user for verification. | ||
* @param {string} newPassword - The new password to replace the current one. | ||
* @returns {Promise<{item: User}>} - A Promise that resolves with an object containing the updated user item (excluding sensitive information). | ||
* @throws {Error} - If the user is not found, if the current password is incorrect, or if the password update fails. | ||
*/ | ||
async updatePassword (email, currentPassword, newPassword) { | ||
if (!email || !currentPassword || !newPassword) throw new Error('Email, current password, and new password are required.'); | ||
if (!email || !currentPassword || !newPassword) { | ||
throw new Error('Email, current password, and new password are required.'); | ||
} | ||
const query = 'SELECT * FROM errsole_users WHERE email = ?'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, [email], async (err, user) => { | ||
this.db.get(query, [email], async (err, row) => { | ||
if (err) return reject(err); | ||
if (!user) return reject(new Error('User not found.')); | ||
if (!row) return reject(new Error('User not found.')); | ||
const isPasswordCorrect = await bcrypt.compare(currentPassword, user.hashed_password); | ||
const isPasswordCorrect = await bcrypt.compare(currentPassword, row.hashed_password); | ||
if (!isPasswordCorrect) return reject(new Error('Current password is incorrect.')); | ||
@@ -464,6 +618,6 @@ | ||
const updateQuery = 'UPDATE errsole_users SET hashed_password = ? WHERE email = ?'; | ||
this.db.run(updateQuery, [hashedPassword, email], function (err) { | ||
this.db.run(updateQuery, [hashedPassword, email], err => { | ||
if (err) return reject(err); | ||
delete user.hashed_password; | ||
resolve({ item: user }); | ||
delete row.hashed_password; | ||
resolve({ item: row }); | ||
}); | ||
@@ -474,8 +628,17 @@ }); | ||
async deleteUser (userId) { | ||
if (!userId) throw new Error('User ID is required.'); | ||
/** | ||
* Deletes a user record from the database. | ||
* | ||
* @async | ||
* @function deleteUser | ||
* @param {number} id - The unique ID of the user to be deleted. | ||
* @returns {Promise<{}>} - A Promise that resolves with an empty object upon successful deletion of the user. | ||
* @throws {Error} - Throws an error if no user is found with the given ID or if the database operation fails. | ||
*/ | ||
async deleteUser (id) { | ||
if (!id) throw new Error('User ID is required.'); | ||
const query = 'DELETE FROM errsole_users WHERE id = ?'; | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, [userId], function (err) { | ||
this.db.run(query, [id], function (err) { // You must use an old-school function () { ... } style callback rather than a lambda function, otherwise this.lastID and this.changes will be undefined. | ||
if (err) return reject(err); | ||
@@ -487,37 +650,4 @@ if (this.changes === 0) return reject(new Error('User not found.')); | ||
} | ||
async getCacheSize () { | ||
const query = 'PRAGMA cache_size'; | ||
return new Promise((resolve, reject) => { | ||
this.db.get(query, (err, result) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
resolve(result.cache_size); | ||
}); | ||
}); | ||
} | ||
async setCacheSize () { | ||
const desiredSize = 8192; | ||
const currentSize = await this.getCacheSize(); | ||
if (currentSize < desiredSize) { | ||
const query = `PRAGMA cache_size = ${desiredSize}`; | ||
return new Promise((resolve, reject) => { | ||
this.db.run(query, (err) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
} | ||
} | ||
module.exports = ErrsoleSQLite; |
{ | ||
"name": "errsole-sqlite", | ||
"version": "0.0.0", | ||
"version": "1.0.0", | ||
"description": "SQLite storage plugin for Errsole", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -1,1 +0,13 @@ | ||
# errsole-sqlite | ||
# errsole-sqlite | ||
SQLite storage plugin for Errsole. | ||
## What is Errsole? | ||
Errsole is a Node.js logger with a built-in web dashboard. In this dashboard, you can easily view, filter, and search your app logs. | ||
If your application uses SQLite as its database or if you prefer storing your application logs in SQLite, you should install both the "errsole" and "errsole-sqlite" modules. This setup allows you to store your application logs directly in your SQLite database. | ||
## Full Documentation | ||
[https://github.com/errsole/errsole.js](https://github.com/errsole/errsole.js) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
24344
570
2
13