whatap 0.5.22 → 0.5.24

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.
@@ -281,10 +281,9 @@ var ConfigDefault = {
281
281
  "profile_mongodb_param_enabled": bool("profile_mongodb_param_enabled", false),
282
282
 
283
283
  "trace_mtrace_traceparent_key": str("trace_mtrace_traceparent_key", "traceparent"),
284
-
285
284
  "txtext_txname_enabled": bool("txtext_txname_enabled", true),
286
-
287
- "ignore_env_variable_set": str("ignore_env_variable_set", "")
285
+ "ignore_env_variable_set": str("ignore_env_variable_set", ""),
286
+ "profile_httpc_start_step_enabled": bool("profile_httpc_start_step_enabled", false)
288
287
 
289
288
  };
290
289
 
@@ -194,6 +194,15 @@ HttpObserver.prototype.__createTransactionObserver = function(callback, isHttps,
194
194
  ctx.profile.add(step);
195
195
  }
196
196
 
197
+ // res.on('aborted', () => {
198
+ // self.__endTransaction(null, ctx, req, res)
199
+ // })
200
+
201
+ res.on('close', () => {
202
+ // 강제로 종료할 경우
203
+ self.__endTransaction(null, ctx, req, res)
204
+ });
205
+
197
206
  aop.after(res, 'end', function(obj, args) {
198
207
  if(ctx == null) { return; }
199
208
  PluginLoaderManager.do('httpserviceend', ctx, req, res);
@@ -537,6 +546,8 @@ function initCtx(req, res) {
537
546
  };
538
547
 
539
548
  HttpObserver.prototype.__endTransaction = function(error, ctx, req, res) {
549
+ if(ctx == null || TraceContextManager.isExist(ctx._id) === false) { return; }
550
+
540
551
  var param_enabled = false;
541
552
  if(conf.profile_http_parameter_enabled === true) {
542
553
  param_enabled = true;
@@ -591,7 +602,7 @@ HttpObserver.prototype.__endTransaction = function(error, ctx, req, res) {
591
602
  return;
592
603
  }
593
604
 
594
- if(ctx == null || TraceContextManager.isExist(ctx._id) === false) { return; }
605
+ // if(ctx == null || TraceContextManager.isExist(ctx._id) === false) { return; }
595
606
 
596
607
  if(ctx.isStaticContents !== true && conf._trace_ignore_url_set[ctx.service_hash]){
597
608
  ctx.isStaticContents = true;
@@ -841,6 +852,19 @@ HttpObserver.prototype.inject = function( mod, moduleName ) {
841
852
  ctx.footprint('Http Call Start');
842
853
 
843
854
  if (ctx.httpc_port < 0) { ctx.httpc_port = 80 };
855
+
856
+ if(conf.getProperty('profile_httpc_start_step_enabled', false)){
857
+ step.elapsed = ctx.getElapsedTime() - step.start_time;
858
+ step.url = HashUtil.hashFromString(ctx.httpc_url);
859
+ DataTextAgent.HTTPC_URL.add(step.url, ctx.httpc_url);
860
+ step.host = HashUtil.hashFromString(ctx.httpc_host);
861
+ DataTextAgent.HTTPC_HOST.add(step.host, ctx.httpc_host);
862
+ step.port = ctx.httpc_port;
863
+
864
+ ctx.active_httpc_hash = step.url;
865
+ ctx.profile.push(step);
866
+ endHttpc(ctx, step);
867
+ }
844
868
  }catch (e) {
845
869
  Logger.printError('WHATAP-852', 'Http Repeat ', e, true);
846
870
  return original.apply(this, arguments);
@@ -20,6 +20,8 @@ const DataProfileAgent = require('../data/dataprofile-agent');
20
20
  const MeterUsers = require("../counter/meter/meter-users");
21
21
  const MeterService = require('../counter/meter/meter-service').MeterService;
22
22
  const shimmer = require('../core/shimmer');
23
+ const MessageStep = require('../step/message-step');
24
+ const HashUtil = require('../util/hashutil');
23
25
 
24
26
  // 설정을 객체로 통합하여 관리 (참조 성능 향상)
25
27
  var config = {
@@ -147,50 +149,254 @@ SocketIOObserver.prototype.inject = function (mod, moduleName) {
147
149
 
148
150
  var self = this;
149
151
 
150
- shimmer.wrap(mod.prototype, 'on', function (original) {
151
- return function (event, listener) {
152
- if (event === 'connection') {
153
- return original.call(this, event, function (socket) {
154
- // 효율적인 이벤트 핸들러 래핑
155
- const wrappedEmit = function (origEmit) {
156
- // 클로저 최소화 (메모리 사용 감소)
157
- const emitFn = function (emitEvent, ...args) {
158
- // 빠른 조건 검사 (조기 반환)
159
- if (!config.trace_background_socket_enabled) {
152
+ // 공통 emit 래핑 함수
153
+ const wrapEmitFunction = function(target, emitType, getContextInfo) {
154
+ if (!target || typeof target.emit !== 'function' || target.emit.__wrapped__) {
155
+ return false;
156
+ }
157
+
158
+ const originalEmit = target.emit;
159
+ target.emit = function(event, ...args) {
160
+ if (!config.trace_background_socket_enabled) {
161
+ return originalEmit.apply(this, [event, ...args]);
162
+ }
163
+
164
+ const contextInfo = getContextInfo ? getContextInfo.call(this) : {};
165
+ self.__handleSocketEmitEvent(this, event, args, emitType, contextInfo);
166
+ return originalEmit.apply(this, [event, ...args]);
167
+ };
168
+
169
+ // 원본 함수 속성 보존
170
+ Object.defineProperties(target.emit, {
171
+ length: { value: originalEmit.length },
172
+ name: { value: originalEmit.name },
173
+ __wrapped__: { value: true }
174
+ });
175
+
176
+ return true;
177
+ };
178
+
179
+ const wrapSocketIOEvents = function(target) {
180
+ if (!target || typeof target.on !== 'function') {
181
+ return false;
182
+ }
183
+
184
+ shimmer.wrap(target, 'on', function (original) {
185
+ return function (event, listener) {
186
+ if (event === 'connection') {
187
+ return original.call(this, event, function (socket) {
188
+ // 1. 개별 소켓 emit 래핑
189
+ const wrappedEmit = function (origEmit) {
190
+ const emitFn = function (emitEvent, ...args) {
191
+ if (!config.trace_background_socket_enabled) {
192
+ return origEmit.apply(this, [emitEvent, ...args]);
193
+ }
194
+
195
+ self.__handleSocketEmitEvent(socket, emitEvent, args, 'socket', {
196
+ socketId: socket.id,
197
+ remoteAddress: socket.conn ? socket.conn.remoteAddress : null
198
+ });
160
199
  return origEmit.apply(this, [emitEvent, ...args]);
161
- }
200
+ };
162
201
 
163
- self.__handleSocketEmitEvent(socket, emitEvent, args);
164
- return origEmit.apply(this, [emitEvent, ...args]);
202
+ Object.defineProperties(emitFn, {
203
+ length: { value: origEmit.length },
204
+ name: { value: origEmit.name }
205
+ });
206
+
207
+ return emitFn;
165
208
  };
166
209
 
167
- // 원본 함수 속성 유지
168
- Object.defineProperties(emitFn, {
169
- length: { value: origEmit.length },
170
- name: { value: origEmit.name }
210
+ if (typeof socket.emit === 'function' && !socket.emit.__whatap_wrapped__) {
211
+ const originalEmit = socket.emit;
212
+ socket.emit = wrappedEmit(originalEmit);
213
+ socket.emit.__whatap_wrapped__ = true;
214
+ }
215
+
216
+ // 2. socket.broadcast.emit 래핑
217
+ if (socket.broadcast && typeof socket.broadcast.emit === 'function' && !socket.broadcast.emit.__whatap_wrapped__) {
218
+ const originalBroadcastEmit = socket.broadcast.emit;
219
+ socket.broadcast.emit = function(emitEvent, ...args) {
220
+ if (!config.trace_background_socket_enabled) {
221
+ return originalBroadcastEmit.apply(this, [emitEvent, ...args]);
222
+ }
223
+
224
+ self.__handleSocketEmitEvent(socket, emitEvent, args, 'broadcast', {
225
+ socketId: socket.id,
226
+ broadcastType: 'all_except_sender',
227
+ remoteAddress: socket.conn ? socket.conn.remoteAddress : null
228
+ });
229
+ return originalBroadcastEmit.apply(this, [emitEvent, ...args]);
230
+ };
231
+ socket.broadcast.emit.__whatap_wrapped__ = true;
232
+ }
233
+
234
+ // 3. socket.to() 래핑 (room broadcast)
235
+ if (typeof socket.to === "function" && !socket.to.__whatap_wrapped__) {
236
+ const originalTo = socket.to;
237
+ socket.to = function(room) {
238
+ const roomObj = originalTo.call(this, room);
239
+
240
+ if (roomObj && typeof roomObj.emit === 'function' && !roomObj.emit.__whatap_wrapped__) {
241
+ const originalRoomEmit = roomObj.emit;
242
+ roomObj.emit = function(emitEvent, ...args) {
243
+ if (!config.trace_background_socket_enabled) {
244
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
245
+ }
246
+
247
+ self.__handleSocketEmitEvent(socket, emitEvent, args, 'room_broadcast', {
248
+ socketId: socket.id,
249
+ broadcastType: 'room',
250
+ room: room,
251
+ remoteAddress: socket.conn ? socket.conn.remoteAddress : null
252
+ });
253
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
254
+ };
255
+ roomObj.emit.__whatap_wrapped__ = true;
256
+ }
257
+
258
+ return roomObj;
259
+ };
260
+ socket.to.__whatap_wrapped__ = true;
261
+ }
262
+
263
+ // 4. socket.in() 래핑 (room broadcast - 다른 방식)
264
+ if (typeof socket.in === "function" && !socket.in.__whatap_wrapped__) {
265
+ const originalIn = socket.in;
266
+ socket.in = function(room) {
267
+ const roomObj = originalIn.call(this, room);
268
+
269
+ if (roomObj && typeof roomObj.emit === 'function' && !roomObj.emit.__whatap_wrapped__) {
270
+ const originalRoomEmit = roomObj.emit;
271
+ roomObj.emit = function(emitEvent, ...args) {
272
+ if (!config.trace_background_socket_enabled) {
273
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
274
+ }
275
+
276
+ self.__handleSocketEmitEvent(socket, emitEvent, args, 'room_broadcast', {
277
+ socketId: socket.id,
278
+ broadcastType: 'room',
279
+ room: room,
280
+ remoteAddress: socket.conn ? socket.conn.remoteAddress : null
281
+ });
282
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
283
+ };
284
+ roomObj.emit.__whatap_wrapped__ = true;
285
+ }
286
+
287
+ return roomObj;
288
+ };
289
+ socket.in.__whatap_wrapped__ = true;
290
+ }
291
+
292
+ return listener.apply(this, [socket]);
293
+ });
294
+ }
295
+
296
+ return original.call(this, event, listener);
297
+ };
298
+ });
299
+
300
+ // 5. io.emit 래핑 (전체 브로드캐스트)
301
+ if (target.emit && typeof target.emit === 'function' && !target.emit.__whatap_wrapped__) {
302
+ const originalIoEmit = target.emit;
303
+ target.emit = function(emitEvent, ...args) {
304
+ if (!config.trace_background_socket_enabled) {
305
+ return originalIoEmit.apply(this, [emitEvent, ...args]);
306
+ }
307
+
308
+ self.__handleSocketEmitEvent(null, emitEvent, args, 'global_broadcast', {
309
+ broadcastType: 'global',
310
+ connectedSockets: this.sockets ? (this.sockets.sockets ? this.sockets.sockets.size : 0) : 0
311
+ });
312
+ return originalIoEmit.apply(this, [emitEvent, ...args]);
313
+ };
314
+ target.emit.__whatap_wrapped__ = true;
315
+ }
316
+
317
+ // 6. io.to() 래핑 (서버 레벨 룸 브로드캐스트)
318
+ if (typeof target.to === "function" && !target.to.__whatap_wrapped__) {
319
+ const originalIoTo = target.to;
320
+ target.to = function(room) {
321
+ const roomObj = originalIoTo.call(this, room);
322
+ if (roomObj && typeof roomObj.emit === 'function') {
323
+ const originalRoomEmit = roomObj.emit;
324
+ roomObj.emit = function(emitEvent, ...args) {
325
+ if (!config.trace_background_socket_enabled) {
326
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
327
+ }
328
+
329
+ // 해당 룸의 소켓 연결 개수 계산
330
+ let roomSocketCount = 0;
331
+ if (this.adapter && this.adapter.rooms) {
332
+ const roomSockets = this.adapter.rooms.get(room);
333
+ roomSocketCount = roomSockets ? roomSockets.size : 0;
334
+ }
335
+
336
+ self.__handleSocketEmitEvent(null, emitEvent, args, 'server_room_broadcast', {
337
+ broadcastType: 'server_room',
338
+ room: room,
339
+ connectedSockets: roomSocketCount
171
340
  });
341
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
342
+ };
343
+ roomObj.emit.__whatap_wrapped__ = true;
344
+ }
172
345
 
173
- return emitFn;
346
+ return roomObj;
347
+ };
348
+ target.to.__whatap_wrapped__ = true;
349
+ }
350
+
351
+ // 7. io.in() 래핑 (서버 레벨 룸 브로드캐스트 - 다른 방식)
352
+ if (typeof target.in === "function" && !target.in.__whatap_wrapped__) {
353
+ const originalIoIn = target.in;
354
+ target.in = function(room) {
355
+ const roomObj = originalIoIn.call(this, room);
356
+
357
+ if (roomObj && typeof roomObj.emit === 'function' && !roomObj.emit.__whatap_wrapped__) {
358
+ const originalRoomEmit = roomObj.emit;
359
+ roomObj.emit = function(emitEvent, ...args) {
360
+ if (!config.trace_background_socket_enabled) {
361
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
362
+ }
363
+
364
+ self.__handleSocketEmitEvent(null, emitEvent, args, 'server_room_broadcast', {
365
+ broadcastType: 'server_room',
366
+ room: room,
367
+ connectedSockets: this.sockets ? (this.sockets.sockets ? this.sockets.sockets.size : 0) : 0
368
+ });
369
+ return originalRoomEmit.apply(this, [emitEvent, ...args]);
174
370
  };
371
+ roomObj.emit.__whatap_wrapped__ = true;
372
+ }
175
373
 
176
- // emit 함수에만 래핑 적용 (불필요한 래핑 제거)
177
- if (typeof socket.emit === 'function' && !socket.emit.__wrapped__) {
178
- const original = socket.emit;
179
- socket.emit = wrappedEmit(original);
180
- socket.emit.__wrapped__ = true;
181
- }
374
+ return roomObj;
375
+ };
376
+ target.in.__whatap_wrapped__ = true;
377
+ }
182
378
 
183
- return listener.apply(this, [socket]);
184
- });
185
- }
379
+ return true;
380
+ };
186
381
 
187
- return original.call(this, event, listener);
188
- };
189
- });
382
+ // 1. Socket.IO v2.x 스타일 (mod.prototype)
383
+ if (mod.prototype) {
384
+ wrapSocketIOEvents(mod.prototype);
385
+ }
386
+
387
+ // 2. Socket.IO v4.x 스타일 (mod.Server.prototype)
388
+ if (mod.Server && mod.Server.prototype) {
389
+ wrapSocketIOEvents(mod.Server.prototype);
390
+ }
391
+
392
+ // 3. 직접 모듈 래핑 (일부 버전에서 필요할 수 있음)
393
+ // if (typeof mod.on === 'function') {
394
+ // wrapSocketIOEvents(mod);
395
+ // }
190
396
  };
191
397
 
192
- // 소켓 이벤트 처리 최적화 (CPU 및 메모리 사용 감소)
193
- SocketIOObserver.prototype.__handleSocketEmitEvent = function(socket, event, args) {
398
+ // 소켓 이벤트 처리 최적화 (CPU 및 메모리 사용 감소) - broadcast 지원 추가
399
+ SocketIOObserver.prototype.__handleSocketEmitEvent = function(socket, event, args, emitType, contextInfo) {
194
400
  // 빠른 샘플링 체크 (조기 반환으로 성능 향상)
195
401
  if (config.trace_sampling_enabled && !this.socketCounter.checkSampling()) {
196
402
  MeterService.add(0, 1, 0, SecurityMaster.PCODE, SecurityMaster.OKIND, SecurityMaster.OID);
@@ -201,15 +407,17 @@ SocketIOObserver.prototype.__handleSocketEmitEvent = function(socket, event, arg
201
407
  const shouldProfileResource = Math.random() < config.resource_sampling_rate;
202
408
 
203
409
  // 컨텍스트 생성 및 실행
204
- TraceContextManager._asyncLocalStorage.run(this.__initCtx(socket, args, shouldProfileResource), () => {
410
+ TraceContextManager._asyncLocalStorage.run(this.__initCtx(socket, args, shouldProfileResource, emitType, contextInfo), () => {
205
411
  try {
206
412
  var ctx = TraceContextManager._asyncLocalStorage.getStore();
207
413
  if (!ctx) return;
208
414
 
209
415
  // IP 주소 처리 최적화
210
416
  var host = null;
211
- if (socket.conn && socket.conn.remoteAddress) {
212
- const ipInfo = this.getProcessedIp(socket.conn.remoteAddress);
417
+ var remoteAddress = contextInfo.remoteAddress || (socket && socket.conn && socket.conn.remoteAddress);
418
+
419
+ if (remoteAddress) {
420
+ const ipInfo = this.getProcessedIp(remoteAddress);
213
421
  if (ipInfo) {
214
422
  host = ipInfo.host;
215
423
  // 이미 처리된 IP 바이트 사용
@@ -217,8 +425,23 @@ SocketIOObserver.prototype.__handleSocketEmitEvent = function(socket, event, arg
217
425
  step.start_time = ctx.getElapsedTime();
218
426
  step.ipaddr = ipInfo.ipBytes;
219
427
  step.elapsed = 0; // 즉시 종료 (측정 최소화)
428
+
429
+ // 브로드캐스트 타입 정보 추가
430
+ if (emitType !== 'socket') {
431
+ step.broadcast_type = emitType;
432
+ step.broadcast_info = contextInfo;
433
+ }
434
+
220
435
  ctx.profile.push(step);
221
436
  }
437
+ } else {
438
+ const ipBytes = Buffer.from(IPUtil.stringToBytes("0.0.0.0"));
439
+ var step = new SocketStep();
440
+ step.start_time = ctx.getElapsedTime();
441
+ step.ipaddr = ipBytes;
442
+ step.elapsed = 0;
443
+
444
+ ctx.profile.push(step);
222
445
  }
223
446
 
224
447
  // 트랜잭션 종료 및 데이터 전송
@@ -246,7 +469,9 @@ SocketIOObserver.prototype.__endTransaction = function(error, ctx) {
246
469
  profile.time = wtx.endTime;
247
470
  wtx.elapsed = ctx.getElapsedTime();
248
471
 
249
- DataTextAgent.SERVICE.add(ctx.service_hash, ctx.service_name);
472
+ // 서비스 이름에 브로드캐스트 타입 정보 추가
473
+ let serviceName = ctx.service_name;
474
+ DataTextAgent.SERVICE.add(ctx.service_hash, serviceName);
250
475
 
251
476
  wtx.seq = ctx.txid;
252
477
  wtx.service = ctx.service_hash;
@@ -294,19 +519,55 @@ SocketIOObserver.prototype.__endTransaction = function(error, ctx) {
294
519
  }
295
520
  };
296
521
 
297
- // 컨텍스트 초기화 최적화
298
- SocketIOObserver.prototype.__initCtx = function(socket, args, shouldProfileResource) {
522
+ // 컨텍스트 초기화 최적화 - broadcast 정보 추가
523
+ SocketIOObserver.prototype.__initCtx = function(socket, args, shouldProfileResource, emitType, contextInfo) {
299
524
  const ctx = TraceContextManager.start();
300
525
  if (!ctx) {return;}
301
526
 
302
527
  var remote_addr;
303
- if (socket.conn && socket.conn.remoteAddress) {
304
- const address = socket.conn.remoteAddress;
305
- if(address && address.includes(':')){
306
- remote_addr = address.substring(address.lastIndexOf(':')+1);
528
+ var remoteAddress = contextInfo.remoteAddress || (socket && socket.conn && socket.conn.remoteAddress);
529
+
530
+ if (remoteAddress) {
531
+ if(remoteAddress && remoteAddress.includes(':')){
532
+ remote_addr = remoteAddress.substring(remoteAddress.lastIndexOf(':')+1);
307
533
  }
308
534
  }
309
535
 
536
+ ctx.service_name = "/socket.io";
537
+ ctx.service_hash = HashUtil.hashFromString(ctx.service_name);
538
+
539
+ // MessageStep 생성 - 브로드캐스트 타입별 정보
540
+ var step = new MessageStep();
541
+ var title, message;
542
+
543
+ switch (emitType) {
544
+ case 'broadcast':
545
+ title = "Broadcast";
546
+ message = "Broadcast to all clients except sender";
547
+ break;
548
+ case 'room_broadcast':
549
+ title = "Room Broadcast";
550
+ message = `Broadcast to room: ${contextInfo.room || 'unknown'}`;
551
+ break;
552
+ case 'global_broadcast':
553
+ title = "Global Broadcast";
554
+ message = `Total clients: ${contextInfo.connectedSockets || 0}`;
555
+ break;
556
+ case 'server_room_broadcast':
557
+ title = "Server Room Broadcast";
558
+ message = `Room: ${contextInfo.room || 'unknown'}, Total clients: ${contextInfo.connectedSockets || 0}`;
559
+ break;
560
+ default:
561
+ title = "Socket";
562
+ message = "Direct socket message";
563
+ }
564
+
565
+ step.hash = HashUtil.hashFromString(title);
566
+ step.start_time = ctx.getElapsedTime();
567
+ step.desc = message;
568
+ DataTextAgent.MESSAGE.add(step.hash, title);
569
+ ctx.profile.push(step);
570
+
310
571
  // 리소스 측정은 샘플링된 경우에만 수행
311
572
  ctx.shouldProfileResource = shouldProfileResource;
312
573
  if (shouldProfileResource) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "whatap",
3
3
  "homepage": "http://www.whatap.io",
4
- "version": "0.5.22",
5
- "releaseDate": "20250630",
4
+ "version": "0.5.24",
5
+ "releaseDate": "20250805",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},