@nest-omni/core 4.1.3-31 → 4.1.3-32

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-omni/core",
3
- "version": "4.1.3-31",
3
+ "version": "4.1.3-32",
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",
@@ -16,8 +16,10 @@ const profiling_node_1 = require("@sentry/profiling-node");
16
16
  const dotenv = require("dotenv");
17
17
  const fs_1 = require("fs");
18
18
  const path_1 = require("path");
19
+ const pino_1 = require("pino");
19
20
  const process = require("process");
20
21
  const mode_setup_1 = require("./mode.setup");
22
+ const email_log_1 = require("../email-log");
21
23
  function findValidRootPath() {
22
24
  const getAppRootPath = () => {
23
25
  // 优先使用 require.main.filename(应用入口文件)
@@ -72,6 +74,60 @@ requiredEnvVars.forEach((envVar) => {
72
74
  throw new Error(`Missing required environment variable: ${envVar}`);
73
75
  }
74
76
  });
77
+ // ============================================================================
78
+ // 创建用于 Sentry 错误邮件告警的 Pino Logger
79
+ // ============================================================================
80
+ // 这个 Logger 会在 Sentry 捕获到非 HTTP 请求的错误时发送邮件告警
81
+ // (如 Cron Job、Bull Queue 中的错误)
82
+ // ============================================================================
83
+ let sentryEmailLogger = null;
84
+ function initSentryEmailLogger() {
85
+ const emailLogEnabled = process.env.EMAIL_LOG_ENABLED === 'true';
86
+ if (!emailLogEnabled) {
87
+ return;
88
+ }
89
+ try {
90
+ // 从环境变量读取邮件配置
91
+ const emailConfig = {
92
+ enabled: true,
93
+ to: (process.env.EMAIL_LOG_TO || '')
94
+ .split(',')
95
+ .map((e) => e.trim())
96
+ .filter(Boolean),
97
+ from: process.env.EMAIL_LOG_FROM || 'noreply@example.com',
98
+ level: process.env.EMAIL_LOG_LEVEL || 'error',
99
+ subjectPrefix: process.env.EMAIL_LOG_SUBJECT_PREFIX || '[Sentry Alert]',
100
+ smtpHost: process.env.EMAIL_LOG_SMTP_HOST || 'localhost',
101
+ smtpPort: parseInt(process.env.EMAIL_LOG_SMTP_PORT || '587', 10),
102
+ smtpUsername: process.env.EMAIL_LOG_SMTP_USERNAME || '',
103
+ smtpPassword: process.env.EMAIL_LOG_SMTP_PASSWORD || '',
104
+ smtpSecure: process.env.EMAIL_LOG_SMTP_SECURE === 'true',
105
+ smtpIgnoreTLSError: process.env.EMAIL_LOG_SMTP_IGNORE_TLS_ERROR === 'true',
106
+ rateLimitMaxEmails: parseInt(process.env.EMAIL_LOG_RATE_LIMIT_MAX_EMAILS || '10', 10),
107
+ rateLimitWindowMs: parseInt(process.env.EMAIL_LOG_RATE_LIMIT_WINDOW_MS || '60000', 10),
108
+ rateLimitBurstSize: parseInt(process.env.EMAIL_LOG_RATE_LIMIT_BURST_SIZE || '3', 10),
109
+ useHtmlFormat: process.env.EMAIL_LOG_USE_HTML_FORMAT !== 'false',
110
+ };
111
+ // 创建 Pino 邮件传输
112
+ const emailTransport = new email_log_1.PinoEmailTransport(emailConfig);
113
+ // 创建 Logger(只输出到邮件,不重复输出到控制台)
114
+ sentryEmailLogger = (0, pino_1.default)({
115
+ name: 'SentryEmailForwarder',
116
+ level: 'error',
117
+ }, pino_1.default.multistream([
118
+ {
119
+ level: emailConfig.level,
120
+ stream: emailTransport,
121
+ },
122
+ ]));
123
+ console.log('[Sentry] Email logger initialized for non-HTTP errors');
124
+ }
125
+ catch (error) {
126
+ console.error('[Sentry] Failed to initialize email logger:', error);
127
+ }
128
+ }
129
+ // 初始化 Sentry 邮件 Logger
130
+ initSentryEmailLogger();
75
131
  Sentry.init({
76
132
  dsn: process.env.SENTRY_DSN || '',
77
133
  release: process.env.API_VERSION || '',
@@ -83,6 +139,45 @@ Sentry.init({
83
139
  profileLifecycle: 'trace',
84
140
  sendDefaultPii: true,
85
141
  integrations: [profiling_node_1.nodeProfilingIntegration],
142
+ // ============================================================================
143
+ // beforeSend 钩子:在 Sentry 发送错误之前,通过 Pino 发送邮件告警
144
+ // ============================================================================
145
+ // 注意:HTTP 请求中的错误已经在 HttpExceptionFilter 中处理,这里跳过
146
+ // 只处理非 HTTP 请求的错误(如 Cron Job、Bull Queue 中的错误)
147
+ // ============================================================================
148
+ beforeSend(event) {
149
+ var _a, _b, _c, _d, _e;
150
+ // 如果有 request.url,说明是 HTTP 请求错误
151
+ // 这些错误已经在 HttpExceptionFilter 中通过 Pino Logger 处理了
152
+ // 这里跳过,避免重复发送邮件
153
+ if ((_a = event.request) === null || _a === void 0 ? void 0 : _a.url) {
154
+ return event;
155
+ }
156
+ // 只处理非 HTTP 请求的错误(Cron、Bull 等)
157
+ if (sentryEmailLogger && event.exception) {
158
+ const exceptionValue = (_c = (_b = event.exception) === null || _b === void 0 ? void 0 : _b.values) === null || _c === void 0 ? void 0 : _c[0];
159
+ sentryEmailLogger.error({
160
+ msg: `[Sentry] ${event.message || (exceptionValue === null || exceptionValue === void 0 ? void 0 : exceptionValue.value) || 'Error captured by Sentry'}`,
161
+ error: (exceptionValue === null || exceptionValue === void 0 ? void 0 : exceptionValue.value) || 'Unknown error',
162
+ stack: (_e = (_d = exceptionValue === null || exceptionValue === void 0 ? void 0 : exceptionValue.stacktrace) === null || _d === void 0 ? void 0 : _d.frames) === null || _e === void 0 ? void 0 : _e.map((f) => `${f.filename}:${f.lineno}`).join('\n'),
163
+ sentryEventId: event.event_id,
164
+ environment: event.environment,
165
+ release: event.release,
166
+ // Sentry 收集的额外上下文
167
+ request: event.request,
168
+ user: event.user,
169
+ tags: event.tags,
170
+ extra: event.extra,
171
+ // 异常类型和值
172
+ exceptionType: exceptionValue === null || exceptionValue === void 0 ? void 0 : exceptionValue.type,
173
+ exceptionValue: exceptionValue === null || exceptionValue === void 0 ? void 0 : exceptionValue.value,
174
+ // 错误来源(通过 transaction 判断是 Cron 还是 Bull)
175
+ transaction: event.transaction,
176
+ });
177
+ }
178
+ // 返回 event 让 Sentry 继续正常处理
179
+ return event;
180
+ },
86
181
  });
87
182
  const crud_1 = require("@dataui/crud");
88
183
  crud_1.CrudConfigService.load({