@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,442 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
9
|
+
var t = {};
|
|
10
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
11
|
+
t[p] = s[p];
|
|
12
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
13
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
14
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
15
|
+
t[p[i]] = s[p[i]];
|
|
16
|
+
}
|
|
17
|
+
return t;
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.EmailLogFormatterService = void 0;
|
|
21
|
+
const common_1 = require("@nestjs/common");
|
|
22
|
+
const log_level_helper_1 = require("../utils/log-level.helper");
|
|
23
|
+
/**
|
|
24
|
+
* Formats log entries for email delivery
|
|
25
|
+
* Supports both HTML and plain text formats
|
|
26
|
+
*/
|
|
27
|
+
let EmailLogFormatterService = class EmailLogFormatterService {
|
|
28
|
+
/**
|
|
29
|
+
* Format log entry as HTML email
|
|
30
|
+
*/
|
|
31
|
+
formatHtml(log, appName, subjectPrefix) {
|
|
32
|
+
const { level, time, msg, err } = log, rest = __rest(log, ["level", "time", "msg", "err"]);
|
|
33
|
+
const levelLabel = this.getLevelLabel(level);
|
|
34
|
+
const levelColor = this.getLevelColor(level);
|
|
35
|
+
const timestamp = new Date(time || Date.now()).toISOString();
|
|
36
|
+
const subject = `${subjectPrefix} ${appName} - ${levelLabel.toUpperCase()}: ${msg || 'No message'}`;
|
|
37
|
+
// Separate HTTP context from other metadata
|
|
38
|
+
const httpContext = this.extractHttpContext(rest);
|
|
39
|
+
const otherMetadata = this.excludeHttpContext(rest);
|
|
40
|
+
const html = `
|
|
41
|
+
<!DOCTYPE html>
|
|
42
|
+
<html>
|
|
43
|
+
<head>
|
|
44
|
+
<meta charset="utf-8">
|
|
45
|
+
<style>
|
|
46
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; }
|
|
47
|
+
.container { max-width: 900px; margin: 0 auto; padding: 20px; }
|
|
48
|
+
.header { border-bottom: 3px solid ${levelColor}; padding-bottom: 15px; margin-bottom: 20px; }
|
|
49
|
+
.level { display: inline-block; padding: 6px 14px; background: ${levelColor}; color: white; border-radius: 4px; font-weight: bold; font-size: 14px; }
|
|
50
|
+
.timestamp { color: #666; font-size: 14px; margin-top: 8px; }
|
|
51
|
+
.message { font-size: 18px; margin: 20px 0; }
|
|
52
|
+
.section { margin: 25px 0; }
|
|
53
|
+
.section-title { font-weight: bold; margin-bottom: 12px; color: #333; font-size: 16px; }
|
|
54
|
+
.code-block { background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; padding: 15px; overflow-x: auto; }
|
|
55
|
+
.code-block pre { margin: 0; white-space: pre-wrap; word-wrap: break-word; font-family: 'Monaco', 'Menlo', monospace; font-size: 13px; }
|
|
56
|
+
.error-stack { color: #d32f2f; }
|
|
57
|
+
.metadata { background: #f9f9f9; border-left: 3px solid #ddd; padding: 12px 15px; margin-top: 10px; border-radius: 4px; }
|
|
58
|
+
.metadata-section { background: #fafafa; border: 1px solid #e0e0e0; border-radius: 6px; padding: 15px; margin: 15px 0; }
|
|
59
|
+
.metadata-section-title { font-weight: bold; margin-bottom: 12px; color: #444; font-size: 15px; border-bottom: 2px solid #e0e0e0; padding-bottom: 8px; }
|
|
60
|
+
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
|
61
|
+
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #ddd; }
|
|
62
|
+
th { background: #f5f5f5; font-weight: bold; color: #333; font-size: 13px; text-transform: uppercase; font-size: 11px; letter-spacing: 0.5px; }
|
|
63
|
+
td { font-size: 14px; }
|
|
64
|
+
tr:last-child td { border-bottom: none; }
|
|
65
|
+
.label { color: #666; font-size: 12px; }
|
|
66
|
+
.value { color: #333; }
|
|
67
|
+
.highlight { background: #fff3cd; padding: 2px 6px; border-radius: 3px; }
|
|
68
|
+
.user-info { background: #e3f2fd; border-left: 4px solid #2196f3; padding: 12px; margin: 10px 0; border-radius: 4px; }
|
|
69
|
+
.request-info { background: #f3e5f5; border-left: 4px solid #9c27b0; padding: 12px; margin: 10px 0; border-radius: 4px; }
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body>
|
|
73
|
+
<div class="container">
|
|
74
|
+
<div class="header">
|
|
75
|
+
<span class="level">${levelLabel.toUpperCase()}</span>
|
|
76
|
+
<div class="timestamp">${timestamp}</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="message">${this.escapeHtml(msg || 'No message')}</div>
|
|
80
|
+
|
|
81
|
+
${err ? this.formatError(err) : ''}
|
|
82
|
+
|
|
83
|
+
${httpContext.request ? this.formatRequestInfo(httpContext.request) : ''}
|
|
84
|
+
|
|
85
|
+
${httpContext.user ? this.formatUserInfo(httpContext.user) : ''}
|
|
86
|
+
|
|
87
|
+
${Object.keys(otherMetadata).length > 0 ? this.formatMetadata('Additional Metadata', otherMetadata) : ''}
|
|
88
|
+
</div>
|
|
89
|
+
</body>
|
|
90
|
+
</html>`;
|
|
91
|
+
return { subject, html: html.trim() };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format log entry as plain text
|
|
95
|
+
*/
|
|
96
|
+
formatText(log, appName, subjectPrefix) {
|
|
97
|
+
const { level, time, msg, err } = log, rest = __rest(log, ["level", "time", "msg", "err"]);
|
|
98
|
+
const levelLabel = this.getLevelLabel(level);
|
|
99
|
+
const timestamp = new Date(time || Date.now()).toISOString();
|
|
100
|
+
const subject = `${subjectPrefix} ${appName} - ${levelLabel.toUpperCase()}: ${msg || 'No message'}`;
|
|
101
|
+
// Separate HTTP context from other metadata
|
|
102
|
+
const httpContext = this.extractHttpContext(rest);
|
|
103
|
+
const otherMetadata = this.excludeHttpContext(rest);
|
|
104
|
+
let text = `${levelLabel.toUpperCase()}: ${msg || 'No message'}\n`;
|
|
105
|
+
text += `Time: ${timestamp}\n`;
|
|
106
|
+
text += `App: ${appName}\n`;
|
|
107
|
+
if (err) {
|
|
108
|
+
text += `\nError:\n${this.formatErrorText(err)}`;
|
|
109
|
+
}
|
|
110
|
+
if (httpContext.request) {
|
|
111
|
+
text += `\n${this.formatRequestInfoText(httpContext.request)}`;
|
|
112
|
+
}
|
|
113
|
+
if (httpContext.user) {
|
|
114
|
+
text += `\n${this.formatUserInfoText(httpContext.user)}`;
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(otherMetadata).length > 0) {
|
|
117
|
+
text += `\nAdditional Metadata:\n${JSON.stringify(otherMetadata, null, 2)}`;
|
|
118
|
+
}
|
|
119
|
+
return { subject, text };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Extract HTTP context from log metadata
|
|
123
|
+
*/
|
|
124
|
+
extractHttpContext(metadata) {
|
|
125
|
+
const context = {};
|
|
126
|
+
// Extract request information - prefer 'request' from customProps over 'req' from Pino
|
|
127
|
+
if (metadata.request) {
|
|
128
|
+
context.request = metadata.request;
|
|
129
|
+
}
|
|
130
|
+
else if (metadata.req) {
|
|
131
|
+
context.request = metadata.req;
|
|
132
|
+
}
|
|
133
|
+
// Extract user information
|
|
134
|
+
if (metadata.user) {
|
|
135
|
+
context.user = metadata.user;
|
|
136
|
+
}
|
|
137
|
+
// Extract response information
|
|
138
|
+
if (metadata.response) {
|
|
139
|
+
context.response = metadata.response;
|
|
140
|
+
}
|
|
141
|
+
else if (metadata.res) {
|
|
142
|
+
context.response = metadata.res;
|
|
143
|
+
}
|
|
144
|
+
return context;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Exclude HTTP context from general metadata
|
|
148
|
+
*/
|
|
149
|
+
excludeHttpContext(metadata) {
|
|
150
|
+
const _a = metadata, { request, req, response, res, user } = _a, rest = __rest(_a, ["request", "req", "response", "res", "user"]);
|
|
151
|
+
return rest;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Format request information for HTML
|
|
155
|
+
*/
|
|
156
|
+
formatRequestInfo(request) {
|
|
157
|
+
var _a, _b;
|
|
158
|
+
const info = [
|
|
159
|
+
{ key: 'Method', value: request.method || 'N/A', highlight: true },
|
|
160
|
+
{ key: 'Full URL', value: (request.baseUrl || '') + (request.url || 'N/A'), highlight: true },
|
|
161
|
+
{ key: 'Path', value: request.url || 'N/A', highlight: true },
|
|
162
|
+
{ key: 'IP Address', value: request.ip || request.remoteAddress || 'N/A' },
|
|
163
|
+
{ key: 'Protocol', value: request.protocol || 'http' },
|
|
164
|
+
{ key: 'Host', value: request.host || ((_a = request.headers) === null || _a === void 0 ? void 0 : _a.host) || 'N/A' },
|
|
165
|
+
{ key: 'Port', value: ((_b = request.port) === null || _b === void 0 ? void 0 : _b.toString()) || 'N/A' },
|
|
166
|
+
{ key: 'Request ID', value: request.id || request.requestId || 'N/A' },
|
|
167
|
+
];
|
|
168
|
+
// Add query parameters if present
|
|
169
|
+
if (request.query && Object.keys(request.query).length > 0) {
|
|
170
|
+
info.push({
|
|
171
|
+
key: 'Query Params',
|
|
172
|
+
value: JSON.stringify(request.query, null, 2)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Add path parameters if present
|
|
176
|
+
if (request.params && Object.keys(request.params).length > 0) {
|
|
177
|
+
info.push({
|
|
178
|
+
key: 'Path Params',
|
|
179
|
+
value: JSON.stringify(request.params, null, 2)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// Generate curl command
|
|
183
|
+
const curlCommand = this.generateCurlCommand(request);
|
|
184
|
+
return `
|
|
185
|
+
<div class="section">
|
|
186
|
+
<div class="metadata-section request-info">
|
|
187
|
+
<div class="metadata-section-title">🌐 HTTP Request Information</div>
|
|
188
|
+
<table>
|
|
189
|
+
${info.map(({ key, value, highlight }) => `
|
|
190
|
+
<tr>
|
|
191
|
+
<td width="150">${key}</td>
|
|
192
|
+
<td class="${highlight ? 'highlight' : 'value'}">${this.escapeHtml(String(value))}</td>
|
|
193
|
+
</tr>
|
|
194
|
+
`).join('')}
|
|
195
|
+
</table>
|
|
196
|
+
${curlCommand ? `
|
|
197
|
+
<div style="margin-top: 15px;">
|
|
198
|
+
<strong>🔧 Curl Command (click to copy):</strong>
|
|
199
|
+
<div class="code-block" style="background: #1e1e1e; color: #d4d4d4; position: relative;">
|
|
200
|
+
<pre style="color: #4ec9b0; white-space: pre-wrap; word-wrap: break-word; font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 13px;">${this.escapeHtml(curlCommand)}</pre>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
` : ''}
|
|
204
|
+
${request.body && Object.keys(request.body).length > 0 ? `
|
|
205
|
+
<div style="margin-top: 12px;">
|
|
206
|
+
<strong>Request Body:</strong>
|
|
207
|
+
<div class="code-block"><pre>${this.escapeHtml(JSON.stringify(request.body, null, 2))}</pre></div>
|
|
208
|
+
</div>
|
|
209
|
+
` : ''}
|
|
210
|
+
</div>
|
|
211
|
+
</div>`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Format request information for text
|
|
215
|
+
*/
|
|
216
|
+
formatRequestInfoText(request) {
|
|
217
|
+
var _a;
|
|
218
|
+
let text = 'HTTP Request Information:\n';
|
|
219
|
+
text += ` Method: ${request.method || 'N/A'}\n`;
|
|
220
|
+
text += ` Full URL: ${(request.baseUrl || '') + (request.url || 'N/A')}\n`;
|
|
221
|
+
text += ` Path: ${request.url || 'N/A'}\n`;
|
|
222
|
+
text += ` IP: ${request.ip || request.remoteAddress || 'N/A'}\n`;
|
|
223
|
+
text += ` Protocol: ${request.protocol || 'http'}\n`;
|
|
224
|
+
text += ` Host: ${request.host || ((_a = request.headers) === null || _a === void 0 ? void 0 : _a.host) || 'N/A'}\n`;
|
|
225
|
+
text += ` Port: ${request.port || 'N/A'}\n`;
|
|
226
|
+
text += ` Request ID: ${request.id || request.requestId || 'N/A'}\n`;
|
|
227
|
+
if (request.query && Object.keys(request.query).length > 0) {
|
|
228
|
+
text += ` Query: ${JSON.stringify(request.query)}\n`;
|
|
229
|
+
}
|
|
230
|
+
if (request.params && Object.keys(request.params).length > 0) {
|
|
231
|
+
text += ` Params: ${JSON.stringify(request.params)}\n`;
|
|
232
|
+
}
|
|
233
|
+
if (request.body && Object.keys(request.body).length > 0) {
|
|
234
|
+
text += ` Body: ${JSON.stringify(request.body, null, 2)}\n`;
|
|
235
|
+
}
|
|
236
|
+
// Add curl command (single line for easy copying)
|
|
237
|
+
const curlCommand = this.generateCurlCommand(request);
|
|
238
|
+
if (curlCommand) {
|
|
239
|
+
text += `\n Curl Command (copy & paste):\n ${curlCommand}\n`;
|
|
240
|
+
}
|
|
241
|
+
return text;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Generate a curl command for the request (single line for easy copying)
|
|
245
|
+
*/
|
|
246
|
+
generateCurlCommand(request) {
|
|
247
|
+
var _a;
|
|
248
|
+
if (!request.method || !request.url) {
|
|
249
|
+
return '';
|
|
250
|
+
}
|
|
251
|
+
// Build full URL with baseUrl (includes protocol, host, port)
|
|
252
|
+
const baseUrl = request.baseUrl || ((_a = request.headers) === null || _a === void 0 ? void 0 : _a.host) || '';
|
|
253
|
+
let fullUrl = baseUrl ? `${baseUrl}${request.url}` : request.url;
|
|
254
|
+
// Add query parameters to URL
|
|
255
|
+
if (request.query && Object.keys(request.query).length > 0) {
|
|
256
|
+
const queryParams = Object.entries(request.query)
|
|
257
|
+
.filter(([_, v]) => v !== undefined && v !== null)
|
|
258
|
+
.map(([k, v]) => [k, String(v)]);
|
|
259
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
260
|
+
if (queryString) {
|
|
261
|
+
fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Start building curl command
|
|
265
|
+
const parts = [];
|
|
266
|
+
// Method (only show if not GET)
|
|
267
|
+
if (request.method && request.method.toUpperCase() !== 'GET') {
|
|
268
|
+
parts.push(`-X ${request.method.toUpperCase()}`);
|
|
269
|
+
}
|
|
270
|
+
// Add headers (include important headers from request)
|
|
271
|
+
if (request.headers) {
|
|
272
|
+
const headerMap = {
|
|
273
|
+
'authorization': 'Authorization',
|
|
274
|
+
'content-type': 'Content-Type',
|
|
275
|
+
'user-agent': 'User-Agent',
|
|
276
|
+
'x-request-id': 'X-Request-ID',
|
|
277
|
+
'referer': 'Referer',
|
|
278
|
+
'cookie': 'Cookie',
|
|
279
|
+
'host': 'Host',
|
|
280
|
+
};
|
|
281
|
+
for (const [key, headerName] of Object.entries(headerMap)) {
|
|
282
|
+
const value = request.headers[key];
|
|
283
|
+
if (value) {
|
|
284
|
+
parts.push(`-H '${headerName}: ${value}'`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Add body if present
|
|
289
|
+
if (request.body && Object.keys(request.body).length > 0) {
|
|
290
|
+
const bodyStr = JSON.stringify(request.body);
|
|
291
|
+
parts.push(`-d '${bodyStr}'`);
|
|
292
|
+
}
|
|
293
|
+
// Combine all parts
|
|
294
|
+
let curl = 'curl';
|
|
295
|
+
if (parts.length > 0) {
|
|
296
|
+
curl += ' ' + parts.join(' ');
|
|
297
|
+
}
|
|
298
|
+
curl += ` '${fullUrl}'`;
|
|
299
|
+
return curl;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Format user information for HTML
|
|
303
|
+
*/
|
|
304
|
+
formatUserInfo(user) {
|
|
305
|
+
const info = [
|
|
306
|
+
{ key: 'User ID', value: user.uid || user.id || user.userId || user.sub || 'N/A' },
|
|
307
|
+
{ key: 'Username', value: user.username || user.name || user.email || 'N/A' },
|
|
308
|
+
{ key: 'Roles', value: user.roles ? user.roles.join(', ') : (user.role || 'N/A') },
|
|
309
|
+
{ key: 'Tenant', value: user.tenantId || user.tenant || 'N/A' },
|
|
310
|
+
];
|
|
311
|
+
return `
|
|
312
|
+
<div class="section">
|
|
313
|
+
<div class="metadata-section user-info">
|
|
314
|
+
<div class="metadata-section-title">👤 User Information</div>
|
|
315
|
+
<table>
|
|
316
|
+
${info.map(({ key, value }) => `
|
|
317
|
+
<tr>
|
|
318
|
+
<td width="150">${key}</td>
|
|
319
|
+
<td class="value">${this.escapeHtml(String(value))}</td>
|
|
320
|
+
</tr>
|
|
321
|
+
`).join('')}
|
|
322
|
+
</table>
|
|
323
|
+
</div>
|
|
324
|
+
</div>`;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Format user information for text
|
|
328
|
+
*/
|
|
329
|
+
formatUserInfoText(user) {
|
|
330
|
+
let text = 'User Information:\n';
|
|
331
|
+
text += ` ID: ${user.uid || user.id || user.userId || user.sub || 'N/A'}\n`;
|
|
332
|
+
text += ` Username: ${user.username || user.name || user.email || 'N/A'}\n`;
|
|
333
|
+
text += ` Roles: ${user.roles ? user.roles.join(', ') : (user.role || 'N/A')}\n`;
|
|
334
|
+
text += ` Tenant: ${user.tenantId || user.tenant || 'N/A'}\n`;
|
|
335
|
+
return text;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Format error object for HTML
|
|
339
|
+
*/
|
|
340
|
+
formatError(err) {
|
|
341
|
+
const errorObj = typeof err === 'string' ? { message: err } : err;
|
|
342
|
+
// Get the stack trace or message
|
|
343
|
+
let stackTrace = errorObj.stack || errorObj.message || String(err);
|
|
344
|
+
// Ensure proper line breaks for stack traces that use \n
|
|
345
|
+
if (typeof stackTrace === 'string') {
|
|
346
|
+
stackTrace = stackTrace.replace(/\\n/g, '\n');
|
|
347
|
+
}
|
|
348
|
+
return `
|
|
349
|
+
<div class="section">
|
|
350
|
+
<div class="section-title">❌ Error Details</div>
|
|
351
|
+
${errorObj.message && errorObj.message !== stackTrace ? `<div style="margin-bottom: 10px;"><strong>Message:</strong> <span class="highlight">${this.escapeHtml(errorObj.message)}</span></div>` : ''}
|
|
352
|
+
<div class="code-block error-stack">
|
|
353
|
+
<pre style="white-space: pre-wrap; word-wrap: break-word;">${this.escapeHtml(String(stackTrace))}</pre>
|
|
354
|
+
</div>
|
|
355
|
+
${errorObj.type ? `<div style="margin-top: 10px;"><strong>Type:</strong> <span class="highlight">${this.escapeHtml(errorObj.type)}</span></div>` : ''}
|
|
356
|
+
${errorObj.code ? `<div><strong>Code:</strong> <span class="highlight">${this.escapeHtml(String(errorObj.code))}</span></div>` : ''}
|
|
357
|
+
</div>`;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Format error object for text
|
|
361
|
+
*/
|
|
362
|
+
formatErrorText(err) {
|
|
363
|
+
const errorObj = typeof err === 'string' ? { message: err } : err;
|
|
364
|
+
let text = errorObj.stack || errorObj.message || String(err);
|
|
365
|
+
// Fix escaped newlines in stack traces
|
|
366
|
+
text = text.replace(/\\n/g, '\n');
|
|
367
|
+
if (errorObj.type && errorObj.type !== text)
|
|
368
|
+
text += `\nType: ${errorObj.type}`;
|
|
369
|
+
if (errorObj.code)
|
|
370
|
+
text += `\nCode: ${errorObj.code}`;
|
|
371
|
+
return text;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Format metadata object for HTML
|
|
375
|
+
*/
|
|
376
|
+
formatMetadata(title, metadata) {
|
|
377
|
+
const rows = Object.entries(metadata)
|
|
378
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
379
|
+
.map(([key, value]) => {
|
|
380
|
+
const displayValue = this.formatValue(value);
|
|
381
|
+
return `<tr><td><strong>${this.escapeHtml(key)}</strong></td><td>${displayValue}</td></tr>`;
|
|
382
|
+
})
|
|
383
|
+
.join('');
|
|
384
|
+
return `
|
|
385
|
+
<div class="section">
|
|
386
|
+
<div class="metadata-section">
|
|
387
|
+
<div class="metadata-section-title">📋 ${title}</div>
|
|
388
|
+
<table>
|
|
389
|
+
${rows}
|
|
390
|
+
</table>
|
|
391
|
+
</div>
|
|
392
|
+
</div>`;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Format a value for HTML display
|
|
396
|
+
*/
|
|
397
|
+
formatValue(value) {
|
|
398
|
+
if (typeof value === 'object') {
|
|
399
|
+
return `<div class="code-block"><pre>${this.escapeHtml(JSON.stringify(value, null, 2))}</pre></div>`;
|
|
400
|
+
}
|
|
401
|
+
return this.escapeHtml(String(value));
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get human-readable log level label
|
|
405
|
+
* Uses shared utility from log-level.helper
|
|
406
|
+
*/
|
|
407
|
+
getLevelLabel(level) {
|
|
408
|
+
return (0, log_level_helper_1.getLevelLabel)(level);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get color for log level
|
|
412
|
+
*/
|
|
413
|
+
getLevelColor(level) {
|
|
414
|
+
const levelNum = (0, log_level_helper_1.getLevelValue)(level);
|
|
415
|
+
const colors = {
|
|
416
|
+
10: '#9e9e9e', // trace - gray
|
|
417
|
+
20: '#2196f3', // debug - blue
|
|
418
|
+
30: '#4caf50', // info - green
|
|
419
|
+
40: '#ff9800', // warn - orange
|
|
420
|
+
50: '#f44336', // error - red
|
|
421
|
+
60: '#d32f2f', // fatal - dark red
|
|
422
|
+
};
|
|
423
|
+
return colors[levelNum] || '#666';
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Escape HTML special characters
|
|
427
|
+
*/
|
|
428
|
+
escapeHtml(text) {
|
|
429
|
+
const map = {
|
|
430
|
+
'&': '&',
|
|
431
|
+
'<': '<',
|
|
432
|
+
'>': '>',
|
|
433
|
+
'"': '"',
|
|
434
|
+
"'": ''',
|
|
435
|
+
};
|
|
436
|
+
return text.replace(/[&<>"']/g, (char) => map[char]);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
exports.EmailLogFormatterService = EmailLogFormatterService;
|
|
440
|
+
exports.EmailLogFormatterService = EmailLogFormatterService = __decorate([
|
|
441
|
+
(0, common_1.Injectable)()
|
|
442
|
+
], EmailLogFormatterService);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Logger, LoggerService, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import type { EmailLogTransportService } from './email-log-transport.service';
|
|
3
|
+
import type { EmailLogOptions } from '../interfaces';
|
|
4
|
+
/**
|
|
5
|
+
* Enhanced Logger service with email alert support
|
|
6
|
+
* This service wraps the standard NestJS Logger and adds email alert functionality
|
|
7
|
+
* for error and fatal level logs.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { EmailLogLogger } from '@nest-omni/core';
|
|
12
|
+
*
|
|
13
|
+
* @Injectable()
|
|
14
|
+
* export class MyService {
|
|
15
|
+
* private readonly logger = new EmailLogLogger(MyService.name);
|
|
16
|
+
*
|
|
17
|
+
* handleError() {
|
|
18
|
+
* this.logger.error('Something went wrong!', error.stack);
|
|
19
|
+
* // This will both log to console and send an email alert
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class EmailLogLogger extends Logger implements LoggerService, OnModuleInit {
|
|
25
|
+
private emailTransportService?;
|
|
26
|
+
private emailTransport?;
|
|
27
|
+
private emailOptions?;
|
|
28
|
+
private minLevel;
|
|
29
|
+
constructor(context?: string, emailTransportService?: EmailLogTransportService);
|
|
30
|
+
/**
|
|
31
|
+
* Initialize with email transport from DI
|
|
32
|
+
*/
|
|
33
|
+
onModuleInit(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Set email transport manually (alternative to DI)
|
|
36
|
+
*/
|
|
37
|
+
setEmailTransport(transport: EmailLogTransportService, options: EmailLogOptions): void;
|
|
38
|
+
/**
|
|
39
|
+
* Log message
|
|
40
|
+
*/
|
|
41
|
+
log(message: any, context?: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Log error message
|
|
44
|
+
*/
|
|
45
|
+
error(message: any, stack?: string, context?: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Log warn message
|
|
48
|
+
*/
|
|
49
|
+
warn(message: any, context?: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Log debug message
|
|
52
|
+
*/
|
|
53
|
+
debug(message: any, context?: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Log verbose message
|
|
56
|
+
*/
|
|
57
|
+
verbose(message: any, context?: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Log fatal error message
|
|
60
|
+
*/
|
|
61
|
+
fatal(message: any, stack?: string, context?: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Send email alert if needed based on log level
|
|
64
|
+
*/
|
|
65
|
+
private sendEmailIfNeeded;
|
|
66
|
+
/**
|
|
67
|
+
* Check if email should be sent for the given log level
|
|
68
|
+
*/
|
|
69
|
+
private shouldSendEmail;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Factory function to create an EmailLogLogger with email transport
|
|
73
|
+
* Use this in your services to get a logger with email alert support
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* import { createEmailLogger } from '@nest-omni/core';
|
|
78
|
+
*
|
|
79
|
+
* @Injectable()
|
|
80
|
+
* export class MyService {
|
|
81
|
+
* private readonly logger = createEmailLogger(MyService.name);
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function createEmailLogger(context: string, emailTransport?: EmailLogTransportService, options?: EmailLogOptions): EmailLogLogger;
|