@cryptexlabs/codex-nodejs-common 0.16.7 → 0.16.9
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/lib/package.json +1 -1
- package/lib/src/exception/friendly-http-exception.d.ts +1 -2
- package/lib/src/exception/friendly-http-exception.js +7 -2
- package/lib/src/exception/friendly-http-exception.js.map +1 -1
- package/lib/src/filter/app-http-exception-filter.d.ts +1 -0
- package/lib/src/filter/app-http-exception-filter.js +11 -3
- package/lib/src/filter/app-http-exception-filter.js.map +1 -1
- package/package.json +1 -1
- package/src/exception/friendly-http-exception.spec.ts +99 -0
- package/src/exception/friendly-http-exception.ts +6 -1
- package/src/filter/app-http-exception-filter.spec.ts +186 -0
- package/src/filter/app-http-exception-filter.ts +11 -3
package/lib/package.json
CHANGED
|
@@ -3,6 +3,5 @@ import { Context } from "../context";
|
|
|
3
3
|
export declare class FriendlyHttpException extends HttpException {
|
|
4
4
|
readonly context: Context;
|
|
5
5
|
readonly userMessage: string;
|
|
6
|
-
|
|
7
|
-
constructor(response: string | Record<string, any>, context: Context, userMessage: string, status: HttpStatus, stack: string);
|
|
6
|
+
constructor(response: string | Record<string, any>, context: Context, userMessage: string, status: HttpStatus, customStack?: string);
|
|
8
7
|
}
|
|
@@ -3,11 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FriendlyHttpException = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
class FriendlyHttpException extends common_1.HttpException {
|
|
6
|
-
constructor(response, context, userMessage, status,
|
|
6
|
+
constructor(response, context, userMessage, status, customStack) {
|
|
7
7
|
super(response, status);
|
|
8
8
|
this.context = context;
|
|
9
9
|
this.userMessage = userMessage;
|
|
10
|
-
|
|
10
|
+
if (customStack) {
|
|
11
|
+
this.stack = customStack;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
Error.captureStackTrace(this, this.constructor);
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
}
|
|
13
18
|
exports.FriendlyHttpException = FriendlyHttpException;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"friendly-http-exception.js","sourceRoot":"","sources":["../../../src/exception/friendly-http-exception.ts"],"names":[],"mappings":";;;AAAA,2CAA2D;AAG3D,MAAa,qBAAsB,SAAQ,sBAAa;IACtD,YACE,QAAsC,EACtB,OAAgB,EAChB,WAAmB,EACnC,MAAkB,
|
|
1
|
+
{"version":3,"file":"friendly-http-exception.js","sourceRoot":"","sources":["../../../src/exception/friendly-http-exception.ts"],"names":[],"mappings":";;;AAAA,2CAA2D;AAG3D,MAAa,qBAAsB,SAAQ,sBAAa;IACtD,YACE,QAAsC,EACtB,OAAgB,EAChB,WAAmB,EACnC,MAAkB,EAElB,WAAoB;QAEpB,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QANR,YAAO,GAAP,OAAO,CAAS;QAChB,gBAAW,GAAX,WAAW,CAAQ;QAMnC,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;CACF;AAhBD,sDAgBC"}
|
|
@@ -8,6 +8,7 @@ export declare class AppHttpExceptionFilter extends BaseExceptionFilter {
|
|
|
8
8
|
private readonly config;
|
|
9
9
|
private _lastErrorLogTime;
|
|
10
10
|
private _suppressedErrorCount;
|
|
11
|
+
private _lastSuppressedError;
|
|
11
12
|
constructor(fallbackLocale: Locale, fallbackLogger: LoggerService, config: DefaultConfig, applicationRef?: HttpServer);
|
|
12
13
|
private _isDebugLevel;
|
|
13
14
|
private _shouldLogError;
|
|
@@ -32,11 +32,12 @@ let AppHttpExceptionFilter = class AppHttpExceptionFilter extends core_1.BaseExc
|
|
|
32
32
|
this.config = config;
|
|
33
33
|
this._lastErrorLogTime = 0;
|
|
34
34
|
this._suppressedErrorCount = 0;
|
|
35
|
+
this._lastSuppressedError = null;
|
|
35
36
|
}
|
|
36
37
|
_isDebugLevel() {
|
|
37
38
|
return this.config.logLevels.some((level) => level.toLowerCase() === "debug");
|
|
38
39
|
}
|
|
39
|
-
_shouldLogError() {
|
|
40
|
+
_shouldLogError(exception) {
|
|
40
41
|
if (this._isDebugLevel()) {
|
|
41
42
|
return true;
|
|
42
43
|
}
|
|
@@ -47,6 +48,9 @@ let AppHttpExceptionFilter = class AppHttpExceptionFilter extends core_1.BaseExc
|
|
|
47
48
|
return true;
|
|
48
49
|
}
|
|
49
50
|
this._suppressedErrorCount++;
|
|
51
|
+
if (exception) {
|
|
52
|
+
this._lastSuppressedError = exception;
|
|
53
|
+
}
|
|
50
54
|
return false;
|
|
51
55
|
}
|
|
52
56
|
catch(exception, host) {
|
|
@@ -96,7 +100,7 @@ let AppHttpExceptionFilter = class AppHttpExceptionFilter extends core_1.BaseExc
|
|
|
96
100
|
else {
|
|
97
101
|
logger.error(`No stack available for error of type ${typeof exception}`);
|
|
98
102
|
}
|
|
99
|
-
const shouldLogError = this._shouldLogError();
|
|
103
|
+
const shouldLogError = this._shouldLogError(exception);
|
|
100
104
|
if (shouldLogError) {
|
|
101
105
|
if (developerText) {
|
|
102
106
|
if (this.config.loggerType === "pino") {
|
|
@@ -130,8 +134,12 @@ let AppHttpExceptionFilter = class AppHttpExceptionFilter extends core_1.BaseExc
|
|
|
130
134
|
logger.error(exception.stack);
|
|
131
135
|
}
|
|
132
136
|
if (this._suppressedErrorCount > 0) {
|
|
133
|
-
|
|
137
|
+
const sampleError = this._lastSuppressedError
|
|
138
|
+
? this.getDeveloperText(this._lastSuppressedError, fallbackLocale)
|
|
139
|
+
: "No sample available";
|
|
140
|
+
logger.error(`Suppressed ${this._suppressedErrorCount} error(s) in the last minute due to rate limiting. Sample: ${sampleError}`);
|
|
134
141
|
this._suppressedErrorCount = 0;
|
|
142
|
+
this._lastSuppressedError = null;
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
let status;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-http-exception-filter.js","sourceRoot":"","sources":["../../../src/filter/app-http-exception-filter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,uCAAmD;AAEnD,0CAA8D;AAC9D,4CAAqD;AACrD,oEAAuD;AACvD,gDAA8C;AAC9C,0CAA8C;AAC9C,sCAA0C;AAC1C,4CAAuD;AACvD,sCAA0C;AAC1C,kCAAqC;AAI9B,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,0BAAmB;
|
|
1
|
+
{"version":3,"file":"app-http-exception-filter.js","sourceRoot":"","sources":["../../../src/filter/app-http-exception-filter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FriendlyHttpException } from "./friendly-http-exception";
|
|
2
|
+
import { HttpStatus, LoggerService } from "@nestjs/common";
|
|
3
|
+
import { Context } from "../context";
|
|
4
|
+
import { Locale, MessageContextInterface } from "@cryptexlabs/codex-data-model";
|
|
5
|
+
import { DefaultConfig } from "../config";
|
|
6
|
+
import { instance, mock, when } from "ts-mockito";
|
|
7
|
+
|
|
8
|
+
describe("FriendlyHttpException", () => {
|
|
9
|
+
let mockContext: Context;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
const mockConfig = mock(DefaultConfig);
|
|
13
|
+
const config = instance(mockConfig);
|
|
14
|
+
when(mockConfig.logLevels).thenReturn(["info"]);
|
|
15
|
+
when(mockConfig.appName).thenReturn("test-app");
|
|
16
|
+
when(mockConfig.appVersion).thenReturn("1.0.0");
|
|
17
|
+
when(mockConfig.environmentName).thenReturn("test");
|
|
18
|
+
when(mockConfig.clientId).thenReturn("test-client");
|
|
19
|
+
|
|
20
|
+
const mockLogger = mock<LoggerService>();
|
|
21
|
+
const logger = instance(mockLogger);
|
|
22
|
+
|
|
23
|
+
const mockMessageContext = mock<MessageContextInterface>();
|
|
24
|
+
const messageContext = instance(mockMessageContext);
|
|
25
|
+
|
|
26
|
+
mockContext = new Context(
|
|
27
|
+
"test-correlation-id",
|
|
28
|
+
logger,
|
|
29
|
+
config,
|
|
30
|
+
{
|
|
31
|
+
id: "client-id",
|
|
32
|
+
name: "client-name",
|
|
33
|
+
version: "1.0.0",
|
|
34
|
+
variant: "test",
|
|
35
|
+
},
|
|
36
|
+
new Locale("en", "US"),
|
|
37
|
+
messageContext
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("stack trace handling", () => {
|
|
42
|
+
it("should capture stack trace automatically when no custom stack is provided", () => {
|
|
43
|
+
const exception = new FriendlyHttpException(
|
|
44
|
+
"Test error message",
|
|
45
|
+
mockContext,
|
|
46
|
+
"User friendly message",
|
|
47
|
+
HttpStatus.BAD_REQUEST
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(exception.stack).toBeDefined();
|
|
51
|
+
expect(typeof exception.stack).toBe("string");
|
|
52
|
+
expect(exception.stack).toContain("FriendlyHttpException");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should use custom stack when provided", () => {
|
|
56
|
+
const customStack = "CustomStack\nLine1\nLine2";
|
|
57
|
+
|
|
58
|
+
const exception = new FriendlyHttpException(
|
|
59
|
+
"Test error message",
|
|
60
|
+
mockContext,
|
|
61
|
+
"User friendly message",
|
|
62
|
+
HttpStatus.BAD_REQUEST,
|
|
63
|
+
customStack
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(exception.stack).toBe(customStack);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should have stack property accessible for logging", () => {
|
|
70
|
+
const exception = new FriendlyHttpException(
|
|
71
|
+
"Test error message",
|
|
72
|
+
mockContext,
|
|
73
|
+
"User friendly message",
|
|
74
|
+
HttpStatus.INTERNAL_SERVER_ERROR
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(exception.stack).toBeDefined();
|
|
78
|
+
expect(exception.name).toBe("FriendlyHttpException");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should preserve other exception properties", () => {
|
|
82
|
+
const userMessage = "Something went wrong";
|
|
83
|
+
const response = "Developer error message";
|
|
84
|
+
const status = HttpStatus.NOT_FOUND;
|
|
85
|
+
|
|
86
|
+
const exception = new FriendlyHttpException(
|
|
87
|
+
response,
|
|
88
|
+
mockContext,
|
|
89
|
+
userMessage,
|
|
90
|
+
status
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(exception.userMessage).toBe(userMessage);
|
|
94
|
+
expect(exception.context).toBe(mockContext);
|
|
95
|
+
expect(exception.getStatus()).toBe(status);
|
|
96
|
+
expect(exception.message).toBe(response);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -8,8 +8,13 @@ export class FriendlyHttpException extends HttpException {
|
|
|
8
8
|
public readonly userMessage: string,
|
|
9
9
|
status: HttpStatus,
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
customStack?: string
|
|
12
12
|
) {
|
|
13
13
|
super(response, status);
|
|
14
|
+
if (customStack) {
|
|
15
|
+
this.stack = customStack;
|
|
16
|
+
} else {
|
|
17
|
+
Error.captureStackTrace(this, this.constructor);
|
|
18
|
+
}
|
|
14
19
|
}
|
|
15
20
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { AppHttpExceptionFilter } from "./app-http-exception-filter";
|
|
2
|
+
import { DefaultConfig } from "../config";
|
|
3
|
+
import { Locale } from "@cryptexlabs/codex-data-model";
|
|
4
|
+
import { instance, mock, when } from "ts-mockito";
|
|
5
|
+
import { LoggerService, HttpException, HttpStatus } from "@nestjs/common";
|
|
6
|
+
|
|
7
|
+
describe("AppHttpExceptionFilter", () => {
|
|
8
|
+
let filter: AppHttpExceptionFilter;
|
|
9
|
+
let configMock: DefaultConfig;
|
|
10
|
+
let loggerMock: LoggerService;
|
|
11
|
+
let fallbackLocale: Locale;
|
|
12
|
+
|
|
13
|
+
const setupFilter = () => {
|
|
14
|
+
configMock = mock(DefaultConfig);
|
|
15
|
+
loggerMock = {
|
|
16
|
+
error: jest.fn(),
|
|
17
|
+
log: jest.fn(),
|
|
18
|
+
warn: jest.fn(),
|
|
19
|
+
debug: jest.fn(),
|
|
20
|
+
verbose: jest.fn(),
|
|
21
|
+
} as any;
|
|
22
|
+
|
|
23
|
+
fallbackLocale = {
|
|
24
|
+
language: "en",
|
|
25
|
+
country: "US",
|
|
26
|
+
i18n: "en-US",
|
|
27
|
+
} as Locale;
|
|
28
|
+
|
|
29
|
+
when(configMock.logLevels).thenReturn(["error", "warn"]);
|
|
30
|
+
when(configMock.errorLogIntervalMs).thenReturn(60000);
|
|
31
|
+
when(configMock.loggerType).thenReturn("pino");
|
|
32
|
+
when(configMock.appName).thenReturn("test-app");
|
|
33
|
+
when(configMock.appVersion).thenReturn("1.0.0");
|
|
34
|
+
when(configMock.environmentName).thenReturn("test");
|
|
35
|
+
when(configMock.clientId).thenReturn("test-client");
|
|
36
|
+
|
|
37
|
+
filter = new AppHttpExceptionFilter(
|
|
38
|
+
fallbackLocale,
|
|
39
|
+
loggerMock,
|
|
40
|
+
instance(configMock)
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe("rate limiting", () => {
|
|
45
|
+
let originalDateNow: () => number;
|
|
46
|
+
let currentTime: number;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
setupFilter();
|
|
50
|
+
originalDateNow = Date.now;
|
|
51
|
+
currentTime = 1000000;
|
|
52
|
+
Date.now = jest.fn(() => currentTime);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
Date.now = originalDateNow;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should capture and log sample error when errors are suppressed", () => {
|
|
60
|
+
const mockHost = {
|
|
61
|
+
switchToHttp: () => ({
|
|
62
|
+
getRequest: () => ({
|
|
63
|
+
headers: {},
|
|
64
|
+
body: {},
|
|
65
|
+
url: "/test",
|
|
66
|
+
method: "GET",
|
|
67
|
+
}),
|
|
68
|
+
getResponse: () => ({
|
|
69
|
+
status: () => ({
|
|
70
|
+
json: () => {},
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
74
|
+
} as any;
|
|
75
|
+
|
|
76
|
+
const firstException = new HttpException(
|
|
77
|
+
"First error message",
|
|
78
|
+
HttpStatus.BAD_REQUEST
|
|
79
|
+
);
|
|
80
|
+
const secondException = new HttpException(
|
|
81
|
+
"Second error message",
|
|
82
|
+
HttpStatus.BAD_REQUEST
|
|
83
|
+
);
|
|
84
|
+
const thirdException = new HttpException(
|
|
85
|
+
"Third error message",
|
|
86
|
+
HttpStatus.BAD_REQUEST
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
filter.catch(firstException, mockHost);
|
|
90
|
+
|
|
91
|
+
filter.catch(secondException, mockHost);
|
|
92
|
+
filter.catch(thirdException, mockHost);
|
|
93
|
+
|
|
94
|
+
currentTime += 60000;
|
|
95
|
+
|
|
96
|
+
filter.catch(
|
|
97
|
+
new HttpException(
|
|
98
|
+
"Fourth error to trigger log",
|
|
99
|
+
HttpStatus.BAD_REQUEST
|
|
100
|
+
),
|
|
101
|
+
mockHost
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const errorCalls = (loggerMock.error as jest.Mock).mock.calls;
|
|
105
|
+
const rateLimitMessage = errorCalls.find(
|
|
106
|
+
(call) => typeof call[0] === "string" && call[0].includes("Suppressed")
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(rateLimitMessage).toBeDefined();
|
|
110
|
+
expect(rateLimitMessage[0]).toContain(
|
|
111
|
+
"Suppressed 2 error(s) in the last minute due to rate limiting"
|
|
112
|
+
);
|
|
113
|
+
expect(rateLimitMessage[0]).toContain("Sample: Third error message");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should not show rate limit message when only one error occurs", () => {
|
|
117
|
+
const mockHost = {
|
|
118
|
+
switchToHttp: () => ({
|
|
119
|
+
getRequest: () => ({
|
|
120
|
+
headers: {},
|
|
121
|
+
body: {},
|
|
122
|
+
url: "/test",
|
|
123
|
+
method: "GET",
|
|
124
|
+
}),
|
|
125
|
+
getResponse: () => ({
|
|
126
|
+
status: () => ({
|
|
127
|
+
json: () => {},
|
|
128
|
+
}),
|
|
129
|
+
}),
|
|
130
|
+
}),
|
|
131
|
+
} as any;
|
|
132
|
+
|
|
133
|
+
const exception = new HttpException("Test error", HttpStatus.BAD_REQUEST);
|
|
134
|
+
|
|
135
|
+
filter.catch(exception, mockHost);
|
|
136
|
+
|
|
137
|
+
const errorCalls = (loggerMock.error as jest.Mock).mock.calls;
|
|
138
|
+
const rateLimitMessage = errorCalls.find(
|
|
139
|
+
(call) => typeof call[0] === "string" && call[0].includes("Suppressed")
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(rateLimitMessage).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("getDeveloperText", () => {
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
setupFilter();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should extract developer text from exception", () => {
|
|
152
|
+
const exception = new HttpException(
|
|
153
|
+
"Test error message",
|
|
154
|
+
HttpStatus.BAD_REQUEST
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const result = filter.getDeveloperText(exception, fallbackLocale);
|
|
158
|
+
|
|
159
|
+
expect(result).toBe("Test error message");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should handle exceptions with response.message", () => {
|
|
163
|
+
const exception = {
|
|
164
|
+
getStatus: () => HttpStatus.BAD_REQUEST,
|
|
165
|
+
response: { message: "Response error message" },
|
|
166
|
+
message: "Fallback message",
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = filter.getDeveloperText(exception, fallbackLocale);
|
|
170
|
+
|
|
171
|
+
expect(result).toBe("Response error message");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should handle exceptions with array response.message", () => {
|
|
175
|
+
const exception = {
|
|
176
|
+
getStatus: () => HttpStatus.BAD_REQUEST,
|
|
177
|
+
response: { message: ["Error 1", "Error 2", "Error 3"] },
|
|
178
|
+
message: "Fallback message",
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const result = filter.getDeveloperText(exception, fallbackLocale);
|
|
182
|
+
|
|
183
|
+
expect(result).toBe("Error 1 + Error 2 + Error 3");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -25,6 +25,7 @@ import { LocaleUtil } from "../util";
|
|
|
25
25
|
export class AppHttpExceptionFilter extends BaseExceptionFilter {
|
|
26
26
|
private _lastErrorLogTime: number = 0;
|
|
27
27
|
private _suppressedErrorCount: number = 0;
|
|
28
|
+
private _lastSuppressedError: any = null;
|
|
28
29
|
|
|
29
30
|
constructor(
|
|
30
31
|
private readonly fallbackLocale: Locale,
|
|
@@ -41,7 +42,7 @@ export class AppHttpExceptionFilter extends BaseExceptionFilter {
|
|
|
41
42
|
);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
private _shouldLogError(): boolean {
|
|
45
|
+
private _shouldLogError(exception?: any): boolean {
|
|
45
46
|
if (this._isDebugLevel()) {
|
|
46
47
|
return true;
|
|
47
48
|
}
|
|
@@ -55,6 +56,9 @@ export class AppHttpExceptionFilter extends BaseExceptionFilter {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
this._suppressedErrorCount++;
|
|
59
|
+
if (exception) {
|
|
60
|
+
this._lastSuppressedError = exception;
|
|
61
|
+
}
|
|
58
62
|
return false;
|
|
59
63
|
}
|
|
60
64
|
|
|
@@ -117,7 +121,7 @@ export class AppHttpExceptionFilter extends BaseExceptionFilter {
|
|
|
117
121
|
logger.error(`No stack available for error of type ${typeof exception}`);
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
const shouldLogError = this._shouldLogError();
|
|
124
|
+
const shouldLogError = this._shouldLogError(exception);
|
|
121
125
|
|
|
122
126
|
if (shouldLogError) {
|
|
123
127
|
if (developerText) {
|
|
@@ -160,10 +164,14 @@ export class AppHttpExceptionFilter extends BaseExceptionFilter {
|
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
if (this._suppressedErrorCount > 0) {
|
|
167
|
+
const sampleError = this._lastSuppressedError
|
|
168
|
+
? this.getDeveloperText(this._lastSuppressedError, fallbackLocale)
|
|
169
|
+
: "No sample available";
|
|
163
170
|
logger.error(
|
|
164
|
-
`Suppressed ${this._suppressedErrorCount} error(s) in the last minute due to rate limiting`
|
|
171
|
+
`Suppressed ${this._suppressedErrorCount} error(s) in the last minute due to rate limiting. Sample: ${sampleError}`
|
|
165
172
|
);
|
|
166
173
|
this._suppressedErrorCount = 0;
|
|
174
|
+
this._lastSuppressedError = null;
|
|
167
175
|
}
|
|
168
176
|
}
|
|
169
177
|
|