ntlogger 2.3.1 → 2.4.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/index.js CHANGED
@@ -1,12 +1,20 @@
1
1
  /*!
2
2
  * ntlogger
3
- * Copyright(c) 2024 Kevin R
3
+ * Copyright(c) 2024 KR
4
4
  * GPL-3.0 Licensed
5
5
  */
6
6
 
7
7
  /**
8
8
  * Change Log:
9
9
  *
10
+ * v2.4.0 - 08/01/2024:
11
+ * [NOTE] Damn its been 4 months.. kinda. forgot to log last changes
12
+ * [CRITICAL] Fixed `includes` in package.json which prevented plugins folder from being pushed to npmjs.com
13
+ * [QA] Added test cases using jest
14
+ * [Feature] Added session ID to logger meta, try `console.log(log.defaultMeta.ID)`
15
+ * [Feature] Created the plugin jest. Added a custom transport for in memory logging and testing
16
+ * [Feature] Created the config var `skipCache` to allow you to create a new logger instance within the same file. (Usual behavior is to return the same instance within the same file)
17
+ *
10
18
  * v2.2.3 - 04/01/2024:
11
19
  * Reduced GitHub Actions to only NPM publish
12
20
  * Fixed export
package/lib/logger.js CHANGED
@@ -13,7 +13,7 @@
13
13
  const winston = require('winston');
14
14
  const crypto = require('crypto');
15
15
 
16
- const { initPlugins, compilePluginComponents} = require('../plugins/index.js');
16
+ const { initPlugins } = require('../plugins/index.js');
17
17
 
18
18
  // Define a map to hold the logger instances by their location
19
19
  const loggerInstances = new Map();
@@ -124,7 +124,8 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports) =>
124
124
  path = './logs',
125
125
  maxSize = 1048576,
126
126
  maxFiles = 5,
127
- timestamp = true
127
+ timestamp = true,
128
+ skipCache = false,
128
129
  } = config;
129
130
 
130
131
  let logTransport = [].filter(Boolean);
@@ -162,6 +163,7 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports) =>
162
163
  transports: logTransport,
163
164
  defaultMeta: {
164
165
  location,
166
+ ID: sessionId,
165
167
  timeCreated: new Date().toLocaleTimeString('en-US', {hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3})
166
168
  }
167
169
  });
@@ -198,7 +200,7 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports) =>
198
200
  */
199
201
  const logger = (location = "Unknown", config = {}) => {
200
202
  // Check if a logger for the given location already exists
201
- if (loggerInstances.has(location)) {
203
+ if (loggerInstances.has(location) && !config.skipCache) {
202
204
  // If it does, grab the existing logger instance from the map
203
205
  const logger = loggerInstances.get(location);
204
206
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntlogger",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/NightSquawk/NightTimeLogger.git"
@@ -22,7 +22,8 @@
22
22
  ],
23
23
  "files": [
24
24
  "index.js",
25
- "lib/*.js"
25
+ "lib/*.js",
26
+ "plugins/*.js"
26
27
  ],
27
28
  "license": "GPL-3.0",
28
29
  "dependencies": {
@@ -33,6 +34,10 @@
33
34
  },
34
35
  "devDependencies": {
35
36
  "@sentry/profiling-node": "^8.17.0",
36
- "dotenv": "^16.4.5"
37
+ "dotenv": "^16.4.5",
38
+ "jest": "^29.0.0"
39
+ },
40
+ "scripts": {
41
+ "test": "jest"
37
42
  }
38
43
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @file /plugins/index.js
3
+ * @description Initializes any plugins required by the application.
4
+ */
5
+
6
+ // Available plugins
7
+ const plugins = {
8
+ Sentry: require('./sentry'),
9
+ MySQL : require('./mysql'),
10
+ Jest: require('./jest'),
11
+
12
+ // TODO: Implement the following plugins
13
+ // mongodb: require('./mongodb'),
14
+ // redis: require('./redis'),
15
+ };
16
+
17
+ function checkPluginAvailability(pluginName) {
18
+ if (!plugins[pluginName]) {
19
+ throw new Error(`Plugin ${pluginName} is not available\nAvailable plugins: ${Object.keys(plugins).join(', ')}`);
20
+ }
21
+ }
22
+
23
+ function getPluginTransport(pluginName) {
24
+ checkPluginAvailability(pluginName);
25
+ return plugins[pluginName].transport;
26
+ }
27
+
28
+ /**
29
+ * Initializes plugins named and configured in the configuration object.
30
+ * @param {Object} config - The configuration object for the plugins
31
+ * @param {string} config.sentry - The configuration object for Sentry
32
+ * @returns {Array} - An array of plugin components
33
+ */
34
+ function initPlugins(config = {}) {
35
+ let pluginTransports = [];
36
+ try {
37
+ for (let plugin of config) {
38
+ if (!plugin.name) {
39
+ throw new Error('Plugin name is required');
40
+ } else {
41
+ checkPluginAvailability(plugin.name);
42
+ let customTransportClass = getPluginTransport(plugin.name);
43
+ pluginTransports.push(new customTransportClass(plugin.config));
44
+ }
45
+ }
46
+ return pluginTransports;
47
+ } catch (err) {
48
+ console.error('Error initializing plugins:', err);
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ initPlugins,
54
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @file /plugins/jest.js
3
+ * @description Stores log messages in memory for Jest testing.
4
+ */
5
+
6
+ const Transport = require('winston-transport');
7
+
8
+ /**
9
+ * Custom Winston transport for storing logs in memory for Jest assertions.
10
+ */
11
+ class JestTransport extends Transport {
12
+ constructor(opts = {}) {
13
+ super(opts);
14
+
15
+ this.name = 'JestTransport';
16
+ this.disc = 'Stores log messages in memory for Jest testing.';
17
+ this.logMessages = [];
18
+ }
19
+
20
+ /**
21
+ * Logs the message and stores it in memory.
22
+ * @param {Object} info - The log information object containing the message, level, and other metadata.
23
+ * @param {Function} callback - Callback function to indicate logging completion.
24
+ */
25
+ log(info, callback) {
26
+ setImmediate(() => {
27
+ this.emit('logged', info);
28
+ });
29
+
30
+ const { level, message, ...meta } = info;
31
+
32
+ // Store the log message in memory
33
+ this.logMessages.push({ level, message, meta });
34
+
35
+ callback();
36
+ }
37
+
38
+ /**
39
+ * Retrieve stored log messages filtered by a specific level, if provided.
40
+ * @param {string} [level] - The log level to filter messages by (e.g., 'info', 'error').
41
+ * @returns {Array} Array of filtered or all log messages.
42
+ */
43
+ getMessages(level) {
44
+ if (level) {
45
+ return this.logMessages.filter(log => log.level === level);
46
+ }
47
+ return this.logMessages;
48
+ }
49
+
50
+ /**
51
+ * Clear all stored log messages.
52
+ */
53
+ clearMessages() {
54
+ this.logMessages = [];
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ transport: JestTransport,
60
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @file /plugins/mysql.js
3
+ * @description Stores logs in MySQL database.
4
+ */
5
+
6
+ const Transport = require('winston-transport');
7
+ const mysql = require('mysql2/promise');
8
+
9
+ let connectionPool = null;
10
+ let tableInitialized = false;
11
+
12
+ class MySQLTransport extends Transport {
13
+ constructor(opts = {}) {
14
+ super(opts);
15
+
16
+ this.name = 'MySQL Transport for NTLogger';
17
+ this.database = opts.database || 'test';
18
+ this.table = opts.table || 'logs';
19
+
20
+ this.levels = {
21
+ internal: 6,
22
+ trace: 5,
23
+ debug: 4,
24
+ info: 3,
25
+ warn: 2,
26
+ error: 1,
27
+ fatal: 0,
28
+ };
29
+ this.logLevel = opts.level ? this.levels[opts.level] : this.levels.info;
30
+
31
+ this.poolConfig = {
32
+ host: opts.host || 'localhost',
33
+ port: opts.port || 3306,
34
+ user: opts.user || 'root',
35
+ password: opts.password || '',
36
+ database: this.database,
37
+ waitForConnections: opts.waitForConnections || true,
38
+ connectionLimit: opts.connectionLimit || 10,
39
+ queueLimit: opts.queueLimit || 0,
40
+ maxIdle: opts.maxIdle || 10,
41
+ idleTimeout: opts.idleTimeout || 60000,
42
+ enableKeepAlive: opts.enableKeepAlive || true,
43
+ keepAliveInitialDelay: opts.keepAliveInitialDelay || 0,
44
+ };
45
+
46
+ this.init();
47
+ }
48
+
49
+ /**
50
+ * Initializes the MySQL connection pool and checks the log table.
51
+ */
52
+ async init() {
53
+ try {
54
+ if (!connectionPool) {
55
+ connectionPool = mysql.createPool(this.poolConfig);
56
+ }
57
+ this.pool = connectionPool;
58
+ await this.checkLogTable();
59
+ } catch (err) {
60
+ console.error(`Failed to initialize ${this.name}:`, err);
61
+ }
62
+ }
63
+
64
+ async log(info, callback) {
65
+ setImmediate(() => {
66
+ this.emit('logged', info);
67
+ });
68
+
69
+ if (!tableInitialized) {
70
+ await this.checkLogTable();
71
+ }
72
+
73
+ const { level, message, ...meta } = info;
74
+
75
+ if (this.levels[level] > this.logLevel) {
76
+ callback();
77
+ return;
78
+ }
79
+
80
+ const metaString = JSON.stringify(meta);
81
+ const timestamp = this.formatTimestamp(meta.timeCreated);
82
+
83
+ try {
84
+ await this.pool.execute(
85
+ `INSERT INTO ${this.table} (level, message, meta, timestamp) VALUES (?, ?, ?, ?)`,
86
+ [level, message, metaString, timestamp]
87
+ );
88
+ } catch (err) {
89
+ console.error(`Failed to log message to ${this.name}:`, err);
90
+ }
91
+
92
+ callback();
93
+ }
94
+
95
+ /**
96
+ * Formats the timestamp for the MySQL database.
97
+ * @param {string} timeCreated - The time created string.
98
+ * @returns {string} - The formatted timestamp.
99
+ */
100
+ formatTimestamp(timeCreated) {
101
+ if (!timeCreated) {
102
+ return new Date().toISOString().slice(0, 19).replace('T', ' ');
103
+ }
104
+
105
+ const date = new Date();
106
+ const [time, modifier] = timeCreated.split(' ');
107
+ let [hours, minutes, seconds] = time.split(':');
108
+
109
+ if (modifier === 'PM' && hours !== '12') {
110
+ hours = parseInt(hours, 10) + 12;
111
+ }
112
+ if (modifier === 'AM' && hours === '12') {
113
+ hours = '00';
114
+ }
115
+
116
+ return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${hours}:${minutes}:${seconds}`;
117
+ }
118
+
119
+ /**
120
+ * Checks if the log table exists in the MySQL database. If it does not exist, it will create it.
121
+ */
122
+ async checkLogTable() {
123
+ if (tableInitialized) return;
124
+
125
+ try {
126
+ const [rows] = await this.pool.execute(
127
+ `SELECT COUNT(*) AS count
128
+ FROM information_schema.tables
129
+ WHERE table_schema = ?
130
+ AND table_name = ?`,
131
+ [this.database, this.table]
132
+ );
133
+
134
+ if (rows[0].count === 0) {
135
+ await this.pool.execute(
136
+ `CREATE TABLE IF NOT EXISTS ${this.table} (
137
+ id INT AUTO_INCREMENT PRIMARY KEY,
138
+ level VARCHAR(255) NOT NULL,
139
+ message TEXT NOT NULL,
140
+ meta TEXT,
141
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
142
+ )`
143
+ );
144
+ }
145
+
146
+ tableInitialized = true;
147
+ } catch (err) {
148
+ console.error(`Failed to check or create table ${this.table}:`, err);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Checks if the MySQL connection pool is active.
154
+ * @returns {boolean} - True if the connection pool is active, false otherwise.
155
+ */
156
+ isActive() {
157
+ return this.pool && !this.pool.ended;
158
+ }
159
+
160
+ /**
161
+ * Closes the MySQL connection pool.
162
+ * @returns {Promise<void>}
163
+ */
164
+ async close() {
165
+ if (connectionPool) {
166
+ await connectionPool.end();
167
+ connectionPool = null;
168
+ console.log('Closed MySQL connection pool.');
169
+ }
170
+ }
171
+ }
172
+
173
+ module.exports = {
174
+ transport: MySQLTransport,
175
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @file /plugins/sentry.js
3
+ * @description Initializes the Sentry SDK for error tracking and defines a custom Winston transport.
4
+ */
5
+
6
+ const Transport = require('winston-transport');
7
+ const Sentry = require("@sentry/node");
8
+
9
+ /**
10
+ * Custom Winston transport for sending logs to Sentry.
11
+ */
12
+ class SentryTransport extends Transport {
13
+ constructor(opts = {}) {
14
+ super(opts);
15
+
16
+ this.name = 'Sentry Transport for NTLogger';
17
+ this.init(opts);
18
+ }
19
+
20
+ /**
21
+ * Initializes the Sentry SDK with the provided configuration.
22
+ * @param {Object} config - The configuration object for Sentry initialization.
23
+ */
24
+ init(config = {}) {
25
+ try {
26
+ Sentry.init({...config});
27
+ } catch (err) {
28
+ console.error(`Failed to initialize ${this.name}:`, err);
29
+ }
30
+ }
31
+
32
+ log(info, callback) {
33
+ setImmediate(() => {
34
+ this.emit('logged', info);
35
+ });
36
+
37
+ const { level, message, ...meta } = info;
38
+
39
+ if (level === 'error') {
40
+ Sentry.captureException(new Error(message), { extra: meta });
41
+ } else {
42
+ Sentry.captureMessage(message, { level, extra: meta });
43
+ }
44
+
45
+ callback();
46
+ }
47
+ }
48
+
49
+ module.exports = {
50
+ transport: SentryTransport,
51
+ };