@nest-omni/core 4.1.3-29 → 4.1.3-30
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/audit/interceptors/audit-action.interceptor.js +1 -1
- package/audit/services/audit-action.service.js +1 -1
- package/audit/services/operation-description.service.js +1 -1
- package/audit/services/transaction-audit.service.js +1 -1
- package/email-log/email-log.constants.d.ts +8 -0
- package/email-log/email-log.constants.js +11 -0
- package/email-log/email-log.module.d.ts +47 -0
- package/email-log/email-log.module.js +140 -0
- package/email-log/index.d.ts +11 -0
- package/email-log/index.js +48 -0
- package/email-log/interfaces/email-log-options.interface.d.ts +61 -0
- package/email-log/interfaces/email-log-options.interface.js +134 -0
- package/email-log/interfaces/email-log-transport.interface.d.ts +20 -0
- package/email-log/interfaces/email-log-transport.interface.js +2 -0
- package/email-log/interfaces/index.d.ts +2 -0
- package/email-log/interfaces/index.js +18 -0
- package/email-log/providers/email-provider.d.ts +42 -0
- package/email-log/providers/email-provider.js +127 -0
- package/email-log/providers/index.d.ts +1 -0
- package/email-log/providers/index.js +17 -0
- package/email-log/services/email-log-alert.service.d.ts +46 -0
- package/email-log/services/email-log-alert.service.js +162 -0
- package/email-log/services/email-log-formatter.service.d.ts +78 -0
- package/email-log/services/email-log-formatter.service.js +442 -0
- package/email-log/services/email-log-logger.service.d.ts +85 -0
- package/email-log/services/email-log-logger.service.js +168 -0
- package/email-log/services/email-log-rate-limiter.service.d.ts +42 -0
- package/email-log/services/email-log-rate-limiter.service.js +110 -0
- package/email-log/services/email-log-transport.service.d.ts +80 -0
- package/email-log/services/email-log-transport.service.js +271 -0
- package/email-log/services/index.d.ts +5 -0
- package/email-log/services/index.js +21 -0
- package/email-log/transports/index.d.ts +1 -0
- package/email-log/transports/index.js +17 -0
- package/email-log/transports/pino-email.transport.d.ts +56 -0
- package/email-log/transports/pino-email.transport.js +188 -0
- package/email-log/utils/index.d.ts +2 -0
- package/email-log/utils/index.js +18 -0
- package/email-log/utils/log-level.helper.d.ts +46 -0
- package/email-log/utils/log-level.helper.js +74 -0
- package/email-log/utils/pino-transport.utils.d.ts +135 -0
- package/email-log/utils/pino-transport.utils.js +238 -0
- package/filters/bad-request.filter.d.ts +9 -0
- package/filters/bad-request.filter.js +38 -12
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +3 -1
- package/setup/bootstrap.setup.js +1 -1
- package/shared/service-registry.module.js +14 -0
- package/shared/services/api-config.service.d.ts +41 -0
- package/shared/services/api-config.service.js +163 -6
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createEmailTransport = createEmailTransport;
|
|
13
|
+
exports.createPinoMultistream = createPinoMultistream;
|
|
14
|
+
exports.createPinoEmailLogger = createPinoEmailLogger;
|
|
15
|
+
exports.attachEmailTransportToLogger = attachEmailTransportToLogger;
|
|
16
|
+
exports.createPinoEmailChildLogger = createPinoEmailChildLogger;
|
|
17
|
+
exports.createEmailTransportFromEnv = createEmailTransportFromEnv;
|
|
18
|
+
const pino_1 = require("pino");
|
|
19
|
+
const pino_email_transport_1 = require("../transports/pino-email.transport");
|
|
20
|
+
/**
|
|
21
|
+
* Create a Pino transport stream for email logging
|
|
22
|
+
* This function creates a writable stream that can be used with Pino
|
|
23
|
+
*
|
|
24
|
+
* @param options Email log options
|
|
25
|
+
* @returns A Pino transport configuration
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { createEmailTransport } from '@nest-omni/core';
|
|
30
|
+
* import pino from 'pino';
|
|
31
|
+
*
|
|
32
|
+
* const transport = createEmailTransport({
|
|
33
|
+
* enabled: true,
|
|
34
|
+
* to: ['admin@example.com'],
|
|
35
|
+
* from: 'noreply@example.com',
|
|
36
|
+
* smtpHost: 'smtp.example.com',
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* const logger = pino(transport);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function createEmailTransport(options) {
|
|
43
|
+
return new pino_email_transport_1.PinoEmailTransport(options);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a Pino multistream configuration that includes email transport
|
|
47
|
+
*
|
|
48
|
+
* @param options Email log options
|
|
49
|
+
* @param emailTransport The email transport service instance or PinoEmailTransport
|
|
50
|
+
* @returns Pino multistream targets array
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import { createPinoMultistream } from '@nest-omni/core';
|
|
55
|
+
* import pino from 'pino';
|
|
56
|
+
*
|
|
57
|
+
* const streams = createPinoMultistream(options, emailTransport);
|
|
58
|
+
* const logger = pino(pino.multistream(streams));
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
function createPinoMultistream(options, emailTransport) {
|
|
62
|
+
const streams = [
|
|
63
|
+
// Default stdout stream - log everything to console
|
|
64
|
+
{
|
|
65
|
+
level: 'trace',
|
|
66
|
+
stream: process.stdout,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
// Add email transport if enabled
|
|
70
|
+
if (options.enabled) {
|
|
71
|
+
streams.push({
|
|
72
|
+
level: options.level,
|
|
73
|
+
stream: emailTransport, // Both EmailLogTransportService and PinoEmailTransport are writable streams
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return streams;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a Pino logger with email transport already configured
|
|
80
|
+
* This is the simplest way to get started with email logging
|
|
81
|
+
*
|
|
82
|
+
* @param options Email log options
|
|
83
|
+
* @param baseLogger Optional base Pino logger to extend
|
|
84
|
+
* @returns Pino logger with email transport
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { createPinoEmailLogger } from '@nest-omni/core';
|
|
89
|
+
*
|
|
90
|
+
* const logger = createPinoEmailLogger({
|
|
91
|
+
* enabled: true,
|
|
92
|
+
* to: ['admin@example.com'],
|
|
93
|
+
* from: 'noreply@example.com',
|
|
94
|
+
* smtpHost: 'smtp.example.com',
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* logger.error('Something went wrong!', { userId: '123' });
|
|
98
|
+
* // This will log to console AND send an email
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
function createPinoEmailLogger(options, baseLogger) {
|
|
102
|
+
const transport = createEmailTransport(options);
|
|
103
|
+
if (!options.enabled) {
|
|
104
|
+
// If disabled, return a basic logger
|
|
105
|
+
return baseLogger || (0, pino_1.default)({ level: options.level });
|
|
106
|
+
}
|
|
107
|
+
const streams = createPinoMultistream(options, transport);
|
|
108
|
+
if (baseLogger) {
|
|
109
|
+
// Extend existing logger with email transport
|
|
110
|
+
return (0, pino_1.default)(Object.assign({}, baseLogger.options), pino_1.default.multistream(streams));
|
|
111
|
+
}
|
|
112
|
+
// Create new logger with email transport
|
|
113
|
+
return (0, pino_1.default)({
|
|
114
|
+
level: 'trace', // Log everything to console
|
|
115
|
+
}, pino_1.default.multistream(streams));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Manually attach email transport to an existing Pino logger
|
|
119
|
+
* Use this if you want to add email logging to a specific logger instance
|
|
120
|
+
*
|
|
121
|
+
* @param logger The Pino logger instance
|
|
122
|
+
* @param options Email log options
|
|
123
|
+
* @param emailTransport The email transport service instance
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* import { attachEmailTransportToLogger } from '@nest-omni/core';
|
|
128
|
+
*
|
|
129
|
+
* attachEmailTransportToLogger(myLogger, options, emailTransport);
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
function attachEmailTransportToLogger(logger, options, emailTransport) {
|
|
133
|
+
var _a;
|
|
134
|
+
const minLevel = pino_1.default.levels.values[options.level] || pino_1.default.levels.values.error || 50;
|
|
135
|
+
// Store original write method
|
|
136
|
+
const loggerInstance = logger;
|
|
137
|
+
const originalFlush = (_a = loggerInstance.flush) === null || _a === void 0 ? void 0 : _a.bind(loggerInstance);
|
|
138
|
+
// Override flush to include email transport flush
|
|
139
|
+
if (originalFlush) {
|
|
140
|
+
loggerInstance.flush = (cb) => __awaiter(this, void 0, void 0, function* () {
|
|
141
|
+
yield originalFlush(cb);
|
|
142
|
+
if (typeof emailTransport.flush === 'function') {
|
|
143
|
+
yield emailTransport.flush();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// Add the email transport as a child stream
|
|
148
|
+
const transport = {
|
|
149
|
+
write: (data) => {
|
|
150
|
+
const level = typeof data.level === 'number'
|
|
151
|
+
? data.level
|
|
152
|
+
: pino_1.default.levels.values[data.level] || 30;
|
|
153
|
+
if (level >= minLevel) {
|
|
154
|
+
emailTransport.write(data);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
// Try to use pino.multistream if available
|
|
159
|
+
try {
|
|
160
|
+
const streams = createPinoMultistream(options, emailTransport);
|
|
161
|
+
const newLogger = (0, pino_1.default)(pino_1.default.multistream(streams));
|
|
162
|
+
// Copy all properties from old logger to new one
|
|
163
|
+
Object.assign(loggerInstance, newLogger);
|
|
164
|
+
}
|
|
165
|
+
catch (_b) {
|
|
166
|
+
// pino.multistream not available, use manual approach
|
|
167
|
+
emailTransport._loggerTransport = transport;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create a child logger with email context
|
|
172
|
+
* Useful for adding consistent metadata to all logs from a module
|
|
173
|
+
*
|
|
174
|
+
* @param parentLogger The parent Pino logger
|
|
175
|
+
* @param context Context object to include in all logs
|
|
176
|
+
* @returns Child Pino logger
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* import { createPinoEmailLogger, createPinoEmailChildLogger } from '@nest-omni/core';
|
|
181
|
+
*
|
|
182
|
+
* const baseLogger = createPinoEmailLogger(options);
|
|
183
|
+
* const userServiceLogger = createPinoEmailChildLogger(baseLogger, {
|
|
184
|
+
* module: 'UserService',
|
|
185
|
+
* version: '1.0.0',
|
|
186
|
+
* });
|
|
187
|
+
*
|
|
188
|
+
* userServiceLogger.error('User creation failed', { userId: '123' });
|
|
189
|
+
* // Email will include: module, version, userId in context
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
function createPinoEmailChildLogger(parentLogger, context) {
|
|
193
|
+
return parentLogger.child(context);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Configure Pino email transport from environment variables
|
|
197
|
+
* This is a convenience function that reads from process.env
|
|
198
|
+
*
|
|
199
|
+
* @returns PinoEmailTransport instance or null if not configured
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* import { createEmailTransportFromEnv } from '@nest-omni/core';
|
|
204
|
+
* import pino from 'pino';
|
|
205
|
+
*
|
|
206
|
+
* const emailTransport = createEmailTransportFromEnv();
|
|
207
|
+
* const logger = emailTransport
|
|
208
|
+
* ? pino(pino.multistream([
|
|
209
|
+
* { level: 'trace', stream: process.stdout },
|
|
210
|
+
* { level: 'error', stream: emailTransport },
|
|
211
|
+
* ]))
|
|
212
|
+
* : pino();
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
function createEmailTransportFromEnv() {
|
|
216
|
+
const enabled = process.env.EMAIL_LOG_ENABLED === 'true';
|
|
217
|
+
if (!enabled) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const options = {
|
|
221
|
+
enabled: true,
|
|
222
|
+
to: (process.env.EMAIL_LOG_TO || '').split(',').map(e => e.trim()).filter(Boolean),
|
|
223
|
+
from: process.env.EMAIL_LOG_FROM || 'noreply@example.com',
|
|
224
|
+
level: process.env.EMAIL_LOG_LEVEL || 'error',
|
|
225
|
+
subjectPrefix: process.env.EMAIL_LOG_SUBJECT_PREFIX || '[Log Alert]',
|
|
226
|
+
smtpHost: process.env.EMAIL_LOG_SMTP_HOST || 'localhost',
|
|
227
|
+
smtpPort: parseInt(process.env.EMAIL_LOG_SMTP_PORT || '587'),
|
|
228
|
+
smtpUsername: process.env.EMAIL_LOG_SMTP_USERNAME || '',
|
|
229
|
+
smtpPassword: process.env.EMAIL_LOG_SMTP_PASSWORD || '',
|
|
230
|
+
smtpSecure: process.env.EMAIL_LOG_SMTP_SECURE === 'true',
|
|
231
|
+
smtpIgnoreTLSError: process.env.EMAIL_LOG_SMTP_IGNORE_TLS_ERROR === 'true',
|
|
232
|
+
rateLimitMaxEmails: parseInt(process.env.EMAIL_LOG_RATE_LIMIT_MAX_EMAILS || '10'),
|
|
233
|
+
rateLimitWindowMs: parseInt(process.env.EMAIL_LOG_RATE_LIMIT_WINDOW_MS || '60000'),
|
|
234
|
+
rateLimitBurstSize: parseInt(process.env.EMAIL_LOG_RATE_LIMIT_BURST_SIZE || '3'),
|
|
235
|
+
useHtmlFormat: process.env.EMAIL_LOG_USE_HTML_FORMAT !== 'false',
|
|
236
|
+
};
|
|
237
|
+
return new pino_email_transport_1.PinoEmailTransport(options);
|
|
238
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
|
|
2
2
|
import { Response } from 'express';
|
|
3
|
+
import type { Logger } from 'nestjs-pino';
|
|
4
|
+
/**
|
|
5
|
+
* HTTP Exception Filter
|
|
6
|
+
*
|
|
7
|
+
* Catches all exceptions and formats the response
|
|
8
|
+
* For 500 errors, logs via Pino Logger which will automatically send email alerts if EMAIL_LOG_ENABLED=true
|
|
9
|
+
*/
|
|
3
10
|
export declare class HttpExceptionFilter implements ExceptionFilter {
|
|
11
|
+
private readonly logger?;
|
|
12
|
+
constructor(logger?: Logger);
|
|
4
13
|
catch(exception: any, host: ArgumentsHost): Response<any, Record<string, any>>;
|
|
5
14
|
}
|
|
@@ -5,13 +5,28 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
8
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
15
|
exports.HttpExceptionFilter = void 0;
|
|
10
16
|
const common_1 = require("@nestjs/common");
|
|
11
17
|
const typeorm_1 = require("typeorm");
|
|
12
18
|
const nestjs_i18n_1 = require("nestjs-i18n");
|
|
13
19
|
const validation_metadata_helper_1 = require("../common/helpers/validation-metadata-helper");
|
|
20
|
+
/**
|
|
21
|
+
* HTTP Exception Filter
|
|
22
|
+
*
|
|
23
|
+
* Catches all exceptions and formats the response
|
|
24
|
+
* For 500 errors, logs via Pino Logger which will automatically send email alerts if EMAIL_LOG_ENABLED=true
|
|
25
|
+
*/
|
|
14
26
|
let HttpExceptionFilter = class HttpExceptionFilter {
|
|
27
|
+
constructor(logger) {
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
}
|
|
15
30
|
catch(exception, host) {
|
|
16
31
|
var _a, _b, _c, _d, _e;
|
|
17
32
|
const i18n = nestjs_i18n_1.I18nContext.current(host);
|
|
@@ -27,17 +42,25 @@ let HttpExceptionFilter = class HttpExceptionFilter {
|
|
|
27
42
|
error = 'Entity not found';
|
|
28
43
|
}
|
|
29
44
|
if (statusCode === 500) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
const errorObj = exception instanceof Error ? exception : new Error(String(exception));
|
|
46
|
+
// Use Pino Logger if available (will send email alerts when EMAIL_LOG_ENABLED=true)
|
|
47
|
+
// Otherwise fallback to console.error
|
|
48
|
+
if (this.logger) {
|
|
49
|
+
this.logger.error(errorObj.message, errorObj.stack);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error({
|
|
53
|
+
message: errorObj.message,
|
|
54
|
+
stack: errorObj.stack,
|
|
55
|
+
connection: {
|
|
56
|
+
localAddress: (_a = request.socket) === null || _a === void 0 ? void 0 : _a.localAddress,
|
|
57
|
+
remoteAddress: (_b = request.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress,
|
|
58
|
+
bytesRead: (_c = request.socket) === null || _c === void 0 ? void 0 : _c.bytesRead,
|
|
59
|
+
bytesWritten: (_d = request.socket) === null || _d === void 0 ? void 0 : _d.bytesWritten,
|
|
60
|
+
},
|
|
61
|
+
headers: request.headers,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
41
64
|
}
|
|
42
65
|
const getFirstError = (error, property = '') => {
|
|
43
66
|
var _a;
|
|
@@ -82,5 +105,8 @@ let HttpExceptionFilter = class HttpExceptionFilter {
|
|
|
82
105
|
};
|
|
83
106
|
exports.HttpExceptionFilter = HttpExceptionFilter;
|
|
84
107
|
exports.HttpExceptionFilter = HttpExceptionFilter = __decorate([
|
|
85
|
-
(0, common_1.Catch)()
|
|
108
|
+
(0, common_1.Catch)(),
|
|
109
|
+
(0, common_1.Injectable)(),
|
|
110
|
+
__param(0, (0, common_1.Optional)()),
|
|
111
|
+
__metadata("design:paramtypes", [Function])
|
|
86
112
|
], HttpExceptionFilter);
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -44,4 +44,6 @@ __exportStar(require("./health-checker"), exports);
|
|
|
44
44
|
__exportStar(require("./audit"), exports);
|
|
45
45
|
// File upload module
|
|
46
46
|
__exportStar(require("./file-upload"), exports);
|
|
47
|
+
// Email log module
|
|
48
|
+
__exportStar(require("./email-log"), exports);
|
|
47
49
|
__exportStar(require("@nest-omni/transaction"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-omni/core",
|
|
3
|
-
"version": "4.1.3-
|
|
3
|
+
"version": "4.1.3-30",
|
|
4
4
|
"description": "A comprehensive NestJS framework for building enterprise-grade applications with best practices",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@types/express-session": "^1.18.2",
|
|
46
46
|
"@types/lodash": "^4.17.21",
|
|
47
47
|
"@types/node": "^25.0.3",
|
|
48
|
+
"@types/nodemailer": "^6.4.17",
|
|
48
49
|
"@types/sharp": "^0.32.0",
|
|
49
50
|
"@types/sprintf-js": "^1.1.4",
|
|
50
51
|
"@types/uuid": "^11.0.0",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"moment": "^2.30.1",
|
|
89
90
|
"mysql2": "^3.16.0",
|
|
90
91
|
"nestjs-cls": "^6.2.0",
|
|
92
|
+
"nodemailer": "^6.9.16",
|
|
91
93
|
"nestjs-i18n": "^10.6.0",
|
|
92
94
|
"nestjs-pino": "^4.5.0",
|
|
93
95
|
"node-vault": "^0.10.9",
|
package/setup/bootstrap.setup.js
CHANGED
|
@@ -164,7 +164,7 @@ function bootstrapSetup(AppModule, SetupSwagger) {
|
|
|
164
164
|
}).use, (0, __1.RequestIdMiddleware)(), (0, __1.PowerByMiddleware)(), (0, __1.OmniAuthMiddleware)(), compression());
|
|
165
165
|
// 全局过滤器
|
|
166
166
|
const reflector = app.get(core_1.Reflector);
|
|
167
|
-
app.useGlobalFilters(new setup_1.SentryGlobalFilter(), new __1.HttpExceptionFilter(), new __1.QueryFailedFilter(reflector));
|
|
167
|
+
app.useGlobalFilters(new setup_1.SentryGlobalFilter(), new __1.HttpExceptionFilter(logger), new __1.QueryFailedFilter(reflector));
|
|
168
168
|
// 全局拦截器
|
|
169
169
|
app.useGlobalInterceptors(new __1.LanguageInterceptor(), new __1.TranslationInterceptor(), new nestjs_pino_1.LoggerErrorInterceptor());
|
|
170
170
|
// 全局管道
|
|
@@ -37,6 +37,7 @@ const typeorm_2 = require("typeorm");
|
|
|
37
37
|
const vault_1 = require("../vault");
|
|
38
38
|
const validators_1 = require("../validators");
|
|
39
39
|
const transaction_1 = require("@nest-omni/transaction");
|
|
40
|
+
const email_log_1 = require("../email-log");
|
|
40
41
|
const providers = [
|
|
41
42
|
services_1.ApiConfigService,
|
|
42
43
|
services_1.GeneratorService,
|
|
@@ -162,6 +163,19 @@ if (services_1.ApiConfigService.toBoolean(process.env.SCHEDULE_ENABLED) &&
|
|
|
162
163
|
if (services_1.ApiConfigService.toBoolean(process.env.SCHEDULE_ENABLED)) {
|
|
163
164
|
modules.push(schedule_1.ScheduleModule.forRoot());
|
|
164
165
|
}
|
|
166
|
+
// Email log transport for Pino
|
|
167
|
+
if (services_1.ApiConfigService.toBoolean(process.env.EMAIL_LOG_ENABLED, false)) {
|
|
168
|
+
modules.push(email_log_1.EmailLogModule.forRootAsync({
|
|
169
|
+
inject: [services_1.ApiConfigService],
|
|
170
|
+
useFactory: (config) => {
|
|
171
|
+
const emailConfig = config.emailLogConfig;
|
|
172
|
+
if (!emailConfig) {
|
|
173
|
+
throw new Error('EMAIL_LOG_ENABLED is true but emailLogConfig is null');
|
|
174
|
+
}
|
|
175
|
+
return emailConfig;
|
|
176
|
+
},
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
165
179
|
let ServiceRegistryModule = class ServiceRegistryModule {
|
|
166
180
|
};
|
|
167
181
|
exports.ServiceRegistryModule = ServiceRegistryModule;
|
|
@@ -5,6 +5,7 @@ import { Params } from 'nestjs-pino';
|
|
|
5
5
|
import { BullRootModuleOptions } from '@nestjs/bull/dist/interfaces/bull-module-options.interface';
|
|
6
6
|
import { AxiosProxyConfig } from 'axios';
|
|
7
7
|
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
|
|
8
|
+
import { PinoEmailTransport } from '../../email-log';
|
|
8
9
|
export declare class ApiConfigService {
|
|
9
10
|
private configService;
|
|
10
11
|
static rootPath: string;
|
|
@@ -47,7 +48,47 @@ export declare class ApiConfigService {
|
|
|
47
48
|
get isRedisEnabled(): boolean;
|
|
48
49
|
get bullConfig(): BullRootModuleOptions;
|
|
49
50
|
get pinoConfig(): Params;
|
|
51
|
+
/**
|
|
52
|
+
* Get Pino multistream configuration with email transport
|
|
53
|
+
* This method provides additional stream for email logging
|
|
54
|
+
* Usage: pass to LoggerModule or use with pino.multistream()
|
|
55
|
+
*/
|
|
56
|
+
get pinoEmailStream(): {
|
|
57
|
+
level: string;
|
|
58
|
+
stream: any;
|
|
59
|
+
};
|
|
50
60
|
get axiosProxyConfig(): AxiosProxyConfig | false;
|
|
61
|
+
/**
|
|
62
|
+
* Email log transport configuration
|
|
63
|
+
* Returns null if email logging is disabled
|
|
64
|
+
*/
|
|
65
|
+
get emailLogConfig(): {
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
to: string[];
|
|
68
|
+
from: string;
|
|
69
|
+
level: string;
|
|
70
|
+
subjectPrefix: string;
|
|
71
|
+
smtpHost: string;
|
|
72
|
+
smtpPort: number;
|
|
73
|
+
smtpUsername: string;
|
|
74
|
+
smtpPassword: string;
|
|
75
|
+
smtpSecure: boolean;
|
|
76
|
+
smtpIgnoreTLSError: boolean;
|
|
77
|
+
rateLimitMaxEmails: number;
|
|
78
|
+
rateLimitWindowMs: number;
|
|
79
|
+
rateLimitBurstSize: number;
|
|
80
|
+
useHtmlFormat: boolean;
|
|
81
|
+
includeApps: string[];
|
|
82
|
+
excludeApps: string[];
|
|
83
|
+
includeErrors: string[];
|
|
84
|
+
excludeErrors: string[];
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Get Pino email transport instance
|
|
88
|
+
* This returns a Pino transport that can be used directly with Pino logger
|
|
89
|
+
* Usage: pino(transport) or add to multistream
|
|
90
|
+
*/
|
|
91
|
+
get pinoEmailTransport(): PinoEmailTransport;
|
|
51
92
|
static getRootPath(): string;
|
|
52
93
|
static toBoolean(value: string, defaultValue?: any): boolean;
|
|
53
94
|
getNumber(key: string, defaultValue?: number): number;
|
|
@@ -16,7 +16,9 @@ const config_1 = require("@nestjs/config");
|
|
|
16
16
|
const lodash_1 = require("lodash");
|
|
17
17
|
const connect_redis_1 = require("connect-redis");
|
|
18
18
|
const ioredis_1 = require("ioredis");
|
|
19
|
+
const pino_1 = require("pino");
|
|
19
20
|
const common_2 = require("../../common");
|
|
21
|
+
const email_log_1 = require("../../email-log");
|
|
20
22
|
let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
|
|
21
23
|
constructor(configService) {
|
|
22
24
|
this.configService = configService;
|
|
@@ -204,15 +206,68 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
|
|
|
204
206
|
};
|
|
205
207
|
}
|
|
206
208
|
get pinoConfig() {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
const emailConfig = this.emailLogConfig;
|
|
210
|
+
const emailEnabled = (emailConfig === null || emailConfig === void 0 ? void 0 : emailConfig.enabled) || false;
|
|
211
|
+
// For dev mode without email, use pino-pretty
|
|
212
|
+
if (this.isDev && !emailEnabled) {
|
|
213
|
+
return {
|
|
214
|
+
pinoHttp: {
|
|
215
|
+
genReqId: (req) => req.id,
|
|
216
|
+
transport: {
|
|
217
|
+
target: 'pino-pretty',
|
|
218
|
+
},
|
|
219
|
+
level: 'debug',
|
|
220
|
+
customLogLevel: function (res) {
|
|
221
|
+
if (res.statusCode >= 500)
|
|
222
|
+
return 'error';
|
|
223
|
+
if (res.statusCode >= 400)
|
|
224
|
+
return 'warn';
|
|
225
|
+
return 'info';
|
|
226
|
+
},
|
|
227
|
+
wrapSerializers: true,
|
|
228
|
+
customProps: (req, res) => {
|
|
229
|
+
return {
|
|
230
|
+
env: this.nodeEnv,
|
|
231
|
+
appName: this.getString('NAME'),
|
|
232
|
+
user: req === null || req === void 0 ? void 0 : req.user,
|
|
233
|
+
'req.body': req === null || req === void 0 ? void 0 : req.body,
|
|
234
|
+
'res.body': res === null || res === void 0 ? void 0 : res.body,
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
redact: {
|
|
238
|
+
paths: ['req.headers.authorization', 'req.headers.apikey', 'req.headers.cookie'],
|
|
239
|
+
remove: true,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// For production or when email is enabled
|
|
245
|
+
// Prepare multistream targets
|
|
246
|
+
const streams = [
|
|
247
|
+
{
|
|
248
|
+
level: 'trace',
|
|
249
|
+
stream: process.stdout,
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
// Add email transport if enabled
|
|
253
|
+
if (emailEnabled && emailConfig) {
|
|
254
|
+
try {
|
|
255
|
+
// Create email transport
|
|
256
|
+
const emailTransport = new email_log_1.PinoEmailTransport(emailConfig);
|
|
257
|
+
streams.push({
|
|
258
|
+
level: emailConfig.level || 'error',
|
|
259
|
+
stream: emailTransport,
|
|
260
|
+
});
|
|
210
261
|
}
|
|
211
|
-
|
|
262
|
+
catch (error) {
|
|
263
|
+
console.error('Failed to create Pino email transport:', error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
212
266
|
return {
|
|
213
267
|
pinoHttp: {
|
|
214
268
|
genReqId: (req) => req.id,
|
|
215
|
-
|
|
269
|
+
// Use multistream for combining console and email output
|
|
270
|
+
stream: pino_1.default.multistream(streams),
|
|
216
271
|
level: this.isDev ? 'debug' : 'info',
|
|
217
272
|
customLogLevel: function (res) {
|
|
218
273
|
if (res.statusCode >= 500)
|
|
@@ -223,11 +278,42 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
|
|
|
223
278
|
},
|
|
224
279
|
wrapSerializers: true,
|
|
225
280
|
customProps: (req, res) => {
|
|
281
|
+
var _a, _b, _c, _d, _e;
|
|
282
|
+
// Get protocol, host and port from request
|
|
283
|
+
const protocol = req.protocol || 'http';
|
|
284
|
+
const hostHeader = req.get('host') || req.hostname || 'localhost';
|
|
285
|
+
const hostname = req.hostname || 'localhost';
|
|
286
|
+
const port = ((_a = req.socket) === null || _a === void 0 ? void 0 : _a.localPort) || this.appConfig.port || 3000;
|
|
287
|
+
// Build baseUrl - if host already includes port, don't add it again
|
|
288
|
+
const hasPortInHost = hostHeader.includes(':');
|
|
289
|
+
const baseUrl = hasPortInHost
|
|
290
|
+
? `${protocol}://${hostHeader}`
|
|
291
|
+
: `${protocol}://${hostHeader}:${port}`;
|
|
226
292
|
return {
|
|
227
293
|
env: this.nodeEnv,
|
|
228
294
|
appName: this.getString('NAME'),
|
|
295
|
+
// User information
|
|
229
296
|
user: req === null || req === void 0 ? void 0 : req.user,
|
|
230
|
-
|
|
297
|
+
// Request information (for email alerts)
|
|
298
|
+
request: req ? {
|
|
299
|
+
id: req.id,
|
|
300
|
+
method: req.method,
|
|
301
|
+
url: req.originalUrl || req.url,
|
|
302
|
+
query: req.query,
|
|
303
|
+
params: req.params,
|
|
304
|
+
body: req.body,
|
|
305
|
+
ip: req.ip,
|
|
306
|
+
protocol,
|
|
307
|
+
host: hostname,
|
|
308
|
+
port,
|
|
309
|
+
baseUrl,
|
|
310
|
+
headers: {
|
|
311
|
+
'user-agent': (_b = req.headers) === null || _b === void 0 ? void 0 : _b['user-agent'],
|
|
312
|
+
referer: ((_c = req.headers) === null || _c === void 0 ? void 0 : _c.referer) || ((_d = req.headers) === null || _d === void 0 ? void 0 : _d.referrer),
|
|
313
|
+
host: (_e = req.headers) === null || _e === void 0 ? void 0 : _e.host,
|
|
314
|
+
},
|
|
315
|
+
} : undefined,
|
|
316
|
+
// Response body
|
|
231
317
|
'res.body': res === null || res === void 0 ? void 0 : res.body,
|
|
232
318
|
};
|
|
233
319
|
},
|
|
@@ -238,6 +324,24 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
|
|
|
238
324
|
},
|
|
239
325
|
};
|
|
240
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Get Pino multistream configuration with email transport
|
|
329
|
+
* This method provides additional stream for email logging
|
|
330
|
+
* Usage: pass to LoggerModule or use with pino.multistream()
|
|
331
|
+
*/
|
|
332
|
+
get pinoEmailStream() {
|
|
333
|
+
const emailConfig = this.emailLogConfig;
|
|
334
|
+
if (!emailConfig || !emailConfig.enabled) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
// Return stream configuration for email transport
|
|
338
|
+
// The actual stream will be attached by EmailLogModule
|
|
339
|
+
return {
|
|
340
|
+
level: emailConfig.level,
|
|
341
|
+
// This will be replaced with the actual EmailLogTransportService stream
|
|
342
|
+
stream: null,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
241
345
|
get axiosProxyConfig() {
|
|
242
346
|
if (!this.getBoolean('DIRECT_PROXY_ENABLED')) {
|
|
243
347
|
return false;
|
|
@@ -247,6 +351,59 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
|
|
|
247
351
|
port: this.getNumber('DIRECT_PROXY_PORT'),
|
|
248
352
|
};
|
|
249
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Email log transport configuration
|
|
356
|
+
* Returns null if email logging is disabled
|
|
357
|
+
*/
|
|
358
|
+
get emailLogConfig() {
|
|
359
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
360
|
+
if (!this.getBoolean('EMAIL_LOG_ENABLED', false)) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
enabled: true,
|
|
365
|
+
to: this.getString('EMAIL_LOG_TO')
|
|
366
|
+
.split(',')
|
|
367
|
+
.map((e) => e.trim())
|
|
368
|
+
.filter(Boolean),
|
|
369
|
+
from: this.getString('EMAIL_LOG_FROM'),
|
|
370
|
+
level: this.getString('EMAIL_LOG_LEVEL', 'error'),
|
|
371
|
+
subjectPrefix: this.getString('EMAIL_LOG_SUBJECT_PREFIX', '[Log Alert]'),
|
|
372
|
+
smtpHost: this.getString('EMAIL_LOG_SMTP_HOST'),
|
|
373
|
+
smtpPort: this.getNumber('EMAIL_LOG_SMTP_PORT', 587),
|
|
374
|
+
smtpUsername: this.getString('EMAIL_LOG_SMTP_USERNAME', ''),
|
|
375
|
+
smtpPassword: this.getString('EMAIL_LOG_SMTP_PASSWORD', ''),
|
|
376
|
+
smtpSecure: this.getBoolean('EMAIL_LOG_SMTP_SECURE', false),
|
|
377
|
+
smtpIgnoreTLSError: this.getBoolean('EMAIL_LOG_SMTP_IGNORE_TLS_ERROR', false),
|
|
378
|
+
rateLimitMaxEmails: this.getNumber('EMAIL_LOG_RATE_LIMIT_MAX_EMAILS', 10),
|
|
379
|
+
rateLimitWindowMs: this.getNumber('EMAIL_LOG_RATE_LIMIT_WINDOW_MS', 60000),
|
|
380
|
+
rateLimitBurstSize: this.getNumber('EMAIL_LOG_RATE_LIMIT_BURST_SIZE', 3),
|
|
381
|
+
useHtmlFormat: this.getBoolean('EMAIL_LOG_USE_HTML_FORMAT', true),
|
|
382
|
+
includeApps: ((_b = (_a = this.getString('EMAIL_LOG_INCLUDE_APPS', '')) === null || _a === void 0 ? void 0 : _a.split(',')) === null || _b === void 0 ? void 0 : _b.filter(Boolean)) || undefined,
|
|
383
|
+
excludeApps: ((_d = (_c = this.getString('EMAIL_LOG_EXCLUDE_APPS', '')) === null || _c === void 0 ? void 0 : _c.split(',')) === null || _d === void 0 ? void 0 : _d.filter(Boolean)) || undefined,
|
|
384
|
+
includeErrors: ((_f = (_e = this.getString('EMAIL_LOG_INCLUDE_ERRORS', '')) === null || _e === void 0 ? void 0 : _e.split(',')) === null || _f === void 0 ? void 0 : _f.filter(Boolean)) || undefined,
|
|
385
|
+
excludeErrors: ((_h = (_g = this.getString('EMAIL_LOG_EXCLUDE_ERRORS', '')) === null || _g === void 0 ? void 0 : _g.split(',')) === null || _h === void 0 ? void 0 : _h.filter(Boolean)) || undefined,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get Pino email transport instance
|
|
390
|
+
* This returns a Pino transport that can be used directly with Pino logger
|
|
391
|
+
* Usage: pino(transport) or add to multistream
|
|
392
|
+
*/
|
|
393
|
+
get pinoEmailTransport() {
|
|
394
|
+
const config = this.emailLogConfig;
|
|
395
|
+
if (!config) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
// Create and return email transport
|
|
399
|
+
try {
|
|
400
|
+
return new email_log_1.PinoEmailTransport(config);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error('Failed to create Pino email transport:', error);
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
250
407
|
static getRootPath() {
|
|
251
408
|
if (!ApiConfigService_1.rootPath) {
|
|
252
409
|
throw new Error(`rootPath is not set`);
|