@yolo-croket-dev/core 1.7.45-slowquery.2 → 1.7.46-beta.0
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/functions/abort/abort-context.d.ts +1 -1
- package/functions/abort/abort-context.js +1 -1
- package/functions/abort/enhanced-promise-patch.d.ts +17 -0
- package/functions/abort/enhanced-promise-patch.js +115 -0
- package/functions/amqp-call-manager.function.js +1 -1
- package/functions/amqp-handler.function.js +1 -1
- package/interceptors/rpc.interceptor.js +41 -44
- package/package.json +2 -2
|
@@ -16,7 +16,7 @@ export declare class AbortContext {
|
|
|
16
16
|
requestId: string;
|
|
17
17
|
} | null;
|
|
18
18
|
static disable(): void;
|
|
19
|
-
static endContext(
|
|
19
|
+
static endContext(): void;
|
|
20
20
|
static updateContext(updatedData: Partial<RequestContext>): void;
|
|
21
21
|
/**
|
|
22
22
|
* 컨텍스트가 유효한지 확인
|
|
@@ -39,7 +39,7 @@ class AbortContext {
|
|
|
39
39
|
// AsyncLocalStorage는 각 async 작업의 컨텍스트를 자동으로 관리하므로
|
|
40
40
|
// 명시적으로 disable()을 호출할 필요가 없습니다.
|
|
41
41
|
// disable()을 호출하면 전체 인스턴스가 비활성화되어 이후 요청에 문제가 생깁니다.
|
|
42
|
-
static endContext(
|
|
42
|
+
static endContext() {
|
|
43
43
|
// 컨텍스트는 async 작업이 완료되면 자동으로 정리됩니다.
|
|
44
44
|
// 명시적인 cleanup은 필요하지 않습니다.
|
|
45
45
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
var Promise: PromiseConstructor;
|
|
3
|
+
}
|
|
4
|
+
export declare class EnhancedPromisePatch {
|
|
5
|
+
private static isPatched;
|
|
6
|
+
private static originalPromise;
|
|
7
|
+
private static originalSetTimeout;
|
|
8
|
+
static patch(): void;
|
|
9
|
+
private static patchPromise;
|
|
10
|
+
private static patchSetTimeout;
|
|
11
|
+
static unpatch(): void;
|
|
12
|
+
/**
|
|
13
|
+
* 현재 패치 상태 확인
|
|
14
|
+
* @returns 패치 여부
|
|
15
|
+
*/
|
|
16
|
+
static getPatchStatus(): boolean;
|
|
17
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnhancedPromisePatch = void 0;
|
|
4
|
+
const abort_1 = require("./abort");
|
|
5
|
+
class EnhancedPromisePatch {
|
|
6
|
+
static patch() {
|
|
7
|
+
if (this.isPatched) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
this.patchPromise();
|
|
11
|
+
this.patchSetTimeout();
|
|
12
|
+
this.isPatched = true;
|
|
13
|
+
}
|
|
14
|
+
static patchPromise() {
|
|
15
|
+
this.originalPromise = global.Promise;
|
|
16
|
+
// Promise 생성자 패치
|
|
17
|
+
global.Promise = new Proxy(global.Promise, {
|
|
18
|
+
construct(target, args) {
|
|
19
|
+
const abortSignal = (0, abort_1.getCurrentAbortSignal)();
|
|
20
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
21
|
+
return Promise.reject(new Error('REQUEST_ABORTED: Promise가 중단되었습니다'));
|
|
22
|
+
}
|
|
23
|
+
// target은 Promise 생성자이므로, new 연산자와 함께 사용해야 하며, args는 [executor] 형태의 튜플임을 명시합니다.
|
|
24
|
+
const promise = Reflect.construct(target, args);
|
|
25
|
+
if (!abortSignal) {
|
|
26
|
+
return promise;
|
|
27
|
+
}
|
|
28
|
+
// Promise 체인에 중단 확인 추가
|
|
29
|
+
return promise.then((result) => {
|
|
30
|
+
if (abortSignal.aborted) {
|
|
31
|
+
throw new Error('REQUEST_ABORTED: Promise 실행 중 중단되었습니다');
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}, (error) => {
|
|
35
|
+
if (abortSignal.aborted) {
|
|
36
|
+
throw new Error('REQUEST_ABORTED: Promise 실행 중 중단되었습니다');
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
// Promise.resolve 패치
|
|
43
|
+
const originalResolve = global.Promise.resolve;
|
|
44
|
+
global.Promise.resolve = (value) => {
|
|
45
|
+
const abortSignal = (0, abort_1.getCurrentAbortSignal)();
|
|
46
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
47
|
+
return Promise.reject(new Error('REQUEST_ABORTED: Promise.resolve가 중단되었습니다'));
|
|
48
|
+
}
|
|
49
|
+
const promise = originalResolve.call(global.Promise, value);
|
|
50
|
+
if (!abortSignal) {
|
|
51
|
+
return promise;
|
|
52
|
+
}
|
|
53
|
+
return promise.then((result) => {
|
|
54
|
+
if (abortSignal.aborted) {
|
|
55
|
+
throw new Error('REQUEST_ABORTED: Promise.resolve 실행 중 중단되었습니다');
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}, (error) => {
|
|
59
|
+
if (abortSignal.aborted) {
|
|
60
|
+
throw new Error('REQUEST_ABORTED: Promise.resolve 실행 중 중단되었습니다');
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
// Promise.reject 패치
|
|
66
|
+
const originalReject = global.Promise.reject;
|
|
67
|
+
global.Promise.reject = (reason) => {
|
|
68
|
+
const abortSignal = (0, abort_1.getCurrentAbortSignal)();
|
|
69
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
70
|
+
return Promise.reject(new Error('REQUEST_ABORTED: Promise.reject가 중단되었습니다'));
|
|
71
|
+
}
|
|
72
|
+
return originalReject.call(global.Promise, reason);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
static patchSetTimeout() {
|
|
76
|
+
this.originalSetTimeout = global.setTimeout;
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
78
|
+
global.setTimeout = (callback, delay, ...args) => {
|
|
79
|
+
const abortSignal = (0, abort_1.getCurrentAbortSignal)();
|
|
80
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
81
|
+
throw new Error('REQUEST_ABORTED: setTimeout이 중단되었습니다');
|
|
82
|
+
}
|
|
83
|
+
const timerId = this.originalSetTimeout(() => {
|
|
84
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
85
|
+
throw new Error('REQUEST_ABORTED: setTimeout 콜백이 중단되었습니다');
|
|
86
|
+
}
|
|
87
|
+
callback(...args);
|
|
88
|
+
}, delay);
|
|
89
|
+
if (abortSignal) {
|
|
90
|
+
const abortHandler = () => {
|
|
91
|
+
clearTimeout(timerId);
|
|
92
|
+
};
|
|
93
|
+
abortSignal.addEventListener('abort', abortHandler, { once: true });
|
|
94
|
+
}
|
|
95
|
+
return timerId;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
static unpatch() {
|
|
99
|
+
if (!this.isPatched) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
global.Promise = this.originalPromise;
|
|
103
|
+
global.setTimeout = this.originalSetTimeout;
|
|
104
|
+
this.isPatched = false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 현재 패치 상태 확인
|
|
108
|
+
* @returns 패치 여부
|
|
109
|
+
*/
|
|
110
|
+
static getPatchStatus() {
|
|
111
|
+
return this.isPatched;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.EnhancedPromisePatch = EnhancedPromisePatch;
|
|
115
|
+
EnhancedPromisePatch.isPatched = false;
|
|
@@ -58,7 +58,7 @@ class AmqpManagement {
|
|
|
58
58
|
return result.data;
|
|
59
59
|
}
|
|
60
60
|
catch (err) {
|
|
61
|
-
msaLogger.
|
|
61
|
+
msaLogger.warn(`ThrowWrapppingCall[${funcName}] error: traceId=${param.traceId}`, { traceId: param.traceId }, { param });
|
|
62
62
|
if (options && options.isErrorThrowing) {
|
|
63
63
|
throw err;
|
|
64
64
|
}
|
|
@@ -62,7 +62,7 @@ async function AmqpHandler(client, messagePattern, payload) {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
|
-
msaLogger.
|
|
65
|
+
msaLogger.warn('AMQP_HANDLER_ERROR', { traceId: ((_d = payload === null || payload === void 0 ? void 0 : payload.param) === null || _d === void 0 ? void 0 : _d.traceId) || '-' }, { err });
|
|
66
66
|
throw err;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -170,7 +170,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
170
170
|
isMessageAck: false,
|
|
171
171
|
};
|
|
172
172
|
context_promise_patch_1.ContextPromisePatch.patch();
|
|
173
|
-
|
|
173
|
+
let abortController = new AbortController();
|
|
174
174
|
/** consume 메시지 전부 cancel */
|
|
175
175
|
if (this.gracefulShutdownService.isApplicationExit) {
|
|
176
176
|
msaLogger.debug('RPCInterceptor isApplicationExit called', { traceId: 'IS_APPLICATION_EXIT' });
|
|
@@ -278,7 +278,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
278
278
|
if (!isAck) {
|
|
279
279
|
const currentContext = functions_1.AbortContext.getCurrentContext();
|
|
280
280
|
// 에러 전달
|
|
281
|
-
msaLogger.
|
|
281
|
+
msaLogger.warn('에러 응답 전송 처리', { traceId: execInterceptor.traceId }, {
|
|
282
282
|
error: {
|
|
283
283
|
message: err.message,
|
|
284
284
|
stack: err.stack,
|
|
@@ -325,49 +325,46 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
325
325
|
}
|
|
326
326
|
this.gracefulShutdownService.pushActiveInterceptors(execInterceptor);
|
|
327
327
|
// eslint-disable-next-line consistent-return
|
|
328
|
-
return functions_1.AbortContext.run(abortController.signal, execInterceptor.traceId, () =>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
execInterceptor.isMessageAck = true;
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (!noAck) {
|
|
355
|
-
execInterceptor.isMessageAck = true;
|
|
356
|
-
channel.ack(message);
|
|
357
|
-
}
|
|
358
|
-
},
|
|
359
|
-
error: localErrorProcessor,
|
|
360
|
-
}), (0, operators_1.finalize)(() => {
|
|
361
|
-
this.gracefulShutdownService.popActiveInterceptors(execInterceptor);
|
|
362
|
-
const isAborted = abortController === null || abortController === void 0 ? void 0 : abortController.signal.aborted;
|
|
363
|
-
if (context) {
|
|
364
|
-
if (isAborted) {
|
|
365
|
-
msaLogger.debug('RPCInterceptor context end', { traceId: execInterceptor.traceId }, { context: context.requestId });
|
|
366
|
-
}
|
|
328
|
+
return functions_1.AbortContext.run(abortController.signal, execInterceptor.traceId, () => next
|
|
329
|
+
.handle()
|
|
330
|
+
.pipe((0, operators_1.timeout)({
|
|
331
|
+
each: this.DEFAULT_TIMEOUT,
|
|
332
|
+
with: () => {
|
|
333
|
+
msaLogger.error('RPCInterceptor timeout 발생', { traceId: execInterceptor.traceId }, {
|
|
334
|
+
message: this.DEFAULT_TIMEOUT_MESSAGE,
|
|
335
|
+
messagePattern: method,
|
|
336
|
+
durationMs: Date.now() - now,
|
|
337
|
+
reDeliveryCount,
|
|
338
|
+
staticRetryCount,
|
|
339
|
+
});
|
|
340
|
+
abortController.abort();
|
|
341
|
+
return (0, rxjs_1.throwError)(() => new exceptions_1.InternalServerError('TIMEOUT_ERROR', this.DEFAULT_TIMEOUT_MESSAGE));
|
|
342
|
+
},
|
|
343
|
+
}), (0, operators_1.tap)({
|
|
344
|
+
next: async (val) => {
|
|
345
|
+
val.isError = false;
|
|
346
|
+
/** 만약 응답데이터중 isRequeue가 true라면, 다시 재처리 하도록 함 */
|
|
347
|
+
if (val.data && val.data.isRequeue) {
|
|
348
|
+
channel.nack(message, false, true);
|
|
349
|
+
execInterceptor.isMessageAck = true;
|
|
350
|
+
return;
|
|
367
351
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
352
|
+
if (!noAck) {
|
|
353
|
+
execInterceptor.isMessageAck = true;
|
|
354
|
+
channel.ack(message);
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
error: localErrorProcessor,
|
|
358
|
+
}), (0, operators_1.finalize)(() => {
|
|
359
|
+
this.gracefulShutdownService.popActiveInterceptors(execInterceptor);
|
|
360
|
+
const isAborted = abortController === null || abortController === void 0 ? void 0 : abortController.signal.aborted;
|
|
361
|
+
if (isAborted) {
|
|
362
|
+
msaLogger.debug('RPCInterceptor context end', { traceId: execInterceptor.traceId }, { isAborted });
|
|
363
|
+
}
|
|
364
|
+
functions_1.AbortContext.endContext();
|
|
365
|
+
abortController = null;
|
|
366
|
+
return rxjs_1.EMPTY;
|
|
367
|
+
})));
|
|
371
368
|
}
|
|
372
369
|
};
|
|
373
370
|
exports.RPCInterceptor = RPCInterceptor;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolo-croket-dev/core",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.46-beta.0",
|
|
4
4
|
"author": "Yolo Co., Ltd.",
|
|
5
5
|
"description": "Yolo commonization module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@nestjs/common": "^8.2.3",
|
|
18
18
|
"@nestjs/swagger": "5",
|
|
19
19
|
"@yolo-croket-dev/logger": "^1.0.15",
|
|
20
|
-
"@yolo-croket-dev/msa-logger": "^3.
|
|
20
|
+
"@yolo-croket-dev/msa-logger": "^3.2.2",
|
|
21
21
|
"@yolo-croket-dev/redis": "^1.0.25",
|
|
22
22
|
"axios": "^1.1.3",
|
|
23
23
|
"dd-trace": "^5.46.0",
|