whatap 1.0.2 → 1.0.3-canary.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.
Files changed (198) hide show
  1. package/README.md +77 -33
  2. package/agent/darwin/arm64/whatap_nodejs +0 -0
  3. package/agent/linux/amd64/whatap_nodejs +0 -0
  4. package/agent/linux/arm64/whatap_nodejs +0 -0
  5. package/build.txt +4 -0
  6. package/lib/conf/config-default.js +3 -10
  7. package/lib/conf/configure.js +349 -369
  8. package/lib/conf/license.js +1 -1
  9. package/lib/control/packagectr-helper.js +3 -34
  10. package/lib/core/agent.js +888 -176
  11. package/lib/core/interceptor.js +6 -6
  12. package/lib/core/shimmer.js +36 -82
  13. package/lib/counter/counter-manager.js +8 -79
  14. package/lib/counter/task/activetransaction.js +17 -68
  15. package/lib/io/data-inputx.js +3 -13
  16. package/lib/io/data-outputx.js +206 -268
  17. package/lib/logger.js +6 -6
  18. package/lib/net/security-master.js +20 -139
  19. package/lib/observers/apollo-server-observer.js +27 -33
  20. package/lib/observers/global-observer.js +80 -155
  21. package/lib/observers/http-observer.js +236 -666
  22. package/lib/observers/ioredis-observer.js +294 -0
  23. package/lib/observers/maria-observer.js +362 -204
  24. package/lib/observers/mongodb-observer.js +226 -169
  25. package/lib/observers/mongoose-observer.js +323 -518
  26. package/lib/observers/mssql-observer.js +418 -177
  27. package/lib/observers/mysql-observer.js +449 -342
  28. package/lib/observers/mysql2-observer.js +358 -396
  29. package/lib/observers/oracle-observer.js +384 -559
  30. package/lib/observers/pgsql-observer.js +489 -231
  31. package/lib/observers/prisma-observer.js +92 -303
  32. package/lib/observers/process-observer.js +35 -79
  33. package/lib/observers/redis-observer.js +331 -166
  34. package/lib/observers/socket.io-observer.js +187 -226
  35. package/lib/observers/websocket-observer.js +301 -175
  36. package/lib/pack/counter-pack.js +0 -3
  37. package/lib/pack/log-sink-pack.js +52 -14
  38. package/lib/pack/tagcount-pack.js +4 -4
  39. package/lib/{counter/task → system}/gc-action.js +74 -27
  40. package/lib/trace/trace-context-manager.js +25 -113
  41. package/lib/trace/trace-context.js +7 -21
  42. package/lib/trace/trace-httpc.js +11 -17
  43. package/lib/trace/trace-sql.js +21 -29
  44. package/lib/udp/async_sender.js +119 -0
  45. package/lib/udp/index.js +17 -0
  46. package/lib/udp/packet_enum.js +52 -0
  47. package/lib/udp/packet_queue.js +69 -0
  48. package/lib/udp/packet_type_enum.js +33 -0
  49. package/lib/udp/param_def.js +72 -0
  50. package/lib/udp/udp_session.js +336 -0
  51. package/lib/util/escape-literal-sql.js +5 -5
  52. package/lib/util/hashutil.js +18 -18
  53. package/lib/util/keygen.js +3 -0
  54. package/lib/util/linkedset.js +2 -1
  55. package/lib/util/nodeutil.js +1 -2
  56. package/lib/util/sql-util.js +178 -0
  57. package/lib/util/trace-helper.js +91 -0
  58. package/lib/util/transfer.js +58 -0
  59. package/lib/value/map-value.js +2 -3
  60. package/package.json +5 -9
  61. package/whatap.conf +4 -1
  62. package/lib/conf/conf-sys-mon.js +0 -101
  63. package/lib/control/cmd-config.js +0 -24
  64. package/lib/control/control-handler.js +0 -367
  65. package/lib/core/request-agent.js +0 -27
  66. package/lib/counter/meter/meter-activex.js +0 -67
  67. package/lib/counter/meter/meter-httpc.js +0 -57
  68. package/lib/counter/meter/meter-resource.js +0 -9
  69. package/lib/counter/meter/meter-service.js +0 -168
  70. package/lib/counter/meter/meter-socket.io.js +0 -51
  71. package/lib/counter/meter/meter-sql.js +0 -71
  72. package/lib/counter/meter/meter-users.js +0 -58
  73. package/lib/counter/meter.js +0 -183
  74. package/lib/counter/task/agentinfo.js +0 -107
  75. package/lib/counter/task/gcstat.js +0 -34
  76. package/lib/counter/task/heapmem.js +0 -25
  77. package/lib/counter/task/httpc.js +0 -76
  78. package/lib/counter/task/metering-info.js +0 -125
  79. package/lib/counter/task/proc-cpu.js +0 -29
  80. package/lib/counter/task/realtimeuser.js +0 -31
  81. package/lib/counter/task/res/systemECSTask.js +0 -39
  82. package/lib/counter/task/res/systemKubeTask.js +0 -53
  83. package/lib/counter/task/res/util/awsEcsClientThread.js +0 -218
  84. package/lib/counter/task/res/util/linuxProcStatUtil.js +0 -14
  85. package/lib/counter/task/res-sys-cpu.js +0 -62
  86. package/lib/counter/task/service.js +0 -202
  87. package/lib/counter/task/socketio.js +0 -30
  88. package/lib/counter/task/sql.js +0 -105
  89. package/lib/counter/task/systemperf.js +0 -43
  90. package/lib/data/datapack-sender.js +0 -289
  91. package/lib/data/dataprofile-agent.js +0 -162
  92. package/lib/data/datatext-agent.js +0 -135
  93. package/lib/data/event-level.js +0 -15
  94. package/lib/data/test.js +0 -49
  95. package/lib/data/zipprofile.js +0 -197
  96. package/lib/env/constants.js +0 -21
  97. package/lib/error/error-handler.js +0 -437
  98. package/lib/kube/kube-client.js +0 -144
  99. package/lib/lang/text-types.js +0 -58
  100. package/lib/logsink/line-log-util.js +0 -87
  101. package/lib/logsink/line-log.js +0 -12
  102. package/lib/logsink/log-sender.js +0 -78
  103. package/lib/logsink/log-tracer.js +0 -40
  104. package/lib/logsink/sender-util.js +0 -56
  105. package/lib/logsink/zip/zip-send.js +0 -177
  106. package/lib/net/netflag.js +0 -55
  107. package/lib/net/receiver.js +0 -66
  108. package/lib/net/sender.js +0 -141
  109. package/lib/net/tcp-return.js +0 -18
  110. package/lib/net/tcp-session.js +0 -286
  111. package/lib/net/tcpreq-client-proxy.js +0 -70
  112. package/lib/net/tcprequest-mgr.js +0 -58
  113. package/lib/observers/cluster-observer.js +0 -22
  114. package/lib/observers/express-observer.js +0 -215
  115. package/lib/observers/file-observer.js +0 -184
  116. package/lib/observers/grpc-observer.js +0 -336
  117. package/lib/observers/memcached-observer.js +0 -56
  118. package/lib/observers/mongo-observer.js +0 -317
  119. package/lib/observers/net-observer.js +0 -77
  120. package/lib/observers/promise-observer.js +0 -31
  121. package/lib/observers/schedule-observer.js +0 -67
  122. package/lib/observers/stream-observer.js +0 -19
  123. package/lib/observers/thrift-observer.js +0 -197
  124. package/lib/pack/activestack-pack.js +0 -55
  125. package/lib/pack/apenum.js +0 -8
  126. package/lib/pack/errorsnap-pack.js +0 -69
  127. package/lib/pack/event-pack.js +0 -54
  128. package/lib/pack/hitmap-pack.js +0 -63
  129. package/lib/pack/hitmap-pack1.js +0 -152
  130. package/lib/pack/netstat.js +0 -15
  131. package/lib/pack/otype.js +0 -7
  132. package/lib/pack/profile-pack.js +0 -49
  133. package/lib/pack/realtimeuser-pack.js +0 -41
  134. package/lib/pack/stat-general-pack.js +0 -96
  135. package/lib/pack/staterror-pack.js +0 -120
  136. package/lib/pack/stathttpc-pack.js +0 -66
  137. package/lib/pack/stathttpc-rec.js +0 -78
  138. package/lib/pack/statremote-pack.js +0 -46
  139. package/lib/pack/statservice-pack.js +0 -63
  140. package/lib/pack/statservice-pack1.js +0 -88
  141. package/lib/pack/statservice-rec.js +0 -292
  142. package/lib/pack/statservice-rec_dep.js +0 -151
  143. package/lib/pack/statsql-pack.js +0 -69
  144. package/lib/pack/statsql-rec.js +0 -100
  145. package/lib/pack/statuseragent-pack.js +0 -44
  146. package/lib/pack/tagctr.js +0 -15
  147. package/lib/pack/text-pack.js +0 -50
  148. package/lib/pack/time-count.js +0 -25
  149. package/lib/pack/websocket.js +0 -15
  150. package/lib/pack/zip-pack.js +0 -70
  151. package/lib/pii/pii-item.js +0 -31
  152. package/lib/pii/pii-mask.js +0 -174
  153. package/lib/plugin/plugin-loadermanager.js +0 -57
  154. package/lib/plugin/plugin.js +0 -75
  155. package/lib/service/tx-record.js +0 -332
  156. package/lib/stat/stat-error.js +0 -116
  157. package/lib/stat/stat-httpc.js +0 -98
  158. package/lib/stat/stat-remote-ip.js +0 -46
  159. package/lib/stat/stat-remote-ipurl.js +0 -88
  160. package/lib/stat/stat-sql.js +0 -113
  161. package/lib/stat/stat-tranx.js +0 -58
  162. package/lib/stat/stat-tx-caller.js +0 -160
  163. package/lib/stat/stat-tx-domain.js +0 -111
  164. package/lib/stat/stat-tx-referer.js +0 -112
  165. package/lib/stat/stat-useragent.js +0 -48
  166. package/lib/stat/timingsender.js +0 -76
  167. package/lib/step/activestack-step.js +0 -38
  168. package/lib/step/dbc-step.js +0 -36
  169. package/lib/step/http-stepx.js +0 -67
  170. package/lib/step/message-step.js +0 -40
  171. package/lib/step/method-stepx.js +0 -45
  172. package/lib/step/resultset-step.js +0 -40
  173. package/lib/step/securemsg-step.js +0 -44
  174. package/lib/step/socket-step.js +0 -46
  175. package/lib/step/sql-stepx.js +0 -68
  176. package/lib/step/sqlxtype.js +0 -16
  177. package/lib/step/step.js +0 -66
  178. package/lib/step/stepenum.js +0 -54
  179. package/lib/topology/link.js +0 -63
  180. package/lib/topology/nodeinfo.js +0 -123
  181. package/lib/topology/status-detector.js +0 -111
  182. package/lib/util/anylist.js +0 -103
  183. package/lib/util/cardinality/hyperloglog.js +0 -106
  184. package/lib/util/cardinality/murmurhash.js +0 -31
  185. package/lib/util/cardinality/registerset.js +0 -75
  186. package/lib/util/errordata.js +0 -21
  187. package/lib/util/iputil_x.js +0 -527
  188. package/lib/util/kube-util.js +0 -73
  189. package/lib/util/paramsecurity.js +0 -80
  190. package/lib/util/pre-process.js +0 -13
  191. package/lib/util/process-seq.js +0 -166
  192. package/lib/util/property-util.js +0 -36
  193. package/lib/util/request-queue.js +0 -70
  194. package/lib/util/requestdouble-queue.js +0 -72
  195. package/lib/util/resourceprofile.js +0 -157
  196. package/lib/util/stop-watch.js +0 -30
  197. package/lib/util/system-util.js +0 -10
  198. package/lib/util/userid-util.js +0 -57
@@ -6,24 +6,17 @@
6
6
 
7
7
  var TraceContextManager = require('../trace/trace-context-manager'),
8
8
  ParsedSql = require('../trace/parsed-sql'),
9
- SqlStepX = require('../step/sql-stepx'),
10
- DBCStep = require('../step/dbc-step'),
11
- ResultSetStep = require('../step/resultset-step'),
12
- DataTextAgent = require('../data/datatext-agent'),
13
- StatSql = require('../stat/stat-sql'),
14
- MeterSql = require('../counter/meter/meter-sql'),
15
9
  conf = require('../conf/configure'),
16
10
  IntKeyMap = require('../util/intkey-map'),
17
11
  EscapeLiteralSQL = require('../util/escape-literal-sql'),
18
12
  HashUtil = require('../util/hashutil'),
19
- StatError = require('../stat/stat-error'),
20
- TextTypes = require('../lang/text-types'),
21
- ParamSecurity = require('../util/paramsecurity'),
22
13
  Logger = require('../logger'),
23
14
  DateUtil = require('../util/dateutil'),
24
- Buffer = require('buffer').Buffer,
25
- TraceSQL = require('../trace/trace-sql'),
26
- shimmer = require('../core/shimmer');
15
+ AsyncSender = require('../udp/async_sender'),
16
+ PacketTypeEnum = require('../udp/packet_type_enum'),
17
+ shimmer = require('../core/shimmer'),
18
+ TraceSQL = require('../trace/trace-sql');
19
+ const { AsyncResource } = require('async_hooks');
27
20
 
28
21
  var Mysql2Observer = function (agent) {
29
22
  this.agent = agent;
@@ -33,12 +26,25 @@ var Mysql2Observer = function (agent) {
33
26
  var dbc_hash = 0;
34
27
  var dbc = 'mysql://';
35
28
 
36
- // 전역으로 후킹된 함수들을 추적
37
- var globalHookedFunctions = new WeakSet();
38
- // 후킹된 객체 인스턴스들을 추적 (중복 방지 강화)
29
+ // 후킹된 객체 인스턴스들을 추적 (중복 방지)
39
30
  var hookedInstances = new WeakSet();
40
31
 
41
- var createQueryWrapper = function(isPromise = false) {
32
+ // formatSqlWithParams 함수 개선 - mod를 직접 받아서 사용
33
+ function formatSqlWithParams(sql, params, mod) {
34
+ try {
35
+ if (mod && mod.format && typeof mod.format === 'function') {
36
+ return mod.format(sql, params);
37
+ }
38
+ // fallback: 동적으로 require (성능상 권장하지 않음)
39
+ var mysql = require('mysql2');
40
+ return mysql.format(sql, params);
41
+ } catch (e) {
42
+ return sql;
43
+ }
44
+ }
45
+
46
+ // 쿼리 래핑 함수 (AsyncResource.bind 적용)
47
+ var createQueryWrapper = function(isPromise = false, dbcUrl, mod) {
42
48
  return function(original) {
43
49
  return function wrappedQuery() {
44
50
  var args = Array.prototype.slice.call(arguments);
@@ -46,12 +52,15 @@ var createQueryWrapper = function(isPromise = false) {
46
52
 
47
53
  // ctx가 없으면 추적하지 않고 원본 함수만 실행
48
54
  if (ctx == null || args[0] == null) {
49
- return original.apply(this, args);
55
+ return original.apply(this, arguments);
50
56
  }
51
57
  if (args[0].sql == null && typeof args[0] != 'string') {
52
- return original.apply(this, args);
58
+ return original.apply(this, arguments);
53
59
  }
54
60
 
61
+ // AsyncResource 생성
62
+ var asyncResource = new AsyncResource('mysql2-query');
63
+
55
64
  // 동시 쿼리 처리를 위한 고유 ID 생성
56
65
  var queryId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
57
66
 
@@ -60,181 +69,123 @@ var createQueryWrapper = function(isPromise = false) {
60
69
  ctx._processing_queries = new Set();
61
70
  }
62
71
 
63
- // 같은 SQL 동시에 처리되고 있는지 확인
64
- var sqlKey = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].sql);
65
- if (sqlKey && ctx._processing_queries.has(sqlKey)) {
66
- return original.apply(this, args);
72
+ // SQL 파라미터 추출
73
+ var sql = args.length > 0 ? args[0] : undefined;
74
+ var params = args.length > 1 && Array.isArray(args[1]) ? args[1] : undefined;
75
+
76
+ if (typeof sql !== 'string') {
77
+ sql = args[0].sql || undefined;
78
+ if (args[0].values && Array.isArray(args[0].values)) {
79
+ params = args[0].values;
80
+ }
81
+ }
82
+
83
+ // 최종 SQL 생성 (파라미터 바인딩) - mod 직접 전달
84
+ var finalSql = sql;
85
+ if (typeof sql === 'string' && sql.length > 0 && params && params.length > 0) {
86
+ try {
87
+ finalSql = formatSqlWithParams(sql, params, mod);
88
+ } catch (e) {
89
+ Logger.printError('WHATAP-SQL-BINDING', 'Error binding parameters', e, false);
90
+ finalSql = sql; // 바인딩 실패 시 원본 사용
91
+ }
67
92
  }
68
93
 
69
- if (sqlKey) {
70
- ctx._processing_queries.add(sqlKey);
94
+ // 같은 최종 SQL이 동시에 처리되고 있는지 확인
95
+ if (finalSql && ctx._processing_queries.has(finalSql)) {
96
+ return original.apply(this, args);
71
97
  }
72
98
 
73
- var dbc_step = new DBCStep();
74
- DataTextAgent.DBC.add(dbc_hash, dbc);
75
- DataTextAgent.METHOD.add(dbc_hash, dbc);
76
- DataTextAgent.ERROR.add(dbc_hash, dbc);
99
+ if (finalSql) {
100
+ ctx._processing_queries.add(finalSql);
101
+ }
77
102
 
78
- dbc_step.hash = dbc_hash;
79
- dbc_step.start_time = ctx.getElapsedTime();
80
- ctx.profile.push(dbc_step);
103
+ // HTTP Observer 패턴을 따라 시작 시간 설정
104
+ ctx.start_time = Date.now();
81
105
 
82
- var sql_step = new SqlStepX();
83
- sql_step.queryId = queryId;
84
- sql_step.start_time = ctx.getElapsedTime();
85
- ctx.profile.push(sql_step);
106
+ // DB 연결 패킷 전송 (elapsed = 0으로 설정)
107
+ ctx.elapsed = 0;
108
+ AsyncSender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, [dbcUrl]);
86
109
 
110
+ // SQL 시작 시간 기록
111
+ var sql_start_time = Date.now();
87
112
  ctx.footprint('MySql2 Query Start [' + queryId + ']');
88
113
  ctx.sql_count++;
89
114
 
90
- var sql = args.length > 0 ? args[0] : undefined,
91
- psql = null;
92
-
93
- if (typeof sql !== 'string') {
94
- sql = args[0].sql || undefined;
95
- }
96
-
97
- if (typeof sql === 'string' && sql.length > 0) {
115
+ var psql = null;
116
+ if (typeof finalSql === 'string' && finalSql.length > 0) {
98
117
  try {
99
- psql = escapeLiteral(sql);
100
- Logger.print('WHATAP-SQL-DEBUG', 'Processing SQL [' + queryId + ']: ' + sql.substring(0, 100), false);
118
+ psql = escapeLiteral(finalSql);
119
+ // Logger.print('WHATAP-SQL-DEBUG', 'Processing SQL [' + queryId + ']: ' + finalSql.substring(0, 200), false);
101
120
  } catch (e) {
102
- Logger.printError('WHATAP-191', 'Mysql2Observer escapeliteral error', e, false);
121
+ Logger.printError('WHATAP-225', 'Mysql2Observer escapeliteral error', e, false);
103
122
  }
104
123
  } else {
105
- sql = '';
106
- psql = escapeLiteral(sql);
124
+ finalSql = '';
125
+ psql = escapeLiteral(finalSql);
107
126
  }
108
127
 
109
- if (psql != null) {
110
- sql_step.hash = psql.sql;
111
- }
112
- sql_step.dbc = dbc_hash;
128
+ var sqlHash = psql ? psql.sql : 0;
129
+ ctx.active_sqlhash = true;
130
+ ctx.active_dbc = dbc_hash;
113
131
 
114
- var els = new EscapeLiteralSQL(sql);
115
- els.process();
132
+ // AsyncResource로 바인딩된 쿼리 콜백 생성
133
+ function createBoundQueryCallback() {
134
+ function queryCallback(err, results, fields) {
135
+ var currentCtx = TraceContextManager.getCurrentContext();
116
136
 
117
- // Context 활성 상태를 배열로 관리 (여러 쿼리 동시 처리)
118
- if (!ctx.active_queries) {
119
- ctx.active_queries = [];
120
- }
121
- ctx.active_queries.push({
122
- sqlhash: sql_step.hash,
123
- dbc: sql_step.dbc,
124
- queryId: queryId
125
- });
126
-
127
- // 백워드 호환성을 위해 마지막 쿼리 정보 유지
128
- ctx.active_sqlhash = sql_step.hash;
129
- ctx.active_dbc = sql_step.dbc;
130
-
131
- if (conf.profile_sql_param_enabled) {
132
- var params = args.length > 1 && Array.isArray(args[1]) ? args[1] : undefined;
133
- sql_step.setTrue(1);
134
- var crc = {value: 0};
135
- sql_step.p1 = toParamBytes(psql.param, crc);
136
- if (params != undefined) {
137
- const result = params.map((param) => {
138
- if (typeof param === 'string') {
139
- return `'${param}'`
140
- }
141
- return param
142
- }).toString()
143
- sql_step.p2 = toParamBytes(result, crc);
144
- }
145
- sql_step.pcrc = crc.value;
146
- }
137
+ // bind된 함수에서도 context가 없는 경우 복원
138
+ if (currentCtx == null) {
139
+ TraceContextManager.resume(ctx.id);
140
+ currentCtx = ctx;
141
+ }
147
142
 
148
- // 원본 MySQL Observer의 queryCallback 함수를 참고한 결과 처리 함수
149
- function queryCallback(obj, args) {
150
- if (ctx == null) {
151
- return;
152
- }
143
+ if (currentCtx == null) {
144
+ return;
145
+ }
153
146
 
154
- // 처리 완료된 쿼리를 목록에서 제거
155
- if (sqlKey && ctx._processing_queries) {
156
- ctx._processing_queries.delete(sqlKey);
157
- }
147
+ // 처리 완료된 쿼리를 목록에서 제거
148
+ if (finalSql && currentCtx._processing_queries) {
149
+ currentCtx._processing_queries.delete(finalSql);
150
+ }
158
151
 
159
- // 활성 쿼리 목록에서 현재 쿼리 제거
160
- if (ctx.active_queries) {
161
- ctx.active_queries = ctx.active_queries.filter(q => q.queryId !== queryId);
162
- }
152
+ var sql_elapsed = Date.now() - sql_start_time;
153
+ var resultCount = 0;
163
154
 
164
- TraceContextManager.resume(ctx._id);
155
+ if (err) {
156
+ handleSqlError(currentCtx, err, sqlHash);
157
+ }
165
158
 
166
- // 에러 처리 (args[0]이 에러 객체)
167
- if (args[0]) {
168
- try {
169
- if (conf.trace_sql_error_stack && conf.trace_sql_error_depth) {
170
- var traceDepth = conf.trace_sql_error_depth;
171
- var errorStack = args[0].stack ? args[0].stack.split("\n") : [];
172
- if (errorStack.length > traceDepth) {
173
- errorStack = errorStack.slice(0, traceDepth + 1);
174
- }
175
- ctx.error_message = errorStack.join("\n");
176
- sql_step.error = ctx.error = StatError.addError('mysql2-' + (args[0].code || 'UNKNOWN'),
177
- args[0].sqlMessage || args[0].message, ctx.service_hash, TextTypes.SQL, sql_step.hash);
159
+ // 결과 개수 계산
160
+ if (!err && results) {
161
+ if (Array.isArray(results)) {
162
+ resultCount = results.length;
163
+ } else if (results.affectedRows !== undefined) {
164
+ resultCount = results.affectedRows;
165
+ } else if (results.changedRows !== undefined) {
166
+ resultCount = results.changedRows;
178
167
  }
168
+ }
179
169
 
180
- // 에러 무시 설정 확인
181
- var shouldAddError = false;
182
- if (conf._is_trace_ignore_err_cls_contains === true && args[0].code &&
183
- args[0].code.indexOf(conf.trace_ignore_err_cls_contains) < 0) {
184
- shouldAddError = true;
185
- } else if (conf._is_trace_ignore_err_msg_contains === true && args[0].message &&
186
- args[0].message.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
187
- shouldAddError = true;
188
- } else if (conf._is_trace_ignore_err_cls_contains === false && conf._is_trace_ignore_err_msg_contains === false) {
189
- shouldAddError = true;
190
- }
170
+ // TraceSQL 처리
171
+ try {
172
+ TraceSQL.isSlowSQL(currentCtx);
191
173
 
192
- if (shouldAddError) {
193
- sql_step.error = StatError.addError('mysql2-' + (args[0].code || 'UNKNOWN'),
194
- args[0].message || 'mysql2 error', ctx.service_hash, TextTypes.SQL, sql_step.hash);
195
- if (ctx.error.isZero()) {
196
- ctx.error = sql_step.error;
197
- }
174
+ // ResultSet 처리 (SELECT 쿼리)
175
+ if (!err && results && psql && psql.type === 'S') {
176
+ TraceSQL.isTooManyRecords(resultCount, currentCtx);
198
177
  }
199
178
  } catch (e) {
200
- Logger.printError('WHATAP-192', 'Error handling MySQL2 error', e, false);
179
+ Logger.printError('WHATAP-TRACESQL', 'Error in TraceSQL processing', e, false);
201
180
  }
202
- }
203
181
 
204
- sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
205
- ctx.sql_time += sql_step.elapsed;
206
-
207
- // 핵심 통계 수집 로직
208
- TraceSQL.isSlowSQL(ctx);
209
- ctx.footprint('MySql2 Query Done [' + queryId + ']');
210
-
211
- MeterSql.add(dbc_hash, sql_step.elapsed, args[0] != null);
212
- StatSql.addSqlTime(ctx.service_hash, sql_step.dbc,
213
- sql_step.hash, sql_step.elapsed, args[0] != null, 0);
214
-
215
- // 결과 집합 처리 (SELECT 쿼리) - args[1]이 결과 배열
216
- if (Array.isArray(args[1]) && psql != null && psql.type === 'S') {
217
- var result_step = new ResultSetStep();
218
- result_step.start_time = ctx.getElapsedTime();
219
- result_step.elapsed = 0;
220
- result_step.fetch = args[1].length;
221
- result_step.sqlhash = psql.sql;
222
- result_step.dbc = dbc_hash;
223
- ctx.profile.push(result_step);
224
-
225
- ctx.rs_count = ctx.rs_count ? ctx.rs_count + args[1].length : args[1].length;
226
- ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
227
-
228
- // 핵심 통계 수집 로직
229
- MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
230
- StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
231
- TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
182
+ currentCtx.elapsed = sql_elapsed;
183
+ currentCtx.active_sqlhash = false;
184
+ AsyncSender.send_packet(PacketTypeEnum.TX_SQL, currentCtx, [dbcUrl, finalSql, String(resultCount)]);
232
185
  }
233
186
 
234
- // UPDATE/INSERT 쿼리 처리
235
- if (args[1] != null && args[1].affectedRows != null && psql != null && psql.type === 'U') {
236
- sql_step.updated = args[1].affectedRows || 0;
237
- }
187
+ // AsyncResource로 바인딩하여 안전한 context 보장
188
+ return asyncResource.bind(queryCallback);
238
189
  }
239
190
 
240
191
  // Promise 기반 처리 (mysql2/promise)
@@ -243,119 +194,142 @@ var createQueryWrapper = function(isPromise = false) {
243
194
 
244
195
  // Promise 체인 처리
245
196
  if (result && typeof result.then === 'function') {
197
+ var boundCallback = createBoundQueryCallback();
198
+
246
199
  return result.then(function(queryResult) {
247
200
  // MySQL2 Promise 결과는 [rows, fields] 형태
248
201
  var rows = Array.isArray(queryResult) ? queryResult[0] : queryResult;
249
202
  var fields = Array.isArray(queryResult) ? queryResult[1] : null;
250
203
 
251
- // queryCallback 형태로 변환하여 호출
252
- queryCallback(null, [null, rows, fields]);
204
+ // 바인딩된 콜백 호출
205
+ boundCallback(null, rows, fields);
253
206
  return queryResult;
254
207
  }).catch(function(error) {
255
- // 에러 시 처리
256
- queryCallback(null, [error, null]);
208
+ // 에러 시 바인딩된 콜백 호출
209
+ boundCallback(error, null, null);
257
210
  throw error;
258
211
  });
259
212
  }
260
213
  return result;
261
214
  }
262
215
 
263
- // 콜백 기반 처리 (일반 mysql2) - 원본 MysqlObserver 방식 적용
264
- var lastCallback = false;
216
+ // 콜백 기반 처리 (일반 mysql2)
217
+ var callbackWrapped = false;
218
+ var boundCallback = createBoundQueryCallback();
265
219
 
266
- // 원본 MysqlObserver의 functionHook 방식을 모방
267
220
  // 마지막 인자에서 함수를 찾아서 후킹
268
- lastCallback = hookLastFunction(args, queryCallback);
221
+ for (var i = args.length - 1; i >= 0; i--) {
222
+ if (typeof args[i] === 'function') {
223
+ var originalCallback = args[i];
224
+ args[i] = function() {
225
+ var callbackArgs = Array.prototype.slice.call(arguments);
226
+ try {
227
+ // 바인딩된 콜백 먼저 실행
228
+ boundCallback(callbackArgs[0], callbackArgs[1], callbackArgs[2]);
229
+ } catch (e) {
230
+ Logger.printError('WHATAP-CALLBACK', 'Error in callback hook', e, false);
231
+ }
232
+ // 원본 콜백 실행
233
+ if (originalCallback && typeof originalCallback === 'function') {
234
+ return originalCallback.apply(this, callbackArgs);
235
+ }
236
+ };
237
+ callbackWrapped = true;
238
+ break;
239
+ }
240
+ }
269
241
 
270
- // functionHook이 실패한 경우 _callback 속성 확인 (원본 코드 참조)
271
- if (lastCallback === false && args.length > 0 && args[0]._callback) {
242
+ if (!callbackWrapped && args.length > 0 && args[0]._callback) {
272
243
  try {
273
- // _callback 속성이 있는 경우 직접 후킹
274
244
  var originalCallback = args[0]._callback;
275
245
  args[0]._callback = function() {
276
246
  var callbackArgs = Array.prototype.slice.call(arguments);
277
247
  try {
278
- queryCallback(this, callbackArgs);
248
+ boundCallback(callbackArgs[0], callbackArgs[1], callbackArgs[2]);
279
249
  } catch (e) {
280
250
  Logger.printError('WHATAP-CALLBACK', 'Error in _callback hook', e, false);
281
251
  }
282
- // 원본 콜백 실행
283
252
  if (originalCallback && typeof originalCallback === 'function') {
284
253
  return originalCallback.apply(this, callbackArgs);
285
254
  }
286
255
  };
287
- lastCallback = true;
256
+ callbackWrapped = true;
288
257
  } catch (e) {
289
258
  Logger.printError('WHATAP-CALLBACK-HOOK', 'Error hooking _callback', e, false);
290
259
  }
291
260
  }
292
261
 
293
- // 수정된 args로 원본 함수 실행
294
262
  try {
295
263
  return original.apply(this, args);
296
264
  } catch (queryError) {
297
- // 쿼리 실행 중 에러 발생 시 즉시 처리
298
- queryCallback(null, [queryError, null]);
265
+ boundCallback(queryError, null, null);
299
266
  throw queryError;
300
267
  }
301
268
  };
302
269
  };
303
270
  };
304
271
 
305
- /**
306
- * 원본 MySQL Observer의 functionHook을 모방한 함수
307
- * 마지막 함수 인자를 찾아서 콜백으로 후킹
308
- */
309
- function hookLastFunction(args, callback) {
310
- if (!args || args.length === 0) {
311
- return false;
312
- }
272
+ function handleSqlError(ctx, err, sqlHash) {
273
+ if (!err) return;
313
274
 
314
- // 뒤에서부터 함수를 찾음
315
- for (var i = args.length - 1; i >= 0; i--) {
316
- if (typeof args[i] === 'function') {
317
- var originalFunction = args[i];
318
-
319
- // 함수를 래핑
320
- args[i] = function() {
321
- var functionArgs = Array.prototype.slice.call(arguments);
275
+ try {
276
+ var errorClass = err.code || err.name || err.constructor?.name || 'MySQLError';
277
+ var errorMessage = err.message || err.sqlMessage || 'mysql2 error';
278
+ var errorStack = '';
279
+
280
+ if (conf.trace_sql_error_stack && conf.trace_sql_error_depth && err.stack) {
281
+ var traceDepth = conf.trace_sql_error_depth;
282
+ var stackLines = err.stack.split("\n");
283
+ if (stackLines.length > traceDepth) {
284
+ stackLines = stackLines.slice(0, traceDepth + 1);
285
+ }
286
+ errorStack = stackLines.join("\n");
287
+ ctx.error_message = errorStack;
288
+ }
322
289
 
323
- try {
324
- // WHATAP 콜백 먼저 실행
325
- callback(this, functionArgs);
326
- } catch (e) {
327
- Logger.printError('WHATAP-FUNCTION-HOOK', 'Error in function hook callback', e, false);
328
- }
290
+ var shouldAddError = false;
291
+ if (conf._is_trace_ignore_err_cls_contains === true && errorClass &&
292
+ errorClass.indexOf(conf.trace_ignore_err_cls_contains) < 0) {
293
+ shouldAddError = true;
294
+ } else if (conf._is_trace_ignore_err_msg_contains === true && errorMessage &&
295
+ errorMessage.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
296
+ shouldAddError = true;
297
+ } else if (conf._is_trace_ignore_err_cls_contains === false &&
298
+ conf._is_trace_ignore_err_msg_contains === false) {
299
+ shouldAddError = true;
300
+ }
329
301
 
330
- // 원본 함수 실행
331
- if (originalFunction && typeof originalFunction === 'function') {
332
- try {
333
- return originalFunction.apply(this, functionArgs);
334
- } catch (e) {
335
- Logger.printError('WHATAP-ORIGINAL-FUNCTION', 'Error in original function', e, false);
336
- throw e;
337
- }
338
- }
339
- };
302
+ if (shouldAddError) {
303
+ if(!ctx.error) ctx.error = 1;
304
+ ctx.status = 500;
305
+ ctx.error_message = errorMessage;
306
+ ctx.errClass = errorClass;
307
+ ctx.errMessage = errorMessage;
340
308
 
341
- // 후킹된 함수임을 표시
342
- args[i].__whatap_hooked__ = true;
343
- return true;
309
+ var errors = [errorClass];
310
+ if (errorMessage) {
311
+ errors.push(errorMessage);
312
+ }
313
+ if (errorStack || err.stack) {
314
+ errors.push(errorStack || err.stack);
315
+ }
316
+ AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
344
317
  }
318
+ } catch (e) {
319
+ Logger.printError('WHATAP-226', 'Error handling MySQL2 error', e, false);
345
320
  }
346
-
347
- return false;
348
321
  }
349
322
 
350
- /**
351
- * 개선된 Connection 래퍼 - 원본 패턴 적용
352
- */
353
- var createConnectionWrapper = function(isPromise = false) {
323
+ // 연결 래핑 함수 (AsyncResource 적용)
324
+ var createConnectionWrapper = function(isPromise = false, mod) {
354
325
  return function(original) {
355
326
  return function wrappedCreateConnection() {
356
327
  var args = Array.prototype.slice.call(arguments);
357
328
  var ctx = TraceContextManager.getCurrentContext();
358
329
 
330
+ // AsyncResource 생성
331
+ var asyncResource = ctx ? new AsyncResource('mysql2-connection') : null;
332
+
359
333
  // DB 연결 정보 구성
360
334
  if (dbc_hash === 0 && args.length > 0) {
361
335
  var info = (args[0] || {});
@@ -366,18 +340,12 @@ var createConnectionWrapper = function(isPromise = false) {
366
340
  dbc += '/';
367
341
  dbc += info.database || '';
368
342
  dbc_hash = HashUtil.hashFromString(dbc);
369
- DataTextAgent.DBC.add(dbc_hash, dbc);
370
- DataTextAgent.METHOD.add(dbc_hash, dbc);
371
- DataTextAgent.ERROR.add(dbc_hash, dbc);
372
343
  }
373
344
 
374
- var dbc_step = null;
375
- if (ctx && !ctx.db_opening) {
345
+ if (ctx) {
376
346
  ctx.db_opening = true;
377
347
  ctx.footprint('MySql2 Connecting Start');
378
- dbc_step = new DBCStep();
379
- dbc_step.start_time = ctx.getElapsedTime();
380
- dbc_step.hash = dbc_hash;
348
+ ctx.start_time = Date.now();
381
349
  }
382
350
 
383
351
  var result = original.apply(this, args);
@@ -390,29 +358,31 @@ var createConnectionWrapper = function(isPromise = false) {
390
358
 
391
359
  // Connection의 query와 execute 메서드 후킹
392
360
  if (connection.query && !connection.query.__whatap_wrapped__) {
393
- shimmer.wrap(connection, 'query', createQueryWrapper(isPromise));
361
+ shimmer.wrap(connection, 'query', createQueryWrapper(isPromise, dbc, mod));
394
362
  connection.query.__whatap_wrapped__ = true;
395
363
  }
396
364
  if (connection.execute && !connection.execute.__whatap_wrapped__) {
397
- shimmer.wrap(connection, 'execute', createQueryWrapper(isPromise));
365
+ shimmer.wrap(connection, 'execute', createQueryWrapper(isPromise, dbc, mod));
398
366
  connection.execute.__whatap_wrapped__ = true;
399
367
  }
400
368
 
401
- // 에러 델리게이트 후킹 (원본 패턴)
402
- if (connection._protocol && connection._protocol._delegateError) {
369
+ // 에러 델리게이트 후킹
370
+ if (connection._protocol && connection._protocol._delegateError &&
371
+ !connection._protocol._delegateError.__whatap_wrapped__) {
403
372
  try {
404
- var originalDelegateError = connection._protocol._delegateError;
405
- connection._protocol._delegateError = function() {
406
- var args = Array.prototype.slice.call(arguments);
407
- try {
408
- // WHATAP 에러 처리
409
- handleProtocolError(args);
410
- } catch (e) {
411
- Logger.printError('WHATAP-PROTOCOL-ERROR', 'Error in protocol error handler', e, false);
412
- }
413
- // 원본 함수 실행
414
- return originalDelegateError.apply(this, args);
415
- };
373
+ shimmer.wrap(connection._protocol, '_delegateError', function(original) {
374
+ return function wrappedDelegateError() {
375
+ var args = Array.prototype.slice.call(arguments);
376
+ var ctx = TraceContextManager.getCurrentContext();
377
+
378
+ if (ctx && args[0]) {
379
+ handleProtocolError(ctx, args[0]);
380
+ }
381
+
382
+ return original.apply(this, args);
383
+ };
384
+ });
385
+ connection._protocol._delegateError.__whatap_wrapped__ = true;
416
386
  } catch (e) {
417
387
  Logger.printError('WHATAP-PROTOCOL-HOOK', 'Error hooking _delegateError', e, false);
418
388
  }
@@ -421,50 +391,30 @@ var createConnectionWrapper = function(isPromise = false) {
421
391
  return connection;
422
392
  }
423
393
 
424
- // 프로토콜 에러 처리 함수
425
- function handleProtocolError(args) {
426
- var ctx = TraceContextManager.getCurrentContext();
427
- if (ctx == null || !args[0]) {
428
- return;
429
- }
430
-
431
- var laststep = ctx.profile.getLastSteps(1);
432
- if (laststep == null || laststep.length === 0) {
433
- return;
434
- }
435
-
436
- var step = laststep[0];
437
- if (!args[0].fatal) {
438
- MeterSql.add(step.dbc, step.elapsed, true);
439
- StatSql.addSqlTime(ctx.service_hash, step.dbc, step.hash, step.elapsed, true, 0);
440
- }
394
+ function handleProtocolError(ctx, error) {
395
+ if (!error) return;
441
396
 
442
397
  try {
443
- var errorCode = args[0].code || 'UNKNOWN';
444
- var errorMessage = ctx.error_message = args[0].message || 'mysql2 error';
445
- ctx.error_class = args[0].name || args[0].constructor?.name || 'MySQLError';
446
-
447
- if (args[0].fatal) {
448
- step.error = StatError.addError('mysql2-' + errorCode, errorMessage, ctx.service_hash);
449
- if (ctx.error.isZero()) {
450
- ctx.error = step.error;
451
- }
452
- } else {
453
- var shouldAddError = false;
454
- if (conf._is_trace_ignore_err_cls_contains === true && errorCode.indexOf(conf.trace_ignore_err_cls_contains) < 0) {
455
- shouldAddError = true;
456
- } else if (conf._is_trace_ignore_err_msg_contains === true && errorMessage.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
457
- shouldAddError = true;
458
- } else if (conf._is_trace_ignore_err_cls_contains === false && conf._is_trace_ignore_err_msg_contains === false) {
459
- shouldAddError = true;
460
- }
398
+ var errorClass = error.code || error.name || error.constructor?.name || 'MySQLError';
399
+ var errorMessage = error.message || 'mysql2 error';
400
+
401
+ var shouldAddError = false;
402
+ if (conf._is_trace_ignore_err_cls_contains === true && errorClass.indexOf(conf.trace_ignore_err_cls_contains) < 0) {
403
+ shouldAddError = true;
404
+ } else if (conf._is_trace_ignore_err_msg_contains === true && errorMessage.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
405
+ shouldAddError = true;
406
+ } else if (conf._is_trace_ignore_err_cls_contains === false && conf._is_trace_ignore_err_msg_contains === false) {
407
+ shouldAddError = true;
408
+ }
461
409
 
462
- if (shouldAddError) {
463
- step.error = StatError.addError('mysql2-' + errorCode, errorMessage, ctx.service_hash, TextTypes.SQL, step.hash);
464
- if (ctx.error.isZero()) {
465
- ctx.error = step.error;
466
- }
410
+ if (shouldAddError) {
411
+ ctx.error = 1;
412
+ ctx.status = 500;
413
+ var errors = [errorClass];
414
+ if (errorMessage) {
415
+ errors.push(errorMessage);
467
416
  }
417
+ AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
468
418
  }
469
419
  } catch (e) {
470
420
  Logger.printError('WHATAP-PROTOCOL-ERROR-HANDLING', 'Error processing protocol error', e, false);
@@ -473,37 +423,41 @@ var createConnectionWrapper = function(isPromise = false) {
473
423
 
474
424
  // Promise 기반 연결 처리
475
425
  if (isPromise && result && typeof result.then === 'function') {
476
- return result.then(function(connection) {
477
- hookConnection(connection);
478
-
479
- if (ctx && dbc_step) {
480
- ctx.footprint('MySql2 Connecting Done');
481
- ctx.db_opening = false;
482
- dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
483
- ctx.profile.push(dbc_step);
484
- }
485
- return connection;
486
- }).catch(function(error) {
487
- if (ctx && dbc_step) {
488
- ctx.footprint('MySql2 Connecting Error');
489
- ctx.db_opening = false;
490
- dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
491
- ctx.profile.push(dbc_step);
492
- }
493
- throw error;
494
- });
495
- }
496
- // 동기/콜백 기반 연결 처리
497
- else {
426
+ if (asyncResource) {
427
+ return result.then(asyncResource.bind(function(connection) {
428
+ hookConnection(connection);
429
+
430
+ if (ctx) {
431
+ ctx.elapsed = Date.now() - ctx.start_time;
432
+ ctx.footprint('MySql2 Connecting Done');
433
+ ctx.db_opening = false;
434
+ }
435
+ return connection;
436
+ })).catch(asyncResource.bind(function(error) {
437
+ if (ctx) {
438
+ ctx.elapsed = Date.now() - ctx.start_time;
439
+ ctx.footprint('MySql2 Connecting Error');
440
+ ctx.db_opening = false;
441
+ handleSqlError(ctx, error, 0);
442
+ }
443
+ throw error;
444
+ }));
445
+ } else {
446
+ return result.then(function(connection) {
447
+ hookConnection(connection);
448
+ return connection;
449
+ });
450
+ }
451
+ } else {
452
+ // 동기/콜백 기반 연결 처리
498
453
  if (result && typeof result === 'object') {
499
454
  hookConnection(result);
500
455
  }
501
456
 
502
- if (ctx && dbc_step) {
457
+ if (ctx) {
458
+ ctx.elapsed = Date.now() - ctx.start_time;
503
459
  ctx.footprint('MySql2 Connecting Done');
504
460
  ctx.db_opening = false;
505
- dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
506
- ctx.profile.push(dbc_step);
507
461
  }
508
462
  }
509
463
 
@@ -512,10 +466,7 @@ var createConnectionWrapper = function(isPromise = false) {
512
466
  };
513
467
  };
514
468
 
515
- /**
516
- * 개선된 Pool 래퍼 - 원본 패턴 적용
517
- */
518
- var createPoolWrapper = function(isPromise = false) {
469
+ var createPoolWrapper = function(isPromise = false, mod) {
519
470
  return function(original) {
520
471
  return function wrappedCreatePool() {
521
472
  var args = Array.prototype.slice.call(arguments);
@@ -530,28 +481,22 @@ var createPoolWrapper = function(isPromise = false) {
530
481
  dbc += '/';
531
482
  dbc += info.database || '';
532
483
  dbc_hash = HashUtil.hashFromString(dbc);
533
- DataTextAgent.DBC.add(dbc_hash, dbc);
534
- DataTextAgent.METHOD.add(dbc_hash, dbc);
535
- DataTextAgent.ERROR.add(dbc_hash, dbc);
536
484
  }
537
485
 
538
486
  var pool = original.apply(this, args);
539
487
 
540
- // Pool 객체 후킹
541
488
  if (pool && !hookedInstances.has(pool)) {
542
489
  hookedInstances.add(pool);
543
490
 
544
- // Pool의 query와 execute 메서드 후킹
545
491
  if (pool.query && !pool.query.__whatap_wrapped__) {
546
- shimmer.wrap(pool, 'query', createQueryWrapper(isPromise));
492
+ shimmer.wrap(pool, 'query', createQueryWrapper(isPromise, dbc, mod));
547
493
  pool.query.__whatap_wrapped__ = true;
548
494
  }
549
495
  if (pool.execute && !pool.execute.__whatap_wrapped__) {
550
- shimmer.wrap(pool, 'execute', createQueryWrapper(isPromise));
496
+ shimmer.wrap(pool, 'execute', createQueryWrapper(isPromise, dbc, mod));
551
497
  pool.execute.__whatap_wrapped__ = true;
552
498
  }
553
499
 
554
- // getConnection 메서드 후킹 - 원본 패턴 적용
555
500
  if (pool.getConnection && !pool.getConnection.__whatap_wrapped__) {
556
501
  shimmer.wrap(pool, 'getConnection', function(original) {
557
502
  return function wrappedGetConnection() {
@@ -562,42 +507,76 @@ var createPoolWrapper = function(isPromise = false) {
562
507
  return original.apply(this, args);
563
508
  }
564
509
 
565
- // 원본 패턴: functionHook을 사용하여 콜백 후킹
566
- hookLastFunction(args, function(obj, callbackArgs) {
567
- TraceContextManager.resume(ctx._id);
568
- DataTextAgent.DBC.add(dbc_hash, dbc);
569
-
570
- if (callbackArgs[0] != null) {
571
- return; // 에러가 있으면 처리하지 않음
510
+ var asyncResource = new AsyncResource('mysql2-get-connection');
511
+ ctx.start_time = Date.now();
512
+
513
+ for (var i = args.length - 1; i >= 0; i--) {
514
+ if (typeof args[i] === 'function') {
515
+ var originalCallback = args[i];
516
+ // AsyncResource로 바인딩된 콜백 생성
517
+ args[i] = asyncResource.bind(function() {
518
+ var callbackArgs = Array.prototype.slice.call(arguments);
519
+ var err = callbackArgs[0];
520
+ var conn = callbackArgs[1];
521
+
522
+ var currentCtx = TraceContextManager.getCurrentContext();
523
+ if (!currentCtx) {
524
+ TraceContextManager.resume(ctx.id);
525
+ currentCtx = ctx;
526
+ }
527
+
528
+ if (currentCtx) {
529
+ currentCtx.elapsed = Date.now() - ctx.start_time;
530
+ if (!err && conn && !conn.__query_hook__) {
531
+ conn.__query_hook__ = true;
532
+
533
+ // Connection 객체의 메서드들 후킹
534
+ if (conn.query && !conn.query.__whatap_wrapped__) {
535
+ shimmer.wrap(conn, 'query', createQueryWrapper(isPromise, dbc, mod));
536
+ conn.query.__whatap_wrapped__ = true;
537
+ }
538
+ if (conn.execute && !conn.execute.__whatap_wrapped__) {
539
+ shimmer.wrap(conn, 'execute', createQueryWrapper(isPromise, dbc, mod));
540
+ conn.execute.__whatap_wrapped__ = true;
541
+ }
542
+ }
543
+ }
544
+
545
+ return originalCallback.apply(this, callbackArgs);
546
+ });
547
+ break;
572
548
  }
549
+ }
573
550
 
574
- var conn = callbackArgs[1];
575
- if (conn && !conn.__query_hook__) {
576
- conn.__query_hook__ = true;
551
+ var result = original.apply(this, args);
577
552
 
578
- // Connection 객체의 메서드들 후킹
579
- if (conn.query && !conn.query.__whatap_wrapped__) {
580
- shimmer.wrap(conn, 'query', createQueryWrapper(isPromise));
581
- conn.query.__whatap_wrapped__ = true;
582
- }
583
- if (conn.execute && !conn.execute.__whatap_wrapped__) {
584
- shimmer.wrap(conn, 'execute', createQueryWrapper(isPromise));
585
- conn.execute.__whatap_wrapped__ = true;
553
+ // Promise 기반 처리
554
+ if (result && typeof result.then === 'function' && args.every(arg => typeof arg !== 'function')) {
555
+ return result.then(asyncResource.bind(function(connection) {
556
+ var currentCtx = TraceContextManager.getCurrentContext();
557
+ if (!currentCtx) {
558
+ TraceContextManager.resume(ctx.id);
559
+ currentCtx = ctx;
586
560
  }
587
- }
588
- });
589
561
 
590
- var result = original.apply(this, args);
562
+ if (currentCtx) {
563
+ currentCtx.elapsed = Date.now() - ctx.start_time;
564
+ if (connection && !connection.__query_hook__) {
565
+ connection.__query_hook__ = true;
566
+
567
+ if (connection.query && !connection.query.__whatap_wrapped__) {
568
+ shimmer.wrap(connection, 'query', createQueryWrapper(isPromise, dbc, mod));
569
+ connection.query.__whatap_wrapped__ = true;
570
+ }
571
+ if (connection.execute && !connection.execute.__whatap_wrapped__) {
572
+ shimmer.wrap(connection, 'execute', createQueryWrapper(isPromise, dbc, mod));
573
+ connection.execute.__whatap_wrapped__ = true;
574
+ }
575
+ }
576
+ }
591
577
 
592
- // 연결 통계 업데이트 (원본 패턴)
593
- try {
594
- if (this._allConnections != undefined) {
595
- var all = this._allConnections.length;
596
- var idle = this._freeConnections.length;
597
- MeterSql.setConnection((all - idle), idle, dbc);
598
- }
599
- } catch (e) {
600
- Logger.printError('WHATAP-CONNECTION-STATS', 'Error updating connection stats', e, false);
578
+ return connection;
579
+ }));
601
580
  }
602
581
 
603
582
  return result;
@@ -612,50 +591,6 @@ var createPoolWrapper = function(isPromise = false) {
612
591
  };
613
592
  };
614
593
 
615
- var toParamBytes = function (p, crc) {
616
- if (p == null || p.length === 0) {
617
- return null;
618
- }
619
- try {
620
- return ParamSecurity.encrypt(Buffer.from(p, 'utf8'), crc);
621
- } catch (e) {
622
- return null;
623
- }
624
- };
625
-
626
- Mysql2Observer.prototype.inject = function (mod, moduleName) {
627
- if (mod.__whatap_observe__) {
628
- return;
629
- }
630
- mod.__whatap_observe__ = true;
631
- Logger.initPrint("Mysql2Observer");
632
-
633
- if (conf.sql_enabled === false) {
634
- return;
635
- }
636
-
637
- // mysql2/promise인지 일반 mysql2인지 구분
638
- var isPromise = moduleName && moduleName.includes('promise');
639
-
640
- Logger.print('WHATAP-MYSQL2-INJECT', 'Injecting MySQL2 module: ' + moduleName + ', Promise mode: ' + isPromise, false);
641
-
642
- // 전역 함수 추적으로 중복 후킹 방지
643
- if (mod.createConnection && !globalHookedFunctions.has(mod.createConnection)) {
644
- globalHookedFunctions.add(mod.createConnection);
645
- shimmer.wrap(mod, 'createConnection', createConnectionWrapper(isPromise));
646
- }
647
-
648
- if (mod.createPool && !globalHookedFunctions.has(mod.createPool)) {
649
- globalHookedFunctions.add(mod.createPool);
650
- shimmer.wrap(mod, 'createPool', createPoolWrapper(isPromise));
651
- }
652
-
653
- if (mod.createPoolCluster && !globalHookedFunctions.has(mod.createPoolCluster)) {
654
- globalHookedFunctions.add(mod.createPoolCluster);
655
- shimmer.wrap(mod, 'createPoolCluster', createPoolWrapper(isPromise));
656
- }
657
- };
658
-
659
594
  var checkedSql = new IntKeyMap(2000).setMax(2000);
660
595
  var nonLiteSql = new IntKeyMap(5000).setMax(5000);
661
596
  var date = DateUtil.yyyymmdd();
@@ -688,8 +623,6 @@ function escapeLiteral(sql) {
688
623
  els.process();
689
624
 
690
625
  var hash = HashUtil.hashFromString(els.getParsedSql());
691
- DataTextAgent.SQL.add(hash, els.getParsedSql());
692
-
693
626
  if (hash === sqlHash) {
694
627
  psql = new ParsedSql(els.sqlType, hash, null);
695
628
  nonLiteSql.put(sqlHash, psql);
@@ -700,4 +633,33 @@ function escapeLiteral(sql) {
700
633
  return psql;
701
634
  }
702
635
 
636
+ Mysql2Observer.prototype.inject = function (mod, moduleName) {
637
+ if (mod.__whatap_observe__) {
638
+ return;
639
+ }
640
+ mod.__whatap_observe__ = true;
641
+ Logger.initPrint("Mysql2Observer");
642
+
643
+ if (conf.sql_enabled === false) {
644
+ return;
645
+ }
646
+
647
+ var isPromise = moduleName && moduleName.includes('promise');
648
+
649
+ if (mod.createConnection && !mod.createConnection.__whatap_wrapped__) {
650
+ shimmer.wrap(mod, 'createConnection', createConnectionWrapper(isPromise, mod));
651
+ mod.createConnection.__whatap_wrapped__ = true;
652
+ }
653
+
654
+ if (mod.createPool && !mod.createPool.__whatap_wrapped__) {
655
+ shimmer.wrap(mod, 'createPool', createPoolWrapper(isPromise, mod));
656
+ mod.createPool.__whatap_wrapped__ = true;
657
+ }
658
+
659
+ if (mod.createPoolCluster && !mod.createPoolCluster.__whatap_wrapped__) {
660
+ shimmer.wrap(mod, 'createPoolCluster', createPoolWrapper(isPromise, mod));
661
+ mod.createPoolCluster.__whatap_wrapped__ = true;
662
+ }
663
+ };
664
+
703
665
  exports.Mysql2Observer = Mysql2Observer;