@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.
Files changed (51) hide show
  1. package/audit/interceptors/audit-action.interceptor.js +1 -1
  2. package/audit/services/audit-action.service.js +1 -1
  3. package/audit/services/operation-description.service.js +1 -1
  4. package/audit/services/transaction-audit.service.js +1 -1
  5. package/email-log/email-log.constants.d.ts +8 -0
  6. package/email-log/email-log.constants.js +11 -0
  7. package/email-log/email-log.module.d.ts +47 -0
  8. package/email-log/email-log.module.js +140 -0
  9. package/email-log/index.d.ts +11 -0
  10. package/email-log/index.js +48 -0
  11. package/email-log/interfaces/email-log-options.interface.d.ts +61 -0
  12. package/email-log/interfaces/email-log-options.interface.js +134 -0
  13. package/email-log/interfaces/email-log-transport.interface.d.ts +20 -0
  14. package/email-log/interfaces/email-log-transport.interface.js +2 -0
  15. package/email-log/interfaces/index.d.ts +2 -0
  16. package/email-log/interfaces/index.js +18 -0
  17. package/email-log/providers/email-provider.d.ts +42 -0
  18. package/email-log/providers/email-provider.js +127 -0
  19. package/email-log/providers/index.d.ts +1 -0
  20. package/email-log/providers/index.js +17 -0
  21. package/email-log/services/email-log-alert.service.d.ts +46 -0
  22. package/email-log/services/email-log-alert.service.js +162 -0
  23. package/email-log/services/email-log-formatter.service.d.ts +78 -0
  24. package/email-log/services/email-log-formatter.service.js +442 -0
  25. package/email-log/services/email-log-logger.service.d.ts +85 -0
  26. package/email-log/services/email-log-logger.service.js +168 -0
  27. package/email-log/services/email-log-rate-limiter.service.d.ts +42 -0
  28. package/email-log/services/email-log-rate-limiter.service.js +110 -0
  29. package/email-log/services/email-log-transport.service.d.ts +80 -0
  30. package/email-log/services/email-log-transport.service.js +271 -0
  31. package/email-log/services/index.d.ts +5 -0
  32. package/email-log/services/index.js +21 -0
  33. package/email-log/transports/index.d.ts +1 -0
  34. package/email-log/transports/index.js +17 -0
  35. package/email-log/transports/pino-email.transport.d.ts +56 -0
  36. package/email-log/transports/pino-email.transport.js +188 -0
  37. package/email-log/utils/index.d.ts +2 -0
  38. package/email-log/utils/index.js +18 -0
  39. package/email-log/utils/log-level.helper.d.ts +46 -0
  40. package/email-log/utils/log-level.helper.js +74 -0
  41. package/email-log/utils/pino-transport.utils.d.ts +135 -0
  42. package/email-log/utils/pino-transport.utils.js +238 -0
  43. package/filters/bad-request.filter.d.ts +9 -0
  44. package/filters/bad-request.filter.js +38 -12
  45. package/index.d.ts +1 -0
  46. package/index.js +2 -0
  47. package/package.json +3 -1
  48. package/setup/bootstrap.setup.js +1 -1
  49. package/shared/service-registry.module.js +14 -0
  50. package/shared/services/api-config.service.d.ts +41 -0
  51. 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
- console.error({
31
- message: exception.message,
32
- stack: exception.stack,
33
- connection: {
34
- localAddress: (_a = request.socket) === null || _a === void 0 ? void 0 : _a.localAddress,
35
- remoteAddress: (_b = request.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress,
36
- bytesRead: (_c = request.socket) === null || _c === void 0 ? void 0 : _c.bytesRead,
37
- bytesWritten: (_d = request.socket) === null || _d === void 0 ? void 0 : _d.bytesWritten,
38
- },
39
- headers: request.headers,
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
@@ -19,4 +19,5 @@ export * from './setup';
19
19
  export * from './health-checker';
20
20
  export * from './audit';
21
21
  export * from './file-upload';
22
+ export * from './email-log';
22
23
  export * from '@nest-omni/transaction';
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-29",
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",
@@ -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 transport = this.isDev
208
- ? {
209
- target: 'pino-pretty',
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
- : null;
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
- transport,
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
- 'req.body': req === null || req === void 0 ? void 0 : req.body,
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`);