@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,168 @@
|
|
|
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 __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.EmailLogLogger = void 0;
|
|
22
|
+
exports.createEmailLogger = createEmailLogger;
|
|
23
|
+
const common_1 = require("@nestjs/common");
|
|
24
|
+
const log_level_helper_1 = require("../utils/log-level.helper");
|
|
25
|
+
/**
|
|
26
|
+
* Enhanced Logger service with email alert support
|
|
27
|
+
* This service wraps the standard NestJS Logger and adds email alert functionality
|
|
28
|
+
* for error and fatal level logs.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { EmailLogLogger } from '@nest-omni/core';
|
|
33
|
+
*
|
|
34
|
+
* @Injectable()
|
|
35
|
+
* export class MyService {
|
|
36
|
+
* private readonly logger = new EmailLogLogger(MyService.name);
|
|
37
|
+
*
|
|
38
|
+
* handleError() {
|
|
39
|
+
* this.logger.error('Something went wrong!', error.stack);
|
|
40
|
+
* // This will both log to console and send an email alert
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
let EmailLogLogger = class EmailLogLogger extends common_1.Logger {
|
|
46
|
+
constructor(context, emailTransportService) {
|
|
47
|
+
super(context);
|
|
48
|
+
this.emailTransportService = emailTransportService;
|
|
49
|
+
this.minLevel = 50; // Default to error level
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Initialize with email transport from DI
|
|
53
|
+
*/
|
|
54
|
+
onModuleInit() {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
// This will be called if the service is injected with dependencies
|
|
57
|
+
if (this.emailTransportService) {
|
|
58
|
+
this.emailTransport = this.emailTransportService;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Set email transport manually (alternative to DI)
|
|
64
|
+
*/
|
|
65
|
+
setEmailTransport(transport, options) {
|
|
66
|
+
this.emailTransport = transport;
|
|
67
|
+
this.emailOptions = options;
|
|
68
|
+
this.minLevel = (0, log_level_helper_1.getLevelValue)(options.level);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Log message
|
|
72
|
+
*/
|
|
73
|
+
log(message, context) {
|
|
74
|
+
super.log(message, context);
|
|
75
|
+
this.sendEmailIfNeeded('log', message, context);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Log error message
|
|
79
|
+
*/
|
|
80
|
+
error(message, stack, context) {
|
|
81
|
+
super.error(message, stack, context);
|
|
82
|
+
this.sendEmailIfNeeded('error', message, context, stack);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Log warn message
|
|
86
|
+
*/
|
|
87
|
+
warn(message, context) {
|
|
88
|
+
super.warn(message, context);
|
|
89
|
+
this.sendEmailIfNeeded('warn', message, context);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Log debug message
|
|
93
|
+
*/
|
|
94
|
+
debug(message, context) {
|
|
95
|
+
super.debug(message, context);
|
|
96
|
+
// Don't send email for debug level
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Log verbose message
|
|
100
|
+
*/
|
|
101
|
+
verbose(message, context) {
|
|
102
|
+
super.verbose(message, context);
|
|
103
|
+
// Don't send email for verbose level
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Log fatal error message
|
|
107
|
+
*/
|
|
108
|
+
fatal(message, stack, context) {
|
|
109
|
+
super.error(message, stack, context);
|
|
110
|
+
this.sendEmailIfNeeded('fatal', message, context, stack);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Send email alert if needed based on log level
|
|
114
|
+
*/
|
|
115
|
+
sendEmailIfNeeded(level, message, context, stack) {
|
|
116
|
+
if (!this.emailTransport || !this.shouldSendEmail(level)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
this.emailTransport.write({
|
|
121
|
+
level,
|
|
122
|
+
message: typeof message === 'string' ? message : String(message),
|
|
123
|
+
context,
|
|
124
|
+
stack,
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
// Don't let email errors break the application
|
|
130
|
+
console.error('Failed to send log email:', err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if email should be sent for the given log level
|
|
135
|
+
*/
|
|
136
|
+
shouldSendEmail(level) {
|
|
137
|
+
if (!this.emailTransport || !this.emailOptions) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return (0, log_level_helper_1.getLevelValue)(level) >= this.minLevel;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
exports.EmailLogLogger = EmailLogLogger;
|
|
144
|
+
exports.EmailLogLogger = EmailLogLogger = __decorate([
|
|
145
|
+
(0, common_1.Injectable)(),
|
|
146
|
+
__metadata("design:paramtypes", [String, Function])
|
|
147
|
+
], EmailLogLogger);
|
|
148
|
+
/**
|
|
149
|
+
* Factory function to create an EmailLogLogger with email transport
|
|
150
|
+
* Use this in your services to get a logger with email alert support
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* import { createEmailLogger } from '@nest-omni/core';
|
|
155
|
+
*
|
|
156
|
+
* @Injectable()
|
|
157
|
+
* export class MyService {
|
|
158
|
+
* private readonly logger = createEmailLogger(MyService.name);
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
function createEmailLogger(context, emailTransport, options) {
|
|
163
|
+
const logger = new EmailLogLogger(context, emailTransport);
|
|
164
|
+
if (emailTransport && options) {
|
|
165
|
+
logger.setEmailTransport(emailTransport, options);
|
|
166
|
+
}
|
|
167
|
+
return logger;
|
|
168
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* Token bucket rate limiter for email logs
|
|
4
|
+
* Prevents email spam during error storms
|
|
5
|
+
*
|
|
6
|
+
* Similar pattern to CacheModule's LRUCacheProvider
|
|
7
|
+
*/
|
|
8
|
+
export declare class EmailLogRateLimiterService implements OnModuleDestroy {
|
|
9
|
+
private readonly maxEmails;
|
|
10
|
+
private readonly windowMs;
|
|
11
|
+
private readonly burstSize;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
private tokenBucket;
|
|
14
|
+
private refillTimer;
|
|
15
|
+
constructor(maxEmails: number, windowMs: number, burstSize: number);
|
|
16
|
+
/**
|
|
17
|
+
* Check if email should be sent based on rate limit
|
|
18
|
+
* @param key - Rate limit key (e.g., 'error:global' or 'error:appname')
|
|
19
|
+
* @returns true if allowed, false if rate limited
|
|
20
|
+
*/
|
|
21
|
+
canSend(key: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Get current state for monitoring/debugging
|
|
24
|
+
*/
|
|
25
|
+
getState(key: string): {
|
|
26
|
+
tokens: number;
|
|
27
|
+
lastRefill: number;
|
|
28
|
+
} | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Reset rate limit for a key (admin function)
|
|
31
|
+
*/
|
|
32
|
+
reset(key: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Start periodic token refill
|
|
35
|
+
* Refills all buckets every second to prevent token starvation
|
|
36
|
+
*/
|
|
37
|
+
private startRefillTimer;
|
|
38
|
+
/**
|
|
39
|
+
* Cleanup on module destroy
|
|
40
|
+
*/
|
|
41
|
+
onModuleDestroy(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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 __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var EmailLogRateLimiterService_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.EmailLogRateLimiterService = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
/**
|
|
16
|
+
* Token bucket rate limiter for email logs
|
|
17
|
+
* Prevents email spam during error storms
|
|
18
|
+
*
|
|
19
|
+
* Similar pattern to CacheModule's LRUCacheProvider
|
|
20
|
+
*/
|
|
21
|
+
let EmailLogRateLimiterService = EmailLogRateLimiterService_1 = class EmailLogRateLimiterService {
|
|
22
|
+
constructor(maxEmails, windowMs, burstSize) {
|
|
23
|
+
this.maxEmails = maxEmails;
|
|
24
|
+
this.windowMs = windowMs;
|
|
25
|
+
this.burstSize = burstSize;
|
|
26
|
+
this.logger = new common_1.Logger(EmailLogRateLimiterService_1.name);
|
|
27
|
+
this.tokenBucket = new Map();
|
|
28
|
+
this.refillTimer = null;
|
|
29
|
+
// Start periodic refill timer (every second)
|
|
30
|
+
this.startRefillTimer();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if email should be sent based on rate limit
|
|
34
|
+
* @param key - Rate limit key (e.g., 'error:global' or 'error:appname')
|
|
35
|
+
* @returns true if allowed, false if rate limited
|
|
36
|
+
*/
|
|
37
|
+
canSend(key) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
let state = this.tokenBucket.get(key);
|
|
40
|
+
if (!state) {
|
|
41
|
+
// First request - initialize with burst capacity
|
|
42
|
+
state = {
|
|
43
|
+
tokens: this.burstSize,
|
|
44
|
+
lastRefill: now,
|
|
45
|
+
};
|
|
46
|
+
this.tokenBucket.set(key, state);
|
|
47
|
+
}
|
|
48
|
+
// Refill tokens based on time elapsed
|
|
49
|
+
const elapsed = now - state.lastRefill;
|
|
50
|
+
const tokensToAdd = (elapsed / this.windowMs) * this.maxEmails;
|
|
51
|
+
state.tokens = Math.min(this.maxEmails, state.tokens + tokensToAdd);
|
|
52
|
+
state.lastRefill = now;
|
|
53
|
+
// Check if we have tokens available
|
|
54
|
+
if (state.tokens >= 1) {
|
|
55
|
+
state.tokens -= 1;
|
|
56
|
+
this.tokenBucket.set(key, state);
|
|
57
|
+
this.logger.debug(`Rate limit check passed for key: ${key}, remaining tokens: ${state.tokens.toFixed(2)}`);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
this.logger.warn(`Rate limit exceeded for key: ${key}, tokens: ${state.tokens.toFixed(2)}`);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get current state for monitoring/debugging
|
|
65
|
+
*/
|
|
66
|
+
getState(key) {
|
|
67
|
+
const state = this.tokenBucket.get(key);
|
|
68
|
+
return state ? Object.assign({}, state) : undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reset rate limit for a key (admin function)
|
|
72
|
+
*/
|
|
73
|
+
reset(key) {
|
|
74
|
+
this.tokenBucket.delete(key);
|
|
75
|
+
this.logger.debug(`Rate limit reset for key: ${key}`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Start periodic token refill
|
|
79
|
+
* Refills all buckets every second to prevent token starvation
|
|
80
|
+
*/
|
|
81
|
+
startRefillTimer() {
|
|
82
|
+
this.refillTimer = setInterval(() => {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
for (const [key, state] of this.tokenBucket.entries()) {
|
|
85
|
+
const elapsed = now - state.lastRefill;
|
|
86
|
+
if (elapsed > 0) {
|
|
87
|
+
const tokensToAdd = (elapsed / this.windowMs) * this.maxEmails;
|
|
88
|
+
state.tokens = Math.min(this.maxEmails, state.tokens + tokensToAdd);
|
|
89
|
+
state.lastRefill = now;
|
|
90
|
+
this.tokenBucket.set(key, state);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}, 1000); // Refill every second
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Cleanup on module destroy
|
|
97
|
+
*/
|
|
98
|
+
onModuleDestroy() {
|
|
99
|
+
if (this.refillTimer) {
|
|
100
|
+
clearInterval(this.refillTimer);
|
|
101
|
+
this.refillTimer = null;
|
|
102
|
+
}
|
|
103
|
+
this.tokenBucket.clear();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
exports.EmailLogRateLimiterService = EmailLogRateLimiterService;
|
|
107
|
+
exports.EmailLogRateLimiterService = EmailLogRateLimiterService = EmailLogRateLimiterService_1 = __decorate([
|
|
108
|
+
(0, common_1.Injectable)(),
|
|
109
|
+
__metadata("design:paramtypes", [Number, Number, Number])
|
|
110
|
+
], EmailLogRateLimiterService);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { LogDescriptor } from 'pino';
|
|
2
|
+
import { Writable } from 'stream';
|
|
3
|
+
import { EmailLogRateLimiterService } from './email-log-rate-limiter.service';
|
|
4
|
+
import { EmailLogFormatterService } from './email-log-formatter.service';
|
|
5
|
+
import { EmailLogOptions } from '../interfaces';
|
|
6
|
+
import type { IEmailLogTransport } from '../interfaces';
|
|
7
|
+
/**
|
|
8
|
+
* Pino email transport service
|
|
9
|
+
* Implements Writable stream interface for Pino compatibility
|
|
10
|
+
*
|
|
11
|
+
* Follows the pattern of RedisLock and Cache decorators:
|
|
12
|
+
* - Global service holder pattern
|
|
13
|
+
* - Graceful error handling
|
|
14
|
+
* - Configurable via environment variables
|
|
15
|
+
*/
|
|
16
|
+
export declare class EmailLogTransportService extends Writable implements IEmailLogTransport, NodeJS.WritableStream {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly rateLimiter;
|
|
19
|
+
private readonly formatter;
|
|
20
|
+
private readonly logger;
|
|
21
|
+
private readonly appName;
|
|
22
|
+
private emailProvider;
|
|
23
|
+
private isClosing;
|
|
24
|
+
constructor(options: EmailLogOptions, rateLimiter: EmailLogRateLimiterService, formatter: EmailLogFormatterService);
|
|
25
|
+
/**
|
|
26
|
+
* Write method - implements Writable stream interface
|
|
27
|
+
* Called by Pino for each log entry
|
|
28
|
+
*/
|
|
29
|
+
_write(data: LogDescriptor, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
|
|
30
|
+
/**
|
|
31
|
+
* Writev method - handles batch writes
|
|
32
|
+
*/
|
|
33
|
+
_writev(chunks: {
|
|
34
|
+
chunk: LogDescriptor;
|
|
35
|
+
encoding: BufferEncoding;
|
|
36
|
+
}[], callback: (error?: Error | null) => void): void;
|
|
37
|
+
/**
|
|
38
|
+
* Final method - cleanup before stream closes
|
|
39
|
+
*/
|
|
40
|
+
_final(callback: (error?: Error | null) => void): void;
|
|
41
|
+
/**
|
|
42
|
+
* Process a single log entry
|
|
43
|
+
*/
|
|
44
|
+
private processLog;
|
|
45
|
+
/**
|
|
46
|
+
* Check if log should trigger email based on configuration
|
|
47
|
+
*/
|
|
48
|
+
private shouldSendLog;
|
|
49
|
+
/**
|
|
50
|
+
* Get rate limit key for this log entry
|
|
51
|
+
* Uses 'email-log:appName:level' by default
|
|
52
|
+
*/
|
|
53
|
+
private getRateLimitKey;
|
|
54
|
+
/**
|
|
55
|
+
* Get level label from numeric level
|
|
56
|
+
*/
|
|
57
|
+
private getLevelLabel;
|
|
58
|
+
/**
|
|
59
|
+
* Get level value from level name
|
|
60
|
+
*/
|
|
61
|
+
private getLevelValueFromName;
|
|
62
|
+
/**
|
|
63
|
+
* Format and send email
|
|
64
|
+
*/
|
|
65
|
+
private sendEmail;
|
|
66
|
+
/**
|
|
67
|
+
* Check if transport is enabled for given log level
|
|
68
|
+
* Part of IEmailLogTransport interface
|
|
69
|
+
*/
|
|
70
|
+
shouldSend(level: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Flush any pending writes
|
|
73
|
+
* Part of IEmailLogTransport interface
|
|
74
|
+
*/
|
|
75
|
+
flush(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Cleanup on module destroy
|
|
78
|
+
*/
|
|
79
|
+
onModuleDestroy(): Promise<void>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
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 __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
|
+
};
|
|
14
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
var EmailLogTransportService_1;
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.EmailLogTransportService = void 0;
|
|
26
|
+
const common_1 = require("@nestjs/common");
|
|
27
|
+
const pino_1 = require("pino");
|
|
28
|
+
const stream_1 = require("stream");
|
|
29
|
+
const email_log_rate_limiter_service_1 = require("./email-log-rate-limiter.service");
|
|
30
|
+
const email_log_formatter_service_1 = require("./email-log-formatter.service");
|
|
31
|
+
const email_provider_1 = require("../providers/email-provider");
|
|
32
|
+
const email_log_constants_1 = require("../email-log.constants");
|
|
33
|
+
const interfaces_1 = require("../interfaces");
|
|
34
|
+
/**
|
|
35
|
+
* Pino email transport service
|
|
36
|
+
* Implements Writable stream interface for Pino compatibility
|
|
37
|
+
*
|
|
38
|
+
* Follows the pattern of RedisLock and Cache decorators:
|
|
39
|
+
* - Global service holder pattern
|
|
40
|
+
* - Graceful error handling
|
|
41
|
+
* - Configurable via environment variables
|
|
42
|
+
*/
|
|
43
|
+
let EmailLogTransportService = EmailLogTransportService_1 = class EmailLogTransportService extends stream_1.Writable {
|
|
44
|
+
constructor(options, rateLimiter, formatter) {
|
|
45
|
+
super({ objectMode: true }); // Enable object mode for Pino logs
|
|
46
|
+
this.options = options;
|
|
47
|
+
this.rateLimiter = rateLimiter;
|
|
48
|
+
this.formatter = formatter;
|
|
49
|
+
this.logger = new common_1.Logger(EmailLogTransportService_1.name);
|
|
50
|
+
this.isClosing = false;
|
|
51
|
+
this.appName = process.env.NAME || process.env.APP_NAME || 'omni-app';
|
|
52
|
+
this.emailProvider = new email_provider_1.EmailLogProvider({
|
|
53
|
+
smtpHost: options.smtpHost,
|
|
54
|
+
smtpPort: options.smtpPort || 587,
|
|
55
|
+
smtpUsername: options.smtpUsername,
|
|
56
|
+
smtpPassword: options.smtpPassword,
|
|
57
|
+
smtpSecure: options.smtpSecure || false,
|
|
58
|
+
smtpIgnoreTLSError: options.smtpIgnoreTLSError || false,
|
|
59
|
+
});
|
|
60
|
+
this.logger.log(`Email log transport initialized for app: ${this.appName}, level: ${this.options.level}`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Write method - implements Writable stream interface
|
|
64
|
+
* Called by Pino for each log entry
|
|
65
|
+
*/
|
|
66
|
+
_write(data, _encoding, callback) {
|
|
67
|
+
if (this.isClosing) {
|
|
68
|
+
callback();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.processLog(data)
|
|
72
|
+
.then(() => callback())
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
this.logger.error(`Error processing log entry: ${error.message}`, error.stack);
|
|
75
|
+
callback(); // Don't fail the stream, just log the error
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Writev method - handles batch writes
|
|
80
|
+
*/
|
|
81
|
+
_writev(chunks, callback) {
|
|
82
|
+
if (this.isClosing) {
|
|
83
|
+
callback();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
Promise.all(chunks.map(({ chunk }) => this.processLog(chunk)))
|
|
87
|
+
.then(() => callback())
|
|
88
|
+
.catch((error) => {
|
|
89
|
+
this.logger.error(`Error processing log batch: ${error.message}`, error.stack);
|
|
90
|
+
callback();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Final method - cleanup before stream closes
|
|
95
|
+
*/
|
|
96
|
+
_final(callback) {
|
|
97
|
+
this.isClosing = true;
|
|
98
|
+
this.emailProvider
|
|
99
|
+
.close()
|
|
100
|
+
.then(() => callback())
|
|
101
|
+
.catch((error) => {
|
|
102
|
+
this.logger.error(`Error closing email provider: ${error.message}`);
|
|
103
|
+
callback();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Process a single log entry
|
|
108
|
+
*/
|
|
109
|
+
processLog(log) {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
try {
|
|
112
|
+
// Check if transport is enabled for this log level
|
|
113
|
+
if (!this.shouldSendLog(log)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Check rate limiting
|
|
117
|
+
const rateLimitKey = this.getRateLimitKey(log);
|
|
118
|
+
if (!this.rateLimiter.canSend(rateLimitKey)) {
|
|
119
|
+
this.logger.debug(`Rate limited email for log: ${log.msg}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Format and send email
|
|
123
|
+
yield this.sendEmail(log);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
this.logger.error(`Failed to send log email: ${error.message}`, error.stack);
|
|
127
|
+
// Don't throw - we don't want to break logging
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if log should trigger email based on configuration
|
|
133
|
+
*/
|
|
134
|
+
shouldSendLog(log) {
|
|
135
|
+
var _a;
|
|
136
|
+
const { level } = log;
|
|
137
|
+
const levelValue = typeof level === 'string'
|
|
138
|
+
? ((_a = pino_1.levels[level]) !== null && _a !== void 0 ? _a : 0)
|
|
139
|
+
: level;
|
|
140
|
+
const thresholdValue = this.getLevelValueFromName(this.options.level);
|
|
141
|
+
// Check log level threshold
|
|
142
|
+
if (levelValue < thresholdValue) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
// Check app filters
|
|
146
|
+
if (this.options.includeApps && this.options.includeApps.length > 0) {
|
|
147
|
+
if (!this.options.includeApps.includes(this.appName)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (this.options.excludeApps && this.options.excludeApps.includes(this.appName)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
// Check error type filters
|
|
155
|
+
if (log.err) {
|
|
156
|
+
const errorType = log.err.type || log.err.name;
|
|
157
|
+
if (this.options.includeErrors &&
|
|
158
|
+
this.options.includeErrors.length > 0) {
|
|
159
|
+
if (!this.options.includeErrors.includes(errorType)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (this.options.excludeErrors &&
|
|
164
|
+
this.options.excludeErrors.includes(errorType)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get rate limit key for this log entry
|
|
172
|
+
* Uses 'email-log:appName:level' by default
|
|
173
|
+
*/
|
|
174
|
+
getRateLimitKey(log) {
|
|
175
|
+
const levelLabel = typeof log.level === 'string' ? log.level : this.getLevelLabel(log.level);
|
|
176
|
+
return `email-log:${this.appName}:${levelLabel}`;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get level label from numeric level
|
|
180
|
+
*/
|
|
181
|
+
getLevelLabel(level) {
|
|
182
|
+
const labels = {
|
|
183
|
+
10: 'trace',
|
|
184
|
+
20: 'debug',
|
|
185
|
+
30: 'info',
|
|
186
|
+
40: 'warn',
|
|
187
|
+
50: 'error',
|
|
188
|
+
60: 'fatal',
|
|
189
|
+
};
|
|
190
|
+
return labels[level] || 'unknown';
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get level value from level name
|
|
194
|
+
*/
|
|
195
|
+
getLevelValueFromName(levelName) {
|
|
196
|
+
var _a;
|
|
197
|
+
const nameToValue = {
|
|
198
|
+
trace: 10,
|
|
199
|
+
debug: 20,
|
|
200
|
+
info: 30,
|
|
201
|
+
warn: 40,
|
|
202
|
+
error: 50,
|
|
203
|
+
fatal: 60,
|
|
204
|
+
};
|
|
205
|
+
return (_a = nameToValue[levelName]) !== null && _a !== void 0 ? _a : 50;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Format and send email
|
|
209
|
+
*/
|
|
210
|
+
sendEmail(log) {
|
|
211
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
212
|
+
const subjectPrefix = this.options.subjectPrefix || '[Log Alert]';
|
|
213
|
+
const emailContent = this.options.useHtmlFormat
|
|
214
|
+
? this.formatter.formatHtml(log, this.appName, subjectPrefix)
|
|
215
|
+
: this.formatter.formatText(log, this.appName, subjectPrefix);
|
|
216
|
+
yield this.emailProvider.sendLogEmail({
|
|
217
|
+
to: this.options.to,
|
|
218
|
+
cc: this.options.cc,
|
|
219
|
+
from: this.options.from,
|
|
220
|
+
subject: emailContent.subject,
|
|
221
|
+
html: 'html' in emailContent ? emailContent.html : undefined,
|
|
222
|
+
text: 'text' in emailContent ? emailContent.text : undefined,
|
|
223
|
+
});
|
|
224
|
+
this.logger.debug(`Log email sent for ${this.appName}: ${log.msg}`);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Check if transport is enabled for given log level
|
|
229
|
+
* Part of IEmailLogTransport interface
|
|
230
|
+
*/
|
|
231
|
+
shouldSend(level) {
|
|
232
|
+
const levelValue = this.getLevelValueFromName(level);
|
|
233
|
+
const thresholdValue = this.getLevelValueFromName(this.options.level);
|
|
234
|
+
return levelValue >= thresholdValue;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Flush any pending writes
|
|
238
|
+
* Part of IEmailLogTransport interface
|
|
239
|
+
*/
|
|
240
|
+
flush() {
|
|
241
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
this.end((error) => {
|
|
244
|
+
if (error)
|
|
245
|
+
reject(error);
|
|
246
|
+
else
|
|
247
|
+
resolve();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Cleanup on module destroy
|
|
254
|
+
*/
|
|
255
|
+
onModuleDestroy() {
|
|
256
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
257
|
+
this.isClosing = true;
|
|
258
|
+
yield this.emailProvider.close();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
exports.EmailLogTransportService = EmailLogTransportService;
|
|
263
|
+
exports.EmailLogTransportService = EmailLogTransportService = EmailLogTransportService_1 = __decorate([
|
|
264
|
+
(0, common_1.Injectable)(),
|
|
265
|
+
__param(0, (0, common_1.Inject)(email_log_constants_1.EMAIL_LOG_OPTIONS)),
|
|
266
|
+
__param(1, (0, common_1.Inject)(email_log_constants_1.EMAIL_LOG_RATE_LIMITER)),
|
|
267
|
+
__param(2, (0, common_1.Inject)(email_log_constants_1.EMAIL_LOG_FORMATTER)),
|
|
268
|
+
__metadata("design:paramtypes", [interfaces_1.EmailLogOptions,
|
|
269
|
+
email_log_rate_limiter_service_1.EmailLogRateLimiterService,
|
|
270
|
+
email_log_formatter_service_1.EmailLogFormatterService])
|
|
271
|
+
], EmailLogTransportService);
|