ntlogger 2.4.0 → 2.5.2
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 +27 -3
- package/lib/colors.js +25 -0
- package/lib/levels.js +14 -0
- package/lib/logger.js +10 -22
- package/lib/signalHandler.js +53 -0
- package/package.json +10 -8
- package/plugins/README.md +163 -0
- package/plugins/discord.js +110 -0
- package/plugins/index.js +34 -10
- package/plugins/lib/syslogClient.js +112 -0
- package/plugins/syslog.js +84 -0
package/index.js
CHANGED
|
@@ -4,16 +4,40 @@
|
|
|
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:
|
|
15
|
+
*
|
|
16
|
+
* v2.5.2 - 08/11/2024:
|
|
17
|
+
* [BUG] Fixed an issue where the /plugins/lib folder was not being pushed to npmjs.com.
|
|
18
|
+
*
|
|
19
|
+
* v2.5.1 - 08/10/2024:
|
|
20
|
+
* [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.
|
|
21
|
+
*
|
|
22
|
+
* v2.5.0 - 08/10/2024:
|
|
23
|
+
* [NOTE] Syslog plugin can send logs using TLS but it is not tested. Avoid sensitive data.
|
|
24
|
+
* [FEATURE] Added a plugin for Discord webhook integration, allowing log messages to be sent directly to a specified Discord channel.
|
|
25
|
+
* [FEATURE] Added a plugin for Syslog server integration, enabling log messages to be sent to a Syslog server using UDP, TCP, or TLS protocols.
|
|
26
|
+
* [FEATURE] Implemented clean signal handling for graceful shutdowns (SIGINT, SIGTERM).
|
|
27
|
+
*
|
|
28
|
+
* [UPDATED] @sentry/node ^8.17.0 --> ^8.25.0
|
|
29
|
+
* [UPDATED] mysql2 ^3.10.2 --> ^3.11.0
|
|
30
|
+
* [UPDATED] winston ^3.13.1 --> ^3.14.1
|
|
31
|
+
* [UPDATED] @sentry/profiling-node ^8.17.0 --> ^8.25.0
|
|
32
|
+
* [UPDATED] jest ^29.0.0 --> ^29.7.0
|
|
9
33
|
*
|
|
10
34
|
* v2.4.0 - 08/01/2024:
|
|
11
35
|
* [NOTE] Damn its been 4 months.. kinda. forgot to log last changes
|
|
12
36
|
* [CRITICAL] Fixed `includes` in package.json which prevented plugins folder from being pushed to npmjs.com
|
|
13
37
|
* [QA] Added test cases using jest
|
|
14
|
-
* [
|
|
15
|
-
* [
|
|
16
|
-
* [
|
|
38
|
+
* [FEATURE] Added session ID to logger meta, try `console.log(log.defaultMeta.ID)`
|
|
39
|
+
* [FEATURE] Created the plugin jest. Added a custom transport for in memory logging and testing
|
|
40
|
+
* [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
41
|
*
|
|
18
42
|
* v2.2.3 - 04/01/2024:
|
|
19
43
|
* Reduced GitHub Actions to only NPM publish
|
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
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 {
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/NightSquawk/NightTimeLogger.git"
|
|
@@ -18,24 +18,26 @@
|
|
|
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",
|
|
25
27
|
"lib/*.js",
|
|
26
|
-
"plugins
|
|
28
|
+
"plugins/**/*"
|
|
27
29
|
],
|
|
28
30
|
"license": "GPL-3.0",
|
|
29
31
|
"dependencies": {
|
|
30
|
-
"@sentry/node": "^8.
|
|
31
|
-
"mysql2": "^3.
|
|
32
|
-
"winston": "^3.
|
|
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.
|
|
38
|
+
"@sentry/profiling-node": "^8.25.0",
|
|
37
39
|
"dotenv": "^16.4.5",
|
|
38
|
-
"jest": "^29.
|
|
40
|
+
"jest": "^29.7.0"
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
41
43
|
"test": "jest"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# NightTimeLogger Plugins
|
|
2
|
+
|
|
3
|
+
NightTimeLogger is a powerful and flexible logging utility with various plugins to extend its functionality. This README provides an overview of the available plugins and instructions on how to set them up.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Available Plugins
|
|
8
|
+
|
|
9
|
+
### 1. Discord
|
|
10
|
+
**Description:** Sends logs to a Discord webhook, allowing you to receive log messages directly in a specified Discord channel.
|
|
11
|
+
|
|
12
|
+
**Setup:**
|
|
13
|
+
```javascript
|
|
14
|
+
{
|
|
15
|
+
name: 'Discord',
|
|
16
|
+
config: {
|
|
17
|
+
webhookUrl: process.env.DISCORD_WEBHOOK_URL || 'https://ptb.discord.com/api/webhooks/1271901134663192658/FmzqFlzbIJ7NunR_4rpBkPLb4QQ5aHSNEbpT311su-QM3ZlGDI6sFuC4Ff0MF_TFrf3k',
|
|
18
|
+
avatarUrl: process.env.DISCORD_AVATAR_URL || 'https://pbs.twimg.com/profile_images/997535493624508416/V7Ed1k2o_400x400.jpg',
|
|
19
|
+
username: process.env.DISCORD_USERNAME || 'NTLogger - Info',
|
|
20
|
+
level: "info",
|
|
21
|
+
strict: true,
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Sentry
|
|
27
|
+
Description: Initializes the Sentry SDK for error tracking and defines a custom Winston transport for capturing and sending error logs to Sentry.
|
|
28
|
+
|
|
29
|
+
Setup:
|
|
30
|
+
|
|
31
|
+
``` javascript
|
|
32
|
+
{
|
|
33
|
+
name: 'Sentry',
|
|
34
|
+
config: {
|
|
35
|
+
dsn: process.env.SENTRY_DSN || null,
|
|
36
|
+
release: process.env.SENTRY_RELEASE || null,
|
|
37
|
+
tracesSampleRate: 1.0,
|
|
38
|
+
profilesSampleRate: 1.0,
|
|
39
|
+
environment: process.env.NODE_ENV || 'development',
|
|
40
|
+
debug: false,
|
|
41
|
+
attachStacktrace: true,
|
|
42
|
+
integrations: [
|
|
43
|
+
nodeProfilingIntegration()
|
|
44
|
+
],
|
|
45
|
+
serverName: null,
|
|
46
|
+
maxBreadcrumbs: 100,
|
|
47
|
+
autoSessionTracking: true,
|
|
48
|
+
sessionTracking: {},
|
|
49
|
+
tracesSampler: null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### . MySQL
|
|
55
|
+
Description: Stores logs in a MySQL database, allowing you to persist log messages and analyze them using SQL queries.
|
|
56
|
+
|
|
57
|
+
Setup:
|
|
58
|
+
|
|
59
|
+
``` javascript
|
|
60
|
+
{
|
|
61
|
+
name: 'MySQL',
|
|
62
|
+
config: {
|
|
63
|
+
host: process.env.MYSQL_DB_HOST || 'localhost',
|
|
64
|
+
port: process.env.MYSQL_DB_PORT || 3306,
|
|
65
|
+
user: process.env.MYSQL_DB_USER || 'root',
|
|
66
|
+
password: process.env.MYSQL_DB_PASSWORD || '',
|
|
67
|
+
database: process.env.MYSQL_DB_NAME || 'test',
|
|
68
|
+
table: process.env.MYSQL_DB_TABLE || 'logs',
|
|
69
|
+
logLevel: process.env.LOG_LEVEL || 'info',
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
````
|
|
73
|
+
### 4. Jest
|
|
74
|
+
Description: Stores log messages in memory for testing purposes, particularly useful when running tests with Jest.
|
|
75
|
+
|
|
76
|
+
Setup:
|
|
77
|
+
|
|
78
|
+
``` javascript
|
|
79
|
+
{
|
|
80
|
+
name: 'Jest',
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 5. Syslog
|
|
85
|
+
Description: Sends logs to a Syslog server using a custom Syslog client. This allows integration with traditional logging systems that rely on Syslog.
|
|
86
|
+
|
|
87
|
+
Setup:
|
|
88
|
+
|
|
89
|
+
``` javascript
|
|
90
|
+
{
|
|
91
|
+
name: 'Syslog',
|
|
92
|
+
config: {
|
|
93
|
+
host: process.env.SYSLOG_HOST || 'localhost',
|
|
94
|
+
port: process.env.SYSLOG_PORT || 514,
|
|
95
|
+
protocol: process.env.SYSLOG_PROTOCOL || 'UDP', // Options: 'UDP', 'TCP', 'TLS'
|
|
96
|
+
rfc: process.env.SYSLOG_RFC || 'RFC-5424', // Options: 'RFC-3164', 'RFC-5424'
|
|
97
|
+
facility: process.env.SYSLOG_FACILITY || 1, // Local0
|
|
98
|
+
appName: process.env.SYSLOG_APP_NAME || 'MyApp',
|
|
99
|
+
level: 'info',
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Example Logger Configuration
|
|
105
|
+
Here is an example configuration for NightTimeLogger using multiple plugins:
|
|
106
|
+
|
|
107
|
+
``` javascript
|
|
108
|
+
Copy code
|
|
109
|
+
let config = {
|
|
110
|
+
level: 'internal',
|
|
111
|
+
file: false,
|
|
112
|
+
plugins: [
|
|
113
|
+
{
|
|
114
|
+
name: 'Discord',
|
|
115
|
+
config: {
|
|
116
|
+
webhookUrl: process.env.DISCORD_WEBHOOK_URL,
|
|
117
|
+
avatarUrl: process.env.DISCORD_AVATAR_URL,
|
|
118
|
+
username: process.env.DISCORD_USERNAME || 'MyAppLogger',
|
|
119
|
+
level: 'info',
|
|
120
|
+
strict: false,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'Sentry',
|
|
125
|
+
config: {
|
|
126
|
+
dsn: process.env.SENTRY_DSN,
|
|
127
|
+
release: process.env.SENTRY_RELEASE,
|
|
128
|
+
tracesSampleRate: 1.0,
|
|
129
|
+
environment: process.env.NODE_ENV || 'development',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'MySQL',
|
|
134
|
+
config: {
|
|
135
|
+
host: process.env.MYSQL_DB_HOST,
|
|
136
|
+
port: process.env.MYSQL_DB_PORT || 3306,
|
|
137
|
+
user: process.env.MYSQL_DB_USER || 'root',
|
|
138
|
+
password: process.env.MYSQL_DB_PASSWORD || '',
|
|
139
|
+
database: process.env.MYSQL_DB_NAME || 'logs',
|
|
140
|
+
table: 'log_entries',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'Jest',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'Syslog',
|
|
148
|
+
config: {
|
|
149
|
+
host: process.env.SYSLOG_HOST || 'localhost',
|
|
150
|
+
port: process.env.SYSLOG_PORT || 514,
|
|
151
|
+
protocol: 'UDP',
|
|
152
|
+
facility: 1,
|
|
153
|
+
appName: 'MyAppLogger',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Conclusion
|
|
161
|
+
The NightTimeLogger is highly customizable and supports various plugins for different logging requirements. By leveraging these plugins, you can easily integrate logging into your existing infrastructure, whether it be storing logs in databases, sending them to monitoring tools like Sentry, or receiving alerts directly in Discord.
|
|
162
|
+
|
|
163
|
+
For more details on each plugin, refer to the specific plugin files in the project.
|
|
@@ -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
|
|
10
|
-
Jest: require('./jest'),
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
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
|
-
}
|
|
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,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file /plugins/lib/syslogClient.js
|
|
3
|
+
* @description Custom Syslog client supporting UDP, TCP, and TLS transports with RFC-3164 and RFC-5424 formats.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const net = require('net');
|
|
7
|
+
const tls = require('tls');
|
|
8
|
+
const dgram = require('dgram');
|
|
9
|
+
|
|
10
|
+
class SyslogClient {
|
|
11
|
+
constructor(opts = {}) {
|
|
12
|
+
this.host = opts.host || 'localhost';
|
|
13
|
+
this.port = opts.port || 514;
|
|
14
|
+
this.protocol = opts.protocol || 'UDP'; // 'UDP', 'TCP', 'TLS'
|
|
15
|
+
this.rfc = opts.rfc || 'RFC-5424'; // 'RFC-3164', 'RFC-5424'
|
|
16
|
+
this.facility = opts.facility || 1; // Default to user-level messages
|
|
17
|
+
this.appName = opts.appName || 'NTLogger';
|
|
18
|
+
this.hostname = opts.hostname || require('os').hostname();
|
|
19
|
+
this.transport = null;
|
|
20
|
+
|
|
21
|
+
if (this.protocol === 'UDP') {
|
|
22
|
+
this.transport = dgram.createSocket('udp4');
|
|
23
|
+
} else if (this.protocol === 'TCP') {
|
|
24
|
+
this.transport = new net.Socket();
|
|
25
|
+
} else if (this.protocol === 'TLS') {
|
|
26
|
+
this.transport = new tls.TLSSocket();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Connects to the Syslog server if using TCP or TLS.
|
|
32
|
+
*/
|
|
33
|
+
connect() {
|
|
34
|
+
if (this.protocol === 'TCP' || this.protocol === 'TLS') {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const options = { host: this.host, port: this.port };
|
|
37
|
+
const connectListener = () => resolve();
|
|
38
|
+
const errorListener = (err) => reject(err);
|
|
39
|
+
|
|
40
|
+
if (this.protocol === 'TCP') {
|
|
41
|
+
this.transport.connect(options, connectListener).on('error', errorListener);
|
|
42
|
+
} else if (this.protocol === 'TLS') {
|
|
43
|
+
this.transport.connect(options, connectListener).on('error', errorListener);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sends a log message to the Syslog server.
|
|
51
|
+
* @param {string} severity - The severity level of the log.
|
|
52
|
+
* @param {string} message - The log message.
|
|
53
|
+
* @param {object} [meta] - Additional metadata for RFC-5424 structured data.
|
|
54
|
+
*/
|
|
55
|
+
send(severity, message, meta = {}) {
|
|
56
|
+
const formattedMessage = this.formatMessage(severity, message, meta);
|
|
57
|
+
if (this.protocol === 'UDP') {
|
|
58
|
+
this.transport.send(Buffer.from(formattedMessage), 0, formattedMessage.length, this.port, this.host);
|
|
59
|
+
} else {
|
|
60
|
+
this.transport.write(formattedMessage + '\n');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Formats the Syslog message according to the selected RFC standard.
|
|
66
|
+
* @param {string} severity - The severity level of the log.
|
|
67
|
+
* @param {string} message - The log message.
|
|
68
|
+
* @param {object} [meta] - Additional metadata for RFC-5424 structured data.
|
|
69
|
+
* @returns {string} - The formatted Syslog message.
|
|
70
|
+
*/
|
|
71
|
+
formatMessage(severity, message, meta) {
|
|
72
|
+
const timestamp = new Date().toISOString();
|
|
73
|
+
const priority = (this.facility * 8) + severity;
|
|
74
|
+
|
|
75
|
+
if (this.rfc === 'RFC-3164') {
|
|
76
|
+
return `<${priority}>${timestamp} ${this.hostname} ${this.appName}: ${message}`;
|
|
77
|
+
} else if (this.rfc === 'RFC-5424') {
|
|
78
|
+
const structuredData = this.formatStructuredData(meta.structuredData || {});
|
|
79
|
+
return `<${priority}>1 ${timestamp} ${this.hostname} ${this.appName} - - ${structuredData} ${message}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Formats the structured data for RFC-5424.
|
|
85
|
+
* @param {object} structuredData - The structured data to include in the log message.
|
|
86
|
+
* @returns {string} - The formatted structured data string.
|
|
87
|
+
*/
|
|
88
|
+
formatStructuredData(structuredData) {
|
|
89
|
+
let sdString = '';
|
|
90
|
+
for (const [key, value] of Object.entries(structuredData)) {
|
|
91
|
+
sdString += `[${key}`;
|
|
92
|
+
for (const [param, val] of Object.entries(value)) {
|
|
93
|
+
sdString += ` ${param}="${val}"`;
|
|
94
|
+
}
|
|
95
|
+
sdString += ']';
|
|
96
|
+
}
|
|
97
|
+
return sdString || '-';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Closes the transport connection.
|
|
102
|
+
*/
|
|
103
|
+
close() {
|
|
104
|
+
if (this.transport && (this.protocol === 'TCP' || this.protocol === 'TLS')) {
|
|
105
|
+
this.transport.end();
|
|
106
|
+
} else if (this.protocol === 'UDP') {
|
|
107
|
+
this.transport.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = SyslogClient;
|
|
@@ -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
|
+
};
|