@yolo-croket-dev/core 1.10.0-jun.2-beta.1 → 1.10.1

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.
@@ -39,11 +39,22 @@ const common_1 = require("@nestjs/common");
39
39
  const msa_logger_1 = __importStar(require("@yolo-croket-dev/msa-logger"));
40
40
  const operators_1 = require("rxjs/operators");
41
41
  const dd_trace_1 = __importDefault(require("dd-trace"));
42
+ const rmq_diagnostics_function_1 = require("../functions/rmq-diagnostics.function");
42
43
  const redact_auth_data_function_1 = require("../functions/redact-auth-data.function");
43
44
  const grpc_param_metadata_function_1 = require("../functions/grpc-param-metadata.function");
44
45
  const { SERVER_NAME } = process.env;
45
46
  const msaLogger = new msa_logger_1.default(msa_logger_1.ServerTypeEnum.SERVICE);
46
47
  const MAX_CUT_LENGTH = 500;
48
+ function readErrorProperty(error, key) {
49
+ if (error === null || error === undefined)
50
+ return undefined;
51
+ try {
52
+ return Reflect.get(Object(error), key);
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
47
58
  dd_trace_1.default.init();
48
59
  let LoggingServiceInterceptor = class LoggingServiceInterceptor {
49
60
  keyCutter(data) {
@@ -108,6 +119,7 @@ let LoggingServiceInterceptor = class LoggingServiceInterceptor {
108
119
  pattern = content.pattern;
109
120
  payload = context.switchToHttp().getRequest().payload;
110
121
  }
122
+ const connection = isGrpc ? undefined : (0, rmq_diagnostics_function_1.getRmqDiagnosticSnapshot)(rmqContext.getChannelRef());
111
123
  const pastParam = JSON.parse(JSON.stringify(param));
112
124
  const pastFrom = pastParam.from || '-';
113
125
  const pastFroms = pastParam.froms || [pastFrom];
@@ -171,22 +183,47 @@ let LoggingServiceInterceptor = class LoggingServiceInterceptor {
171
183
  loggingSpan.finish();
172
184
  }
173
185
  })).pipe((0, operators_1.catchError)((error) => {
186
+ const diagnostic = connection ? (0, msa_logger_1.normalizeDiagnosticError)(error, 'service_error') : undefined;
187
+ const frame = diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.details.errors[0];
188
+ const subMessageValue = readErrorProperty(error, 'subMessage');
189
+ let subMessage = typeof subMessageValue === 'string' ? subMessageValue : undefined;
190
+ if (subMessage && diagnostic)
191
+ subMessage = (0, msa_logger_1.sanitizeDiagnosticText)(subMessage);
192
+ const messageValue = readErrorProperty(error, 'message');
193
+ const errorMessage = (frame === null || frame === void 0 ? void 0 : frame.message) || (typeof messageValue === 'string' ? messageValue : undefined)
194
+ || (typeof error === 'string' ? error : undefined);
195
+ const nameValue = readErrorProperty(error, 'name');
196
+ const errorName = (frame === null || frame === void 0 ? void 0 : frame.name) || (typeof nameValue === 'string' ? nameValue : 'UNKNOWN_ERROR');
197
+ const stackValue = readErrorProperty(error, 'stack');
198
+ const errorStack = (frame === null || frame === void 0 ? void 0 : frame.stack) || (typeof stackValue === 'string' ? stackValue : undefined);
199
+ const statusValue = readErrorProperty(error, 'status');
200
+ const status = typeof statusValue === 'number' ? statusValue : 500;
201
+ const errOption = readErrorProperty(error, 'errOption');
174
202
  if (loggingSpan) {
175
203
  loggingSpan.setTag('error', true);
176
- loggingSpan.setTag('error.message', error.message);
177
- loggingSpan.setTag('error.status', error.status);
178
- loggingSpan.setTag('error.subMessage', error.subMessage);
179
- loggingSpan.setTag('error.stack', error.stack);
180
- loggingSpan.setTag('error.type', error.constructor.name);
204
+ loggingSpan.setTag('error.message', errorMessage);
205
+ loggingSpan.setTag('error.status', status);
206
+ loggingSpan.setTag('error.subMessage', subMessage);
207
+ loggingSpan.setTag('error.stack', errorStack);
208
+ loggingSpan.setTag('error.type', errorName);
181
209
  }
182
210
  msaLogger.printExceptionFliterService({
211
+ diagnosticInfo: diagnostic ? {
212
+ ...diagnostic.diagnosticInfo,
213
+ ...(0, rmq_diagnostics_function_1.getRmqDiagnosticSnapshot)(rmqContext.getChannelRef()),
214
+ event: 'handler_failed',
215
+ messagePattern: (0, msa_logger_1.sanitizeDiagnosticText)(pattern).slice(0, 240),
216
+ elapsedMs: Date.now() - now,
217
+ startGeneration: connection === null || connection === void 0 ? void 0 : connection.generation,
218
+ startConnectionState: connection === null || connection === void 0 ? void 0 : connection.connectionState,
219
+ } : undefined,
183
220
  traceId,
184
221
  errInfo: {
185
- errType: error.subMessage ? error.message : error.name,
186
- message: error.subMessage ? error.subMessage : error.message,
187
- status: error.status,
188
- stack: error.stack,
189
- errOption: error.errOption,
222
+ errType: subMessage ? errorMessage || errorName : errorName,
223
+ message: subMessage || errorMessage,
224
+ status,
225
+ stack: errorStack,
226
+ errOption,
190
227
  },
191
228
  messageBrokerInfo: {
192
229
  messagePatten: pattern,
@@ -196,12 +233,12 @@ let LoggingServiceInterceptor = class LoggingServiceInterceptor {
196
233
  from: pastFrom,
197
234
  froms: pastFroms,
198
235
  },
199
- });
236
+ }, diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.details);
200
237
  const elapsed = Date.now() - now;
201
238
  msaLogger.printOutService({
202
239
  traceId,
203
240
  responseInfo: {
204
- status: error.status,
241
+ status,
205
242
  ms: elapsed,
206
243
  },
207
244
  messageBrokerInfo: {
@@ -220,7 +257,7 @@ let LoggingServiceInterceptor = class LoggingServiceInterceptor {
220
257
  }));
221
258
  }
222
259
  };
223
- LoggingServiceInterceptor = __decorate([
260
+ exports.LoggingServiceInterceptor = LoggingServiceInterceptor;
261
+ exports.LoggingServiceInterceptor = LoggingServiceInterceptor = __decorate([
224
262
  (0, common_1.Injectable)()
225
263
  ], LoggingServiceInterceptor);
226
- exports.LoggingServiceInterceptor = LoggingServiceInterceptor;
@@ -48,6 +48,7 @@ const functions_1 = require("../functions");
48
48
  const exceptions_1 = require("../exceptions");
49
49
  const context_promise_patch_1 = require("../functions/abort/context-promise-patch");
50
50
  const graceful_shutdown_service_1 = require("../modules/graceful-shutdown/graceful-shutdown.service");
51
+ const rmq_diagnostics_function_1 = require("../functions/rmq-diagnostics.function");
51
52
  const msaLogger = new msa_logger_1.default(msa_logger_1.ServerTypeEnum.SERVICE);
52
53
  // const MAX_CUT_LENGTH = 500;
53
54
  const MAX_LENGTH = 300;
@@ -154,6 +155,7 @@ let RPCInterceptor = class RPCInterceptor {
154
155
  const arg = args[0];
155
156
  const rmq = args[1];
156
157
  const channel = rmq.getChannelRef();
158
+ const startConnection = (0, rmq_diagnostics_function_1.getRmqDiagnosticSnapshot)(channel);
157
159
  const message = rmq.getMessage();
158
160
  const now = Date.now();
159
161
  const content = JSON.parse(message.content.toString());
@@ -206,7 +208,7 @@ let RPCInterceptor = class RPCInterceptor {
206
208
  }
207
209
  const noAck = message.fields.routingKey.includes('ASYNC');
208
210
  let localErrorProcessor = async (err) => {
209
- var _a;
211
+ var _a, _b;
210
212
  const errMsg = err.response ? err.response : err.message;
211
213
  const status = err.status ? err.status : 500;
212
214
  const errType = err.errType;
@@ -278,10 +280,28 @@ let RPCInterceptor = class RPCInterceptor {
278
280
  }
279
281
  }
280
282
  const sendMessage = Buffer.from(JSON.stringify(abstractAmqpHandlerData));
281
- if (!isAck) {
283
+ const diagnostic = startConnection
284
+ ? (0, msa_logger_1.normalizeDiagnosticError)(err, isTimeoutError ? 'response_timeout' : 'service_error') : undefined;
285
+ const diagnosticInfo = diagnostic ? {
286
+ ...diagnostic.diagnosticInfo,
287
+ ...(0, rmq_diagnostics_function_1.getRmqDiagnosticSnapshot)(channel),
288
+ event: 'handler_failed',
289
+ messagePattern: (0, msa_logger_1.sanitizeDiagnosticText)(String(method)).slice(0, 240),
290
+ elapsedMs: duration,
291
+ startGeneration: startConnection === null || startConnection === void 0 ? void 0 : startConnection.generation,
292
+ startConnectionState: startConnection === null || startConnection === void 0 ? void 0 : startConnection.connectionState,
293
+ } : undefined;
294
+ if (!isAck || diagnostic) {
282
295
  const currentContext = functions_1.AbortContext.getCurrentContext();
283
- // 에러 전달
284
- msaLogger.warn('에러 응답 전송 처리', { traceId: execInterceptor.traceId }, {
296
+ msaLogger.warn(isAck ? 'RPCInterceptor 오류 처리' : '에러 응답 전송 처리', {
297
+ traceId: execInterceptor.traceId,
298
+ diagnosticInfo,
299
+ }, diagnostic ? {
300
+ error: diagnostic.details,
301
+ reDeliveryCount,
302
+ staticRetryCount,
303
+ currentContext: (_a = currentContext === null || currentContext === void 0 ? void 0 : currentContext.requestId) !== null && _a !== void 0 ? _a : null,
304
+ } : {
285
305
  error: {
286
306
  message: err.message,
287
307
  stack: err.stack,
@@ -293,11 +313,13 @@ let RPCInterceptor = class RPCInterceptor {
293
313
  messagePattern: method,
294
314
  currentContext: currentContext && currentContext.requestId ? currentContext.requestId : null,
295
315
  });
316
+ }
317
+ if (!isAck) {
296
318
  channel.sendToQueue(message.properties.replyTo, sendMessage, { correlationId: message.properties.correlationId });
297
319
  }
298
320
  const messageContent = message.content.toString();
299
321
  /** 파일 객체 처리 */
300
- (0, functions_1.reWriteFileValue)((_a = messageContent === null || messageContent === void 0 ? void 0 : messageContent.data) === null || _a === void 0 ? void 0 : _a.file);
322
+ (0, functions_1.reWriteFileValue)((_b = messageContent === null || messageContent === void 0 ? void 0 : messageContent.data) === null || _b === void 0 ? void 0 : _b.file);
301
323
  if (!noAck && !isAck) {
302
324
  channel.ack(message);
303
325
  execInterceptor.isMessageAck = true;
@@ -370,9 +392,9 @@ let RPCInterceptor = class RPCInterceptor {
370
392
  })));
371
393
  }
372
394
  };
373
- RPCInterceptor = __decorate([
395
+ exports.RPCInterceptor = RPCInterceptor;
396
+ exports.RPCInterceptor = RPCInterceptor = __decorate([
374
397
  (0, common_1.Injectable)(),
375
398
  __metadata("design:paramtypes", [core_1.Reflector,
376
399
  graceful_shutdown_service_1.GracefulShutdownService])
377
400
  ], RPCInterceptor);
378
- exports.RPCInterceptor = RPCInterceptor;
@@ -18,9 +18,10 @@ var ErrActionTypeEnum;
18
18
  ErrActionTypeEnum["LEGACY_SHOW_TOAST"] = "LEGACY_SHOW_TOAST";
19
19
  ErrActionTypeEnum["FORCE_UPDATE"] = "FORCE_UPDATE";
20
20
  ErrActionTypeEnum["UPDATE_ACCESS_TOKEN"] = "UPDATE_ACCESS_TOKEN";
21
- })(ErrActionTypeEnum = exports.ErrActionTypeEnum || (exports.ErrActionTypeEnum = {}));
21
+ })(ErrActionTypeEnum || (exports.ErrActionTypeEnum = ErrActionTypeEnum = {}));
22
22
  class ErrAction {
23
23
  }
24
+ exports.ErrAction = ErrAction;
24
25
  __decorate([
25
26
  (0, swagger_1.ApiProperty)({
26
27
  description: '액션 타입',
@@ -57,9 +58,9 @@ __decorate([
57
58
  }),
58
59
  __metadata("design:type", String)
59
60
  ], ErrAction.prototype, "webViewUrl", void 0);
60
- exports.ErrAction = ErrAction;
61
61
  class ErrInfo {
62
62
  }
63
+ exports.ErrInfo = ErrInfo;
63
64
  __decorate([
64
65
  (0, swagger_1.ApiProperty)({
65
66
  description: '에러 종류 식별자',
@@ -102,4 +103,3 @@ __decorate([
102
103
  }),
103
104
  __metadata("design:type", Number)
104
105
  ], ErrInfo.prototype, "retryAfterSeconds", void 0);
105
- exports.ErrInfo = ErrInfo;
@@ -1,5 +1,5 @@
1
- import { Method } from 'axios';
2
1
  import { ErrInfo } from './exception';
2
+ type RequestLogMethod = 'GET' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH' | 'PURGE' | 'LINK' | 'UNLINK';
3
3
  export interface ExternalTokenValue {
4
4
  provider: string;
5
5
  client_id: string;
@@ -16,7 +16,7 @@ export interface DeviceRequest {
16
16
  export interface RequestLog {
17
17
  query?: unknown;
18
18
  body?: unknown;
19
- method: Method;
19
+ method: RequestLogMethod | Lowercase<RequestLogMethod>;
20
20
  endpoint: string;
21
21
  }
22
22
  /** 최신 에러 정보 */
@@ -98,3 +98,4 @@ export interface ExternalResponseNormalize<T> {
98
98
  export interface ResponseAction {
99
99
  result: boolean;
100
100
  }
101
+ export {};
@@ -21,4 +21,4 @@ var LogLevelEnum;
21
21
  LogLevelEnum["DEBUG"] = "debug";
22
22
  LogLevelEnum["WARN"] = "warn";
23
23
  LogLevelEnum["ERROR"] = "error";
24
- })(LogLevelEnum = exports.LogLevelEnum || (exports.LogLevelEnum = {}));
24
+ })(LogLevelEnum || (exports.LogLevelEnum = LogLevelEnum = {}));
@@ -1,6 +1,10 @@
1
1
  import { HttpException } from '@nestjs/common';
2
2
  import { LogLevelEnum } from './log-level.enum';
3
3
  import type { ErrAction } from './exception';
4
+ export declare enum MsaExceptionMessageAckBehaviorEnum {
5
+ ACK = "ACK",
6
+ NACK = "NACK"
7
+ }
4
8
  export interface MsaExceptionOption {
5
9
  /**
6
10
  * 로그 레벨
@@ -8,6 +12,12 @@ export interface MsaExceptionOption {
8
12
  * - 기본값은 ERROR 입니다.
9
13
  * */
10
14
  logErrorLevel?: LogLevelEnum;
15
+ /**
16
+ * 메시지 consumer 실패 시 ACK/NACK override 옵션
17
+ * - 현재는 amqp-rabbit-mq subscribe consumer에서만 해석합니다.
18
+ * - option이 없으면 consumer handler의 기본 errorBehavior를 따릅니다.
19
+ */
20
+ messageAckBehavior?: MsaExceptionMessageAckBehaviorEnum;
11
21
  }
12
22
  /**
13
23
  * @author jun
@@ -27,7 +37,7 @@ export interface MsaException {
27
37
  errOption?: MsaExceptionOption;
28
38
  }
29
39
  /** HttpException과 MsaException을 결합한 타입 */
30
- export declare type MsaHttpException = MsaException & HttpException;
40
+ export type MsaHttpException = MsaException & HttpException;
31
41
  /**
32
42
  * @description MsaException 타입 가드
33
43
  * @param error 검사할 에러 객체
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isMsaException = void 0;
3
+ exports.isMsaException = exports.MsaExceptionMessageAckBehaviorEnum = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
+ var MsaExceptionMessageAckBehaviorEnum;
6
+ (function (MsaExceptionMessageAckBehaviorEnum) {
7
+ MsaExceptionMessageAckBehaviorEnum["ACK"] = "ACK";
8
+ MsaExceptionMessageAckBehaviorEnum["NACK"] = "NACK";
9
+ })(MsaExceptionMessageAckBehaviorEnum || (exports.MsaExceptionMessageAckBehaviorEnum = MsaExceptionMessageAckBehaviorEnum = {}));
5
10
  /**
6
11
  * @description MsaException 타입 가드
7
12
  * @param error 검사할 에러 객체
@@ -48,7 +48,8 @@ let GracefulShutdownModule = class GracefulShutdownModule {
48
48
  msaLogger.info('GracefulShutdownService.onModuleDestroy: all interceptors finished, shutting down', { traceId: 'ON_MODULE_DESTROY' });
49
49
  }
50
50
  };
51
- GracefulShutdownModule = __decorate([
51
+ exports.GracefulShutdownModule = GracefulShutdownModule;
52
+ exports.GracefulShutdownModule = GracefulShutdownModule = __decorate([
52
53
  (0, common_1.Global)(),
53
54
  (0, common_1.Module)({
54
55
  providers: [graceful_shutdown_service_1.GracefulShutdownService],
@@ -56,4 +57,3 @@ GracefulShutdownModule = __decorate([
56
57
  }),
57
58
  __metadata("design:paramtypes", [graceful_shutdown_service_1.GracefulShutdownService])
58
59
  ], GracefulShutdownModule);
59
- exports.GracefulShutdownModule = GracefulShutdownModule;
@@ -1,18 +1,26 @@
1
- import { RmqOptions, ServerRMQ } from '@nestjs/microservices';
2
- import { Channel } from 'amqp-connection-manager';
3
- import { ConsumeMessage } from 'amqplib';
4
- export declare type GracefulRmqOptions = RmqOptions['options'] & {
1
+ import { ServerRMQ } from '@nestjs/microservices';
2
+ import type { RmqOptions } from '@nestjs/microservices';
3
+ import type { Channel } from 'amqp-connection-manager';
4
+ import type { ConsumeMessage } from 'amqplib';
5
+ export type GracefulRmqOptions = RmqOptions['options'] & {
5
6
  waitingEndingHandlersTimeoutMs?: number;
6
7
  waitingEndingHandlersIntervalMs?: number;
7
8
  };
9
+ /**
10
+ * @author jun
11
+ * @description RabbitMQ 연결 식별 정보와 graceful shutdown을 제공하는 RMQ 서버
12
+ * @process 연결 옵션에 추적 가능한 connection_name을 추가한 뒤 메시지 종료를 보장한다.
13
+ */
8
14
  export declare class GracefulServerRMQ extends ServerRMQ {
9
15
  protected readonly options: GracefulRmqOptions;
16
+ private readonly observation?;
10
17
  protected runningMessages: number;
11
18
  protected closing: boolean;
12
19
  protected consumerTag: string | null;
13
20
  protected readonly waitingEndingHandlersTimeoutMs: number;
14
21
  protected readonly waitingEndingHandlersIntervalMs: number;
15
22
  constructor(options: GracefulRmqOptions);
23
+ createClient(): any;
16
24
  setupChannel(channel: Channel, callback: () => void): Promise<void>;
17
25
  protected waitingHandlers(): Promise<void>;
18
26
  handleMessage(message: ConsumeMessage, channel: Channel): Promise<void>;
@@ -1,27 +1,88 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.GracefulServerRMQ = void 0;
4
7
  const microservices_1 = require("@nestjs/microservices");
8
+ const os_1 = __importDefault(require("os"));
5
9
  const node_util_1 = require("node:util");
10
+ const rmq_diagnostics_function_1 = require("../../functions/rmq-diagnostics.function");
6
11
  const sleep = (0, node_util_1.promisify)(setTimeout);
7
12
  const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS = 5000;
8
13
  const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS = 500;
14
+ const RMQ_UNKNOWN_CONNECTION_IDENTITY = 'unknown';
15
+ function createRmqConnectionName(queue) {
16
+ const environment = process.env.ENV || process.env.NODE_ENV || RMQ_UNKNOWN_CONNECTION_IDENTITY;
17
+ const owner = process.env.RMQ_OWNER || process.env.USER || process.env.USERNAME || RMQ_UNKNOWN_CONNECTION_IDENTITY;
18
+ return [
19
+ queue || RMQ_UNKNOWN_CONNECTION_IDENTITY,
20
+ environment,
21
+ owner,
22
+ os_1.default.hostname(),
23
+ `pid-${process.pid}`,
24
+ ].join('/');
25
+ }
26
+ function addRmqConnectionName(options) {
27
+ var _a;
28
+ const connectionOptions = (_a = options.socketOptions) === null || _a === void 0 ? void 0 : _a.connectionOptions;
29
+ const configuredClientProperties = connectionOptions === null || connectionOptions === void 0 ? void 0 : connectionOptions.clientProperties;
30
+ const clientProperties = configuredClientProperties
31
+ && typeof configuredClientProperties === 'object'
32
+ && !Array.isArray(configuredClientProperties)
33
+ ? configuredClientProperties
34
+ : {};
35
+ const configuredConnectionName = clientProperties.connection_name;
36
+ const connectionName = typeof configuredConnectionName === 'string' && configuredConnectionName.trim()
37
+ ? configuredConnectionName
38
+ : createRmqConnectionName(options.queue);
39
+ return {
40
+ ...options,
41
+ socketOptions: {
42
+ ...options.socketOptions,
43
+ connectionOptions: {
44
+ ...connectionOptions,
45
+ clientProperties: {
46
+ ...clientProperties,
47
+ connection_name: connectionName,
48
+ },
49
+ },
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * @author jun
55
+ * @description RabbitMQ 연결 식별 정보와 graceful shutdown을 제공하는 RMQ 서버
56
+ * @process 연결 옵션에 추적 가능한 connection_name을 추가한 뒤 메시지 종료를 보장한다.
57
+ */
9
58
  class GracefulServerRMQ extends microservices_1.ServerRMQ {
10
59
  constructor(options) {
11
- super(options);
12
- this.options = options;
60
+ const observation = (0, rmq_diagnostics_function_1.isRmqDiagnosticsEnabled)() ? (0, rmq_diagnostics_function_1.createRmqObserver)(options, 'consumer') : undefined;
61
+ const optionsWithConnectionName = observation
62
+ ? (0, rmq_diagnostics_function_1.withRmqConnectionName)(options, observation) : addRmqConnectionName(options);
63
+ super(optionsWithConnectionName);
13
64
  this.runningMessages = 0;
14
65
  this.closing = false;
15
66
  this.consumerTag = null;
67
+ this.options = optionsWithConnectionName;
68
+ this.observation = observation;
16
69
  this.waitingEndingHandlersTimeoutMs = this.getOptionsProp(this.options, 'waitingEndingHandlersTimeoutMs')
17
70
  || RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS;
18
71
  this.waitingEndingHandlersIntervalMs = this.getOptionsProp(this.options, 'waitingEndingHandlersIntervalMs')
19
72
  || RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS;
20
73
  }
74
+ createClient() {
75
+ var _a;
76
+ const client = super.createClient();
77
+ (_a = this.observation) === null || _a === void 0 ? void 0 : _a.observe(client);
78
+ return client;
79
+ }
21
80
  async setupChannel(channel, callback) {
22
81
  if (this.closing) {
23
82
  return;
24
83
  }
84
+ if (this.observation)
85
+ (0, rmq_diagnostics_function_1.registerRmqObservation)(channel, this.observation);
25
86
  if (!this.queueOptions.noAssert) {
26
87
  await channel.assertQueue(this.queue, this.queueOptions);
27
88
  }
@@ -42,18 +103,24 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
42
103
  });
43
104
  }
44
105
  async close() {
106
+ var _a;
45
107
  this.closing = true;
46
- if (this.channel) {
47
- await this.channel.removeSetup(undefined, (channel) => channel.cancel(this.consumerTag));
108
+ try {
109
+ if (this.channel) {
110
+ await this.channel.removeSetup(undefined, (channel) => channel.cancel(this.consumerTag));
111
+ }
112
+ this.consumerTag = null;
113
+ await Promise.race([
114
+ this.waitingHandlers(),
115
+ this.waitingEndingHandlersTimeoutMs > 0
116
+ && sleep(this.waitingEndingHandlersTimeoutMs),
117
+ ]);
118
+ this.runningMessages = 0;
119
+ await super.close();
120
+ }
121
+ finally {
122
+ (_a = this.observation) === null || _a === void 0 ? void 0 : _a.close();
48
123
  }
49
- this.consumerTag = null;
50
- await Promise.race([
51
- this.waitingHandlers(),
52
- this.waitingEndingHandlersTimeoutMs > 0
53
- && sleep(this.waitingEndingHandlersTimeoutMs),
54
- ]);
55
- this.runningMessages = 0;
56
- super.close();
57
124
  }
58
125
  }
59
126
  exports.GracefulServerRMQ = GracefulServerRMQ;
@@ -133,7 +133,7 @@ let GracefulShutdownService = class GracefulShutdownService {
133
133
  });
134
134
  }
135
135
  };
136
- GracefulShutdownService = __decorate([
136
+ exports.GracefulShutdownService = GracefulShutdownService;
137
+ exports.GracefulShutdownService = GracefulShutdownService = __decorate([
137
138
  (0, common_1.Injectable)()
138
139
  ], GracefulShutdownService);
139
- exports.GracefulShutdownService = GracefulShutdownService;
@@ -15,10 +15,10 @@ const redisProvider = {
15
15
  };
16
16
  let RedisModule = class RedisModule {
17
17
  };
18
- RedisModule = __decorate([
18
+ exports.RedisModule = RedisModule;
19
+ exports.RedisModule = RedisModule = __decorate([
19
20
  (0, common_1.Module)({
20
21
  providers: [redisProvider],
21
22
  exports: [redisProvider],
22
23
  })
23
24
  ], RedisModule);
24
- exports.RedisModule = RedisModule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolo-croket-dev/core",
3
- "version": "1.10.0-jun.2-beta.1",
3
+ "version": "1.10.1",
4
4
  "author": "Yolo Co., Ltd.",
5
5
  "description": "Yolo commonization module",
6
6
  "main": "index.js",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@yolo-croket-dev/logger": "^1.0.15",
28
- "@yolo-croket-dev/msa-logger": "^3.5.0",
28
+ "@yolo-croket-dev/msa-logger": "3.5.3",
29
29
  "@yolo-croket-dev/redis": "^1.0.25",
30
30
  "axios": "^1.1.3",
31
31
  "randomstring": "^1.2.2",
@@ -36,8 +36,8 @@ let ValidationPipe = class ValidationPipe {
36
36
  return value;
37
37
  }
38
38
  };
39
- ValidationPipe = __decorate([
39
+ exports.ValidationPipe = ValidationPipe;
40
+ exports.ValidationPipe = ValidationPipe = __decorate([
40
41
  (0, common_1.Injectable)(),
41
42
  __metadata("design:paramtypes", [Object])
42
43
  ], ValidationPipe);
43
- exports.ValidationPipe = ValidationPipe;
@@ -0,0 +1,47 @@
1
+ const assert = require('node:assert/strict');
2
+ const test = require('node:test');
3
+ const { Observable } = require('rxjs');
4
+
5
+ const { AmqpHandler, HttpExceptionCustom } = require('../functions/amqp-handler.function.js');
6
+
7
+ test('param.timeout이 지나면 AMQP Observable 구독을 해제한다', async () => {
8
+ let isUnsubscribed = false;
9
+ const client = {
10
+ send() {
11
+ return new Observable(() => () => {
12
+ isUnsubscribed = true;
13
+ });
14
+ },
15
+ };
16
+
17
+ await assert.rejects(
18
+ AmqpHandler(client, 'test.timeout', {
19
+ payload: {},
20
+ param: {
21
+ traceId: 'trace-id',
22
+ timeout: 5,
23
+ },
24
+ }),
25
+ (error) => {
26
+ assert.equal(error instanceof HttpExceptionCustom, true);
27
+ assert.equal(error.errInfo.type, 'TIMEOUT_ERROR');
28
+ return true;
29
+ },
30
+ );
31
+
32
+ assert.equal(isUnsubscribed, true);
33
+ });
34
+
35
+ test('응답이 먼저 완료되면 기존 AMQP 응답을 반환한다', async () => {
36
+ const client = {
37
+ send() {
38
+ return new Observable((subscriber) => {
39
+ subscriber.next({ isError: false, data: { ok: true } });
40
+ subscriber.complete();
41
+ });
42
+ },
43
+ };
44
+
45
+ const result = await AmqpHandler(client, 'test.success', { payload: {}, param: { traceId: 'trace-id', timeout: 50 } });
46
+ assert.deepEqual(result, { data: { ok: true } });
47
+ });