ntlogger 2.4.0 → 2.5.1

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
@@ -4,9 +4,30 @@
4
4
  * GPL-3.0 Licensed
5
5
  */
6
6
 
7
+ /**
8
+ * TODO: [Feature] Generic Webhook Plugin for sending logs to any webhook
9
+ * TODO: [Feature] Email Plugin for sending logs to an email address
10
+ * TODO: [Feature] SMS Mail ID Plugin for sending logs to a phone number
11
+ */
12
+
7
13
  /**
8
14
  * Change Log:
9
15
  *
16
+ * v2.5.1 - 08/10/2024:
17
+ * [Bug] Fixed a bug where Jest plugin was not working due to a missing config parameter. This rule was not originally enforced until v2.5.0.
18
+ *
19
+ * v2.5.0 - 08/10/2024:
20
+ * [NOTE] Syslog plugin can send logs using TLS but it is not tested. Avoid sensitive data.
21
+ * [Feature] Added a plugin for Discord webhook integration, allowing log messages to be sent directly to a specified Discord channel.
22
+ * [Feature] Added a plugin for Syslog server integration, enabling log messages to be sent to a Syslog server using UDP, TCP, or TLS protocols.
23
+ * [Feature] Implemented clean signal handling for graceful shutdowns (SIGINT, SIGTERM).
24
+ *
25
+ * [Update] @sentry/node ^8.17.0 --> ^8.25.0
26
+ * [Update] mysql2 ^3.10.2 --> ^3.11.0
27
+ * [Update] winston ^3.13.1 --> ^3.14.1
28
+ * [Update] @sentry/profiling-node ^8.17.0 --> ^8.25.0
29
+ * [Update] jest ^29.0.0 --> ^29.7.0
30
+ *
10
31
  * v2.4.0 - 08/01/2024:
11
32
  * [NOTE] Damn its been 4 months.. kinda. forgot to log last changes
12
33
  * [CRITICAL] Fixed `includes` in package.json which prevented plugins folder from being pushed to npmjs.com
package/lib/colors.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @file /lib/colors.js
3
+ * @description Provides color codes for console output and Discord embeds.
4
+ */
5
+
6
+ module.exports = {
7
+ console: {
8
+ internal: '\x1b[93m', // Bright yellow
9
+ trace: '\x1b[90m', // Light gray
10
+ debug: '\x1b[37m', // White
11
+ info: '\x1b[32m', // Green
12
+ warn: '\x1b[33m', // Yellow
13
+ error: '\x1b[31m', // Red
14
+ fatal: '\x1b[35m', // Magenta
15
+ },
16
+ discord: {
17
+ internal: 0x95a5a6, // Gray
18
+ trace: 0x607d8b, // Blue-gray
19
+ debug: 0x3498db, // Blue
20
+ info: 0x2ecc71, // Green
21
+ warn: 0xf39c12, // Orange
22
+ error: 0xe74c3c, // Red
23
+ fatal: 0x8e44ad, // Purple
24
+ },
25
+ };
package/lib/levels.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file /lib/levels.js
3
+ * @description Provides a mapping of log levels to their respective numerical values.
4
+ */
5
+
6
+ module.exports = {
7
+ internal: 6,
8
+ trace: 5,
9
+ debug: 4,
10
+ info: 3,
11
+ warn: 2,
12
+ error: 1,
13
+ fatal: 0,
14
+ };
package/lib/logger.js CHANGED
@@ -6,40 +6,28 @@
6
6
  * any Node.js project.
7
7
  *
8
8
  * Author: Kevin R. (Kvrnn#6940, Syntax#5569)
9
- * Date: 03/31/2024
10
- * Current Version: 2.1.2
11
9
  * License: GPL-3.0
12
10
  */
11
+
13
12
  const winston = require('winston');
14
13
  const crypto = require('crypto');
15
14
 
16
- const { initPlugins } = require('../plugins/index.js');
15
+ const { setupSignalHandlers } = require('./signalHandler');
16
+ const { initPlugins } = require('../plugins/index');
17
+ const colors = require('./colors');
18
+ const levels = require('./levels');
17
19
 
18
20
  // Define a map to hold the logger instances by their location
19
21
  const loggerInstances = new Map();
20
22
 
23
+ // Call the signal handler setup function
24
+ setupSignalHandlers(loggerInstances);
25
+
21
26
  const customSettings = {
22
- levels: {
23
- internal: 6,
24
- trace: 5,
25
- debug: 4,
26
- info: 3,
27
- warn: 2,
28
- error: 1,
29
- fatal: 0,
30
- },
31
- colors: {
32
- internal: '\x1b[93m', // Bright yellow
33
- trace: '\x1b[90m', // Light gray
34
- debug: '\x1b[37m', // White
35
- info: '\x1b[32m', // Green
36
- warn: '\x1b[33m', // Yellow
37
- error: '\x1b[31m', // Red
38
- fatal: '\x1b[35m', // Magenta
39
- },
27
+ levels: levels,
28
+ colors: colors.console,
40
29
  };
41
30
 
42
-
43
31
  const randomBrightColor = () => {
44
32
  // Configuration variables
45
33
  const minLuminanceThreshold = 0.03928;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @file /lib/signalHandler.js
3
+ * @description Initializes signal handlers for the application.
4
+ */
5
+
6
+ function setupSignalHandlers(loggerInstances) {
7
+ const cleanup = () => {
8
+ console.log('Cleaning up logger resources...');
9
+ for (const [location, logger] of loggerInstances) {
10
+ logger.end(() => {
11
+ console.log(`Logger for ${location} closed.`);
12
+ });
13
+
14
+ for (const transport of logger.transports) {
15
+ if (transport.close) {
16
+ transport.close();
17
+ }
18
+ }
19
+ }
20
+ };
21
+
22
+ process.on('SIGINT', () => {
23
+ console.log('Received SIGINT. Exiting...');
24
+ cleanup();
25
+ process.exit(0);
26
+ });
27
+
28
+ process.on('SIGTERM', () => {
29
+ console.log('Received SIGTERM. Exiting...');
30
+ cleanup();
31
+ process.exit(0);
32
+ });
33
+
34
+ process.on('SIGQUIT', () => {
35
+ console.log('Received SIGQUIT. Exiting...');
36
+ cleanup();
37
+ process.exit(0);
38
+ });
39
+
40
+ process.on('uncaughtException', (err) => {
41
+ console.error('Uncaught Exception:', err);
42
+ cleanup();
43
+ process.exit(1);
44
+ });
45
+
46
+ process.on('unhandledRejection', (reason, promise) => {
47
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
48
+ cleanup();
49
+ process.exit(1);
50
+ });
51
+ }
52
+
53
+ module.exports = { setupSignalHandlers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntlogger",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/NightSquawk/NightTimeLogger.git"
@@ -18,7 +18,9 @@
18
18
  "log management",
19
19
  "logging utility",
20
20
  "sentry compatible",
21
- "mysql compatible"
21
+ "mysql compatible",
22
+ "syslog compatible",
23
+ "discord compatible"
22
24
  ],
23
25
  "files": [
24
26
  "index.js",
@@ -27,15 +29,15 @@
27
29
  ],
28
30
  "license": "GPL-3.0",
29
31
  "dependencies": {
30
- "@sentry/node": "^8.17.0",
31
- "mysql2": "^3.10.2",
32
- "winston": "^3.13.1",
32
+ "@sentry/node": "^8.25.0",
33
+ "mysql2": "^3.11.0",
34
+ "winston": "^3.14.1",
33
35
  "winston-transport": "^4.7.1"
34
36
  },
35
37
  "devDependencies": {
36
- "@sentry/profiling-node": "^8.17.0",
38
+ "@sentry/profiling-node": "^8.25.0",
37
39
  "dotenv": "^16.4.5",
38
- "jest": "^29.0.0"
40
+ "jest": "^29.7.0"
39
41
  },
40
42
  "scripts": {
41
43
  "test": "jest"
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @file /plugins/discord.js
3
+ * @description Sends logs to a Discord webhook.
4
+ */
5
+
6
+ const Transport = require('winston-transport');
7
+ const https = require('https');
8
+ const { URL } = require('url');
9
+
10
+ const colors = require('../lib/colors');
11
+ const levels = require('../lib/levels'); // Assuming this file contains the log levels mapping
12
+
13
+ class DiscordTransport extends Transport {
14
+ constructor(opts = {}) {
15
+ super(opts);
16
+
17
+ this.name = 'Discord Webhook Transport for NTLogger';
18
+
19
+ this.webhookUrl = opts.webhookUrl;
20
+ this.username = opts.username || 'NTLogger';
21
+ this.avatarUrl = opts.avatarUrl || null;
22
+ this.strict = opts.strict || false;
23
+
24
+ try {
25
+ // Set the log level
26
+ this.level = opts.level || 'info';
27
+ if (!(this.level in levels)) {
28
+ throw new Error(`Invalid log level: ${this.level}`);
29
+ }
30
+ this.levelPriority = levels[this.level]; // Get the numerical priority of the log level
31
+ } catch (error) {
32
+ console.error(`Error setting log level: ${error.message}`);
33
+ console.error(error.stack);
34
+ throw error; // Rethrow the error to stop the transport creation if level is invalid
35
+ }
36
+
37
+ this.levelColors = colors.discord;
38
+ }
39
+
40
+ async log(info, callback) {
41
+ setImmediate(() => {
42
+ this.emit('logged', info);
43
+ });
44
+
45
+ const { level, message, ...meta } = info;
46
+
47
+ // Check if the log level matches the configured level (strict mode) or is at or below the configured level
48
+ if (this.strict) {
49
+ if (level !== this.level) {
50
+ callback(); // Skip sending the log if it's not exactly the configured level
51
+ return;
52
+ }
53
+ } else {
54
+ if (levels[level] > this.levelPriority) {
55
+ callback(); // Skip sending the log if it's above the configured level
56
+ return;
57
+ }
58
+ }
59
+
60
+ const payload = JSON.stringify({
61
+ username: this.username,
62
+ avatar_url: this.avatarUrl,
63
+ embeds: [
64
+ {
65
+ title: `Log Level: ${level.toUpperCase()}`,
66
+ description: message,
67
+ color: this.levelColors[level] || 0x000000, // Default to black if the level is unknown
68
+ fields: Object.keys(meta).map(key => ({
69
+ name: key,
70
+ value: typeof meta[key] === 'string' ? meta[key] : JSON.stringify(meta[key], null, 2),
71
+ inline: false,
72
+ })),
73
+ timestamp: new Date().toISOString(),
74
+ }
75
+ ]
76
+ });
77
+
78
+ const webhookUrl = new URL(this.webhookUrl);
79
+
80
+ const options = {
81
+ hostname: webhookUrl.hostname,
82
+ path: webhookUrl.pathname + webhookUrl.search,
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ 'Content-Length': Buffer.byteLength(payload),
87
+ },
88
+ };
89
+
90
+ const req = https.request(options, (res) => {
91
+ res.on('data', (chunk) => {
92
+ console.log(`Response from Discord: ${chunk}`);
93
+ });
94
+ res.on('end', () => {
95
+ callback();
96
+ });
97
+ });
98
+
99
+ req.on('error', (e) => {
100
+ console.error(`Failed to send log to Discord: ${e.message}`);
101
+ });
102
+
103
+ req.write(payload);
104
+ req.end();
105
+ }
106
+ }
107
+
108
+ module.exports = {
109
+ transport: DiscordTransport,
110
+ };
package/plugins/index.js CHANGED
@@ -5,15 +5,16 @@
5
5
 
6
6
  // Available plugins
7
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'),
8
+ Sentry : require('./sentry'),
9
+ MySQL : require('./mysql'),
10
+ Jest : require('./jest'),
11
+ Syslog : require('./syslog'),
12
+ Discord : require('./discord'),
13
+ // WIP SMSMail : require('./smsMail'),
15
14
  };
16
15
 
16
+ // ------------------------------ DO NOT MODIFY BELOW THIS LINE ------------------------------ //
17
+
17
18
  function checkPluginAvailability(pluginName) {
18
19
  if (!plugins[pluginName]) {
19
20
  throw new Error(`Plugin ${pluginName} is not available\nAvailable plugins: ${Object.keys(plugins).join(', ')}`);
@@ -37,16 +38,39 @@ function initPlugins(config = {}) {
37
38
  for (let plugin of config) {
38
39
  if (!plugin.name) {
39
40
  throw new Error('Plugin name is required');
40
- } else {
41
+ }
42
+
43
+ try {
41
44
  checkPluginAvailability(plugin.name);
45
+ } catch (availabilityError) {
46
+ console.error(`Plugin ${plugin.name} is not available:`, availabilityError.message);
47
+ console.error(availabilityError.stack);
48
+ continue;
49
+ }
50
+
51
+ try {
42
52
  let customTransportClass = getPluginTransport(plugin.name);
53
+
54
+ if (typeof customTransportClass !== 'function') {
55
+ throw new Error(`Transport class for plugin ${plugin.name} is not a constructor function`);
56
+ }
57
+
58
+ if (!plugin.config || typeof plugin.config !== 'object') {
59
+ throw new Error(`Invalid or missing config for plugin ${plugin.name}`);
60
+ }
61
+
43
62
  pluginTransports.push(new customTransportClass(plugin.config));
63
+ } catch (transportError) {
64
+ console.error(`Failed to initialize plugin ${plugin.name}:`, transportError.message);
65
+ console.error(transportError.stack);
44
66
  }
45
67
  }
46
- return pluginTransports;
47
68
  } catch (err) {
48
- console.error('Error initializing plugins:', err);
69
+ console.error('Error initializing plugins:', err.message);
70
+ console.error(err.stack);
49
71
  }
72
+
73
+ return pluginTransports;
50
74
  }
51
75
 
52
76
  module.exports = {
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @file /plugins/syslog.js
3
+ * @description Sends logs to a Syslog server using custom Syslog client.
4
+ */
5
+
6
+ const Transport = require('winston-transport');
7
+ const SyslogClient = require('./lib/syslogClient');
8
+
9
+ class SyslogTransport extends Transport {
10
+ constructor(opts = {}) {
11
+ super(opts);
12
+
13
+ this.name = 'Syslog Transport for NTLogger';
14
+ this.client = new SyslogClient({
15
+ host: opts.host || 'localhost',
16
+ port: opts.port || 514,
17
+ protocol: opts.protocol || 'UDP', // Options: 'UDP', 'TCP', 'TLS'
18
+ rfc: opts.rfc || 'RFC-5424', // Options: 'RFC-3164', 'RFC-5424'
19
+ facility: opts.facility || 1,
20
+ appName: opts.appName || 'NTLogger',
21
+ hostname: opts.hostname || require('os').hostname(),
22
+ });
23
+
24
+ this.levels = {
25
+ internal: 7, // Debug level for internal logs
26
+ trace: 7, // Debug
27
+ debug: 7, // Debug
28
+ info: 6, // Informational
29
+ warn: 4, // Warning
30
+ error: 3, // Error
31
+ fatal: 2, // Critical
32
+ };
33
+ this.logLevel = opts.level ? this.levels[opts.level] : this.levels.info;
34
+
35
+ if (this.client.protocol === 'TCP' || this.client.protocol === 'TLS') {
36
+ this.client.connect().catch(err => {
37
+ console.error(`Failed to connect to Syslog server at ${this.client.host}:${this.client.port}:`, err);
38
+ });
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Logs messages to the Syslog server.
44
+ * @param {Object} info - Log information.
45
+ * @param {Function} callback - Callback function.
46
+ */
47
+ log(info, callback) {
48
+ setImmediate(() => {
49
+ this.emit('logged', info);
50
+ });
51
+
52
+ const { level, message, ...meta } = info;
53
+
54
+ if (this.levels[level] > this.logLevel) {
55
+ callback();
56
+ return;
57
+ }
58
+
59
+ this.client.send(this.levels[level], message, meta);
60
+
61
+ callback();
62
+ }
63
+
64
+ /**
65
+ * Checks if the Syslog client is active.
66
+ * @returns {boolean} - True if the client is active, false otherwise.
67
+ */
68
+ isActive() {
69
+ return this.client && (this.client.protocol === 'UDP' || (this.client.transport && !this.client.transport.destroyed));
70
+ }
71
+
72
+ /**
73
+ * Closes the Syslog client connection.
74
+ * @returns {Promise<void>}
75
+ */
76
+ async close() {
77
+ this.client.close();
78
+ console.log('Closed Syslog client connection.');
79
+ }
80
+ }
81
+
82
+ module.exports = {
83
+ transport: SyslogTransport,
84
+ };