lumisjs 1.0.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/LICENSE +21 -0
- package/README.md +274 -0
- package/SECURITY.md +160 -0
- package/index.js +90 -0
- package/package.json +68 -0
- package/src/cache/CacheManager.js +393 -0
- package/src/cache/MemoryAdapter.js +259 -0
- package/src/cache/MultiLevelCache.js +362 -0
- package/src/cache/RedisAdapter.js +329 -0
- package/src/cache/SQLiteAdapter.js +280 -0
- package/src/cache/index.js +15 -0
- package/src/client/Client.js +554 -0
- package/src/client/ShardingManager.js +274 -0
- package/src/config/ConfigValidator.js +172 -0
- package/src/config/index.js +7 -0
- package/src/dashboard/DashboardManager.js +362 -0
- package/src/datagen/DataGenerator.js +47 -0
- package/src/datagen/apis/GraphqlMocker.js +16 -0
- package/src/datagen/apis/OpenApiMocker.js +18 -0
- package/src/datagen/cli.js +130 -0
- package/src/datagen/formatters/csv.js +45 -0
- package/src/datagen/formatters/index.js +15 -0
- package/src/datagen/formatters/json.js +33 -0
- package/src/datagen/formatters/mongo.js +22 -0
- package/src/datagen/formatters/sql.js +32 -0
- package/src/datagen/index.js +17 -0
- package/src/datagen/plugin/PluginManager.js +43 -0
- package/src/datagen/plugins/example-plugin.js +21 -0
- package/src/datagen/schema/SchemaGenerator.js +172 -0
- package/src/datagen/schema/loadSchema.js +14 -0
- package/src/datagen/utils/seed.js +26 -0
- package/src/di/ServiceContainer.js +229 -0
- package/src/errors/ErrorCodes.js +110 -0
- package/src/errors/LumisError.js +85 -0
- package/src/game/EconomyManager.js +161 -0
- package/src/game/GameSessionManager.js +76 -0
- package/src/game/GuildManager.js +197 -0
- package/src/game/InventoryManager.js +140 -0
- package/src/game/LevelingSystem.js +194 -0
- package/src/game/MusicManager.js +587 -0
- package/src/health/HealthChecker.js +170 -0
- package/src/health/index.js +7 -0
- package/src/performance/PerformanceMonitor.js +244 -0
- package/src/performance/index.js +7 -0
- package/src/security/InputSanitizer.js +185 -0
- package/src/security/SecurityMiddleware.js +231 -0
- package/src/security/index.js +9 -0
- package/src/shutdown/GracefulShutdown.js +159 -0
- package/src/shutdown/index.js +7 -0
- package/src/utils/StructuredLogger.js +118 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events');
|
|
4
|
+
const InputSanitizer = require('./InputSanitizer');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Security Middleware
|
|
8
|
+
* Provides security checks and sanitization for incoming requests and data.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class SecurityMiddleware extends EventEmitter {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this.options = {
|
|
16
|
+
maxRequestSize: options.maxRequestSize || 1024 * 1024, // 1MB
|
|
17
|
+
rateLimitWindow: options.rateLimitWindow || 60000, // 1 minute
|
|
18
|
+
rateLimitMax: options.rateLimitMax || 100,
|
|
19
|
+
blockSuspiciousPatterns: options.blockSuspiciousPatterns !== false,
|
|
20
|
+
...options,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this._rateLimits = new Map();
|
|
24
|
+
this._blockedIPs = new Set();
|
|
25
|
+
this._suspiciousPatterns = [
|
|
26
|
+
/<script/i,
|
|
27
|
+
/javascript:/i,
|
|
28
|
+
/data:/i,
|
|
29
|
+
/on\w+\s*=/i,
|
|
30
|
+
/exec\(/i,
|
|
31
|
+
/system\(/i,
|
|
32
|
+
/\.\.\//i,
|
|
33
|
+
/<iframe/i,
|
|
34
|
+
/<object/i,
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a request should be rate limited.
|
|
40
|
+
* @param {string} identifier - IP address or user ID
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
isRateLimited(identifier) {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const windowStart = now - this.options.rateLimitWindow;
|
|
46
|
+
|
|
47
|
+
let requests = this._rateLimits.get(identifier) || [];
|
|
48
|
+
|
|
49
|
+
// Remove old requests outside the window
|
|
50
|
+
requests = requests.filter(time => time > windowStart);
|
|
51
|
+
|
|
52
|
+
if (requests.length >= this.options.rateLimitMax) {
|
|
53
|
+
this.emit('rateLimit', { identifier, count: requests.length });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add current request
|
|
58
|
+
requests.push(now);
|
|
59
|
+
this._rateLimits.set(identifier, requests);
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Block an IP address.
|
|
66
|
+
* @param {string} ip
|
|
67
|
+
* @param {number} [duration] - Duration in milliseconds (0 = permanent)
|
|
68
|
+
*/
|
|
69
|
+
blockIP(ip, duration = 0) {
|
|
70
|
+
this._blockedIPs.add(ip);
|
|
71
|
+
this.emit('ipBlocked', { ip, duration });
|
|
72
|
+
|
|
73
|
+
if (duration > 0) {
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
this._blockedIPs.delete(ip);
|
|
76
|
+
this.emit('ipUnblocked', { ip });
|
|
77
|
+
}, duration);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if an IP is blocked.
|
|
83
|
+
* @param {string} ip
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
isIPBlocked(ip) {
|
|
87
|
+
return this._blockedIPs.has(ip);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sanitize and validate input data.
|
|
92
|
+
* @param {any} data
|
|
93
|
+
* @param {object} [options]
|
|
94
|
+
* @returns {any}
|
|
95
|
+
*/
|
|
96
|
+
sanitizeInput(data, options = {}) {
|
|
97
|
+
if (typeof data === 'string') {
|
|
98
|
+
return InputSanitizer.sanitize(data, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof data === 'object' && data !== null) {
|
|
102
|
+
return InputSanitizer.sanitizeObject(data, options);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check for suspicious patterns in input.
|
|
110
|
+
* @param {string} input
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
containsSuspiciousPatterns(input) {
|
|
114
|
+
if (!this.options.blockSuspiciousPatterns) return false;
|
|
115
|
+
|
|
116
|
+
if (typeof input !== 'string') return false;
|
|
117
|
+
|
|
118
|
+
for (const pattern of this._suspiciousPatterns) {
|
|
119
|
+
if (pattern.test(input)) {
|
|
120
|
+
this.emit('suspiciousPattern', { input, pattern: pattern.source });
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validate request size.
|
|
130
|
+
* @param {number} size
|
|
131
|
+
* @returns {boolean}
|
|
132
|
+
*/
|
|
133
|
+
isValidRequestSize(size) {
|
|
134
|
+
if (size > this.options.maxRequestSize) {
|
|
135
|
+
this.emit('requestTooLarge', { size, max: this.options.maxRequestSize });
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a middleware function for Express-like frameworks.
|
|
143
|
+
* @returns {Function}
|
|
144
|
+
*/
|
|
145
|
+
createMiddleware() {
|
|
146
|
+
return (req, res, next) => {
|
|
147
|
+
const identifier = req.ip || req.connection.remoteAddress;
|
|
148
|
+
|
|
149
|
+
// Check IP block
|
|
150
|
+
if (this.isIPBlocked(identifier)) {
|
|
151
|
+
return res.status(403).json({ error: 'IP blocked' });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check rate limit
|
|
155
|
+
if (this.isRateLimited(identifier)) {
|
|
156
|
+
return res.status(429).json({ error: 'Rate limit exceeded' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check request size
|
|
160
|
+
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
161
|
+
if (!this.isValidRequestSize(contentLength)) {
|
|
162
|
+
return res.status(413).json({ error: 'Request too large' });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Sanitize body if present
|
|
166
|
+
if (req.body) {
|
|
167
|
+
try {
|
|
168
|
+
req.body = this.sanitizeInput(req.body);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
this.emit('sanitizationError', { error: err });
|
|
171
|
+
return res.status(400).json({ error: 'Invalid input' });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for suspicious patterns
|
|
176
|
+
if (req.body && this.containsSuspiciousPatterns(JSON.stringify(req.body))) {
|
|
177
|
+
this.emit('suspiciousRequest', { body: req.body });
|
|
178
|
+
return res.status(400).json({ error: 'Suspicious input detected' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
next();
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Clean up rate limit data.
|
|
187
|
+
*/
|
|
188
|
+
cleanup() {
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const windowStart = now - this.options.rateLimitWindow;
|
|
191
|
+
|
|
192
|
+
for (const [identifier, requests] of this._rateLimits) {
|
|
193
|
+
const filtered = requests.filter(time => time > windowStart);
|
|
194
|
+
if (filtered.length === 0) {
|
|
195
|
+
this._rateLimits.delete(identifier);
|
|
196
|
+
} else {
|
|
197
|
+
this._rateLimits.set(identifier, filtered);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get security statistics.
|
|
204
|
+
* @returns {object}
|
|
205
|
+
*/
|
|
206
|
+
getStats() {
|
|
207
|
+
return {
|
|
208
|
+
blockedIPs: this._blockedIPs.size,
|
|
209
|
+
rateLimitedIdentifiers: this._rateLimits.size,
|
|
210
|
+
totalRequests: Array.from(this._rateLimits.values()).reduce((sum, reqs) => sum + reqs.length, 0),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Reset all security data.
|
|
216
|
+
*/
|
|
217
|
+
reset() {
|
|
218
|
+
this._rateLimits.clear();
|
|
219
|
+
this._blockedIPs.clear();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Destroy the middleware.
|
|
224
|
+
*/
|
|
225
|
+
destroy() {
|
|
226
|
+
this.reset();
|
|
227
|
+
this.removeAllListeners();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = SecurityMiddleware;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events');
|
|
4
|
+
const Logger = require('../utils/Logger');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Graceful Shutdown Handler
|
|
8
|
+
* Handles graceful shutdown of the application with proper cleanup.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class GracefulShutdown extends EventEmitter {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this.options = {
|
|
16
|
+
timeout: options.timeout || 30000, // 30 seconds
|
|
17
|
+
signals: options.signals || ['SIGINT', 'SIGTERM'],
|
|
18
|
+
...options,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
this.logger = new Logger({ prefix: 'Shutdown', level: 'info' });
|
|
22
|
+
|
|
23
|
+
this.shutdownHandlers = new Map();
|
|
24
|
+
this.isShuttingDown = false;
|
|
25
|
+
this._setupSignalHandlers();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register a shutdown handler.
|
|
30
|
+
* @param {string} name
|
|
31
|
+
* @param {Function} handler - Async function to run during shutdown
|
|
32
|
+
* @param {number} [priority=0] - Higher priority runs first
|
|
33
|
+
*/
|
|
34
|
+
register(name, handler, priority = 0) {
|
|
35
|
+
if (typeof handler !== 'function') {
|
|
36
|
+
throw new TypeError('Shutdown handler must be a function');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.shutdownHandlers.set(name, { handler, priority });
|
|
40
|
+
this.logger.info(`Registered shutdown handler: ${name} (priority: ${priority})`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Unregister a shutdown handler.
|
|
45
|
+
* @param {string} name
|
|
46
|
+
*/
|
|
47
|
+
unregister(name) {
|
|
48
|
+
this.shutdownHandlers.delete(name);
|
|
49
|
+
this.logger.info(`Unregistered shutdown handler: ${name}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Setup signal handlers for graceful shutdown.
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
_setupSignalHandlers() {
|
|
57
|
+
for (const signal of this.options.signals) {
|
|
58
|
+
process.on(signal, () => {
|
|
59
|
+
this.logger.info(`Received ${signal} signal`);
|
|
60
|
+
this.shutdown(signal);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initiate graceful shutdown.
|
|
67
|
+
* @param {string} [reason='manual']
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
async shutdown(reason = 'manual') {
|
|
71
|
+
if (this.isShuttingDown) {
|
|
72
|
+
this.logger.warn('Shutdown already in progress');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.isShuttingDown = true;
|
|
77
|
+
this.logger.info(`Initiating graceful shutdown: ${reason}`);
|
|
78
|
+
this.emit('shutdown', { reason });
|
|
79
|
+
|
|
80
|
+
// Sort handlers by priority (higher first)
|
|
81
|
+
const sortedHandlers = Array.from(this.shutdownHandlers.entries())
|
|
82
|
+
.sort(([, a], [, b]) => b.priority - a.priority);
|
|
83
|
+
|
|
84
|
+
// Run all shutdown handlers with timeout
|
|
85
|
+
const shutdownPromises = sortedHandlers.map(([name, { handler }]) => {
|
|
86
|
+
return Promise.race([
|
|
87
|
+
handler().catch(error => {
|
|
88
|
+
this.logger.error(`Shutdown handler failed: ${name}`, error);
|
|
89
|
+
throw error;
|
|
90
|
+
}),
|
|
91
|
+
new Promise((_, reject) =>
|
|
92
|
+
setTimeout(() => reject(new Error('Handler timeout')), this.options.timeout)
|
|
93
|
+
),
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await Promise.allSettled(shutdownPromises);
|
|
99
|
+
this.logger.info('All shutdown handlers completed');
|
|
100
|
+
this.emit('complete');
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.logger.error('Error during shutdown', error);
|
|
103
|
+
this.emit('error', error);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Force exit after timeout
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
this.logger.warn('Forcing exit after timeout');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}, this.options.timeout + 1000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Abort shutdown (if not too late).
|
|
115
|
+
*/
|
|
116
|
+
abort() {
|
|
117
|
+
if (!this.isShuttingDown) {
|
|
118
|
+
this.logger.warn('No shutdown in progress to abort');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.logger.warn('Shutdown abort requested (may not be possible)');
|
|
123
|
+
this.emit('abort');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if shutdown is in progress.
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
isShuttingDown() {
|
|
131
|
+
return this.isShuttingDown;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get registered handlers.
|
|
136
|
+
* @returns {Array}
|
|
137
|
+
*/
|
|
138
|
+
getHandlers() {
|
|
139
|
+
return Array.from(this.shutdownHandlers.entries()).map(([name, { priority }]) => ({
|
|
140
|
+
name,
|
|
141
|
+
priority,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Destroy the shutdown handler.
|
|
147
|
+
*/
|
|
148
|
+
destroy() {
|
|
149
|
+
this.shutdownHandlers.clear();
|
|
150
|
+
this.removeAllListeners();
|
|
151
|
+
|
|
152
|
+
// Remove signal handlers
|
|
153
|
+
for (const signal of this.options.signals) {
|
|
154
|
+
process.removeAllListeners(signal);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = GracefulShutdown;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const winston = require('winston');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Structured Logger
|
|
7
|
+
* Provides structured logging with winston.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class StructuredLogger {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
const {
|
|
13
|
+
level = options.level || 'info',
|
|
14
|
+
service = options.service || 'lumis',
|
|
15
|
+
environment = options.environment || 'development',
|
|
16
|
+
...winstonOptions
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
this.logger = winston.createLogger({
|
|
20
|
+
level,
|
|
21
|
+
format: winston.format.combine(
|
|
22
|
+
winston.format.timestamp(),
|
|
23
|
+
winston.format.errors({ stack: true }),
|
|
24
|
+
winston.format.splat(),
|
|
25
|
+
winston.format.json()
|
|
26
|
+
),
|
|
27
|
+
defaultMeta: { service, environment },
|
|
28
|
+
transports: [
|
|
29
|
+
new winston.transports.Console({
|
|
30
|
+
format: winston.format.combine(
|
|
31
|
+
winston.format.colorize(),
|
|
32
|
+
winston.format.printf(({ timestamp, level, message, service, environment, ...meta }) => {
|
|
33
|
+
const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : '';
|
|
34
|
+
return `${timestamp} [${service}] [${environment}] ${level}: ${message} ${metaStr}`;
|
|
35
|
+
})
|
|
36
|
+
),
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
...winstonOptions,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Add file transport in production
|
|
43
|
+
if (environment === 'production') {
|
|
44
|
+
this.logger.add(new winston.transports.File({ filename: 'error.log', level: 'error' }));
|
|
45
|
+
this.logger.add(new winston.transports.File({ filename: 'combined.log' }));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Log an error.
|
|
51
|
+
* @param {string} message
|
|
52
|
+
* @param {object} [meta]
|
|
53
|
+
*/
|
|
54
|
+
error(message, meta) {
|
|
55
|
+
this.logger.error(message, meta);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Log a warning.
|
|
60
|
+
* @param {string} message
|
|
61
|
+
* @param {object} [meta]
|
|
62
|
+
*/
|
|
63
|
+
warn(message, meta) {
|
|
64
|
+
this.logger.warn(message, meta);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Log info.
|
|
69
|
+
* @param {string} message
|
|
70
|
+
* @param {object} [meta]
|
|
71
|
+
*/
|
|
72
|
+
info(message, meta) {
|
|
73
|
+
this.logger.info(message, meta);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Log debug.
|
|
78
|
+
* @param {string} message
|
|
79
|
+
* @param {object} [meta]
|
|
80
|
+
*/
|
|
81
|
+
debug(message, meta) {
|
|
82
|
+
this.logger.debug(message, meta);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Log verbose.
|
|
87
|
+
* @param {string} message
|
|
88
|
+
* @param {object} [meta]
|
|
89
|
+
*/
|
|
90
|
+
verbose(message, meta) {
|
|
91
|
+
this.logger.verbose(message, meta);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create a child logger with additional metadata.
|
|
96
|
+
* @param {object} meta
|
|
97
|
+
* @returns {StructuredLogger}
|
|
98
|
+
*/
|
|
99
|
+
child(meta) {
|
|
100
|
+
const childLogger = new StructuredLogger({
|
|
101
|
+
level: this.logger.level,
|
|
102
|
+
service: this.logger.defaultMeta.service,
|
|
103
|
+
environment: this.logger.defaultMeta.environment,
|
|
104
|
+
});
|
|
105
|
+
childLogger.logger.defaultMeta = { ...this.logger.defaultMeta, ...meta };
|
|
106
|
+
return childLogger;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the underlying winston logger.
|
|
111
|
+
* @returns {winston.Logger}
|
|
112
|
+
*/
|
|
113
|
+
getLogger() {
|
|
114
|
+
return this.logger;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = StructuredLogger;
|