whatap 0.5.26 → 1.0.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 (196) hide show
  1. package/agent/darwin/arm64/whatap_nodejs +0 -0
  2. package/agent/linux/amd64/whatap_nodejs +0 -0
  3. package/agent/linux/arm64/whatap_nodejs +0 -0
  4. package/build.txt +4 -0
  5. package/lib/conf/config-default.js +3 -10
  6. package/lib/conf/configure.js +349 -369
  7. package/lib/conf/license.js +1 -1
  8. package/lib/control/packagectr-helper.js +3 -34
  9. package/lib/core/agent.js +882 -176
  10. package/lib/core/interceptor.js +6 -6
  11. package/lib/core/shimmer.js +36 -82
  12. package/lib/counter/counter-manager.js +8 -79
  13. package/lib/counter/task/activetransaction.js +17 -68
  14. package/lib/io/data-inputx.js +3 -13
  15. package/lib/io/data-outputx.js +206 -268
  16. package/lib/logger.js +6 -6
  17. package/lib/net/security-master.js +20 -139
  18. package/lib/observers/apollo-server-observer.js +27 -33
  19. package/lib/observers/global-observer.js +80 -155
  20. package/lib/observers/http-observer.js +236 -666
  21. package/lib/observers/ioredis-observer.js +294 -0
  22. package/lib/observers/maria-observer.js +362 -204
  23. package/lib/observers/mongodb-observer.js +226 -169
  24. package/lib/observers/mongoose-observer.js +323 -518
  25. package/lib/observers/mssql-observer.js +418 -177
  26. package/lib/observers/mysql-observer.js +449 -342
  27. package/lib/observers/mysql2-observer.js +358 -396
  28. package/lib/observers/oracle-observer.js +384 -559
  29. package/lib/observers/pgsql-observer.js +489 -231
  30. package/lib/observers/prisma-observer.js +92 -303
  31. package/lib/observers/process-observer.js +35 -79
  32. package/lib/observers/redis-observer.js +331 -166
  33. package/lib/observers/socket.io-observer.js +187 -226
  34. package/lib/observers/websocket-observer.js +301 -175
  35. package/lib/pack/counter-pack.js +0 -3
  36. package/lib/pack/log-sink-pack.js +52 -14
  37. package/lib/pack/tagcount-pack.js +4 -4
  38. package/lib/{counter/task → system}/gc-action.js +74 -27
  39. package/lib/trace/trace-context-manager.js +25 -113
  40. package/lib/trace/trace-context.js +7 -21
  41. package/lib/trace/trace-httpc.js +11 -17
  42. package/lib/trace/trace-sql.js +21 -29
  43. package/lib/udp/async_sender.js +119 -0
  44. package/lib/udp/index.js +17 -0
  45. package/lib/udp/packet_enum.js +52 -0
  46. package/lib/udp/packet_queue.js +69 -0
  47. package/lib/udp/packet_type_enum.js +33 -0
  48. package/lib/udp/param_def.js +72 -0
  49. package/lib/udp/udp_session.js +336 -0
  50. package/lib/util/escape-literal-sql.js +5 -5
  51. package/lib/util/hashutil.js +18 -18
  52. package/lib/util/keygen.js +3 -0
  53. package/lib/util/linkedset.js +2 -1
  54. package/lib/util/nodeutil.js +1 -2
  55. package/lib/util/sql-util.js +178 -0
  56. package/lib/util/trace-helper.js +91 -0
  57. package/lib/util/transfer.js +58 -0
  58. package/lib/value/map-value.js +2 -3
  59. package/package.json +5 -10
  60. package/lib/conf/conf-sys-mon.js +0 -101
  61. package/lib/control/cmd-config.js +0 -24
  62. package/lib/control/control-handler.js +0 -367
  63. package/lib/core/request-agent.js +0 -27
  64. package/lib/counter/meter/meter-activex.js +0 -67
  65. package/lib/counter/meter/meter-httpc.js +0 -57
  66. package/lib/counter/meter/meter-resource.js +0 -9
  67. package/lib/counter/meter/meter-service.js +0 -168
  68. package/lib/counter/meter/meter-socket.io.js +0 -51
  69. package/lib/counter/meter/meter-sql.js +0 -71
  70. package/lib/counter/meter/meter-users.js +0 -58
  71. package/lib/counter/meter.js +0 -183
  72. package/lib/counter/task/agentinfo.js +0 -107
  73. package/lib/counter/task/gcstat.js +0 -34
  74. package/lib/counter/task/heapmem.js +0 -25
  75. package/lib/counter/task/httpc.js +0 -76
  76. package/lib/counter/task/metering-info.js +0 -125
  77. package/lib/counter/task/proc-cpu.js +0 -29
  78. package/lib/counter/task/realtimeuser.js +0 -31
  79. package/lib/counter/task/res/systemECSTask.js +0 -39
  80. package/lib/counter/task/res/systemKubeTask.js +0 -53
  81. package/lib/counter/task/res/util/awsEcsClientThread.js +0 -218
  82. package/lib/counter/task/res/util/linuxProcStatUtil.js +0 -14
  83. package/lib/counter/task/res-sys-cpu.js +0 -62
  84. package/lib/counter/task/service.js +0 -202
  85. package/lib/counter/task/socketio.js +0 -30
  86. package/lib/counter/task/sql.js +0 -105
  87. package/lib/counter/task/systemperf.js +0 -43
  88. package/lib/data/datapack-sender.js +0 -289
  89. package/lib/data/dataprofile-agent.js +0 -162
  90. package/lib/data/datatext-agent.js +0 -135
  91. package/lib/data/event-level.js +0 -15
  92. package/lib/data/test.js +0 -49
  93. package/lib/data/zipprofile.js +0 -197
  94. package/lib/env/constants.js +0 -21
  95. package/lib/error/error-handler.js +0 -437
  96. package/lib/kube/kube-client.js +0 -144
  97. package/lib/lang/text-types.js +0 -58
  98. package/lib/logsink/line-log-util.js +0 -87
  99. package/lib/logsink/line-log.js +0 -12
  100. package/lib/logsink/log-sender.js +0 -78
  101. package/lib/logsink/log-tracer.js +0 -40
  102. package/lib/logsink/sender-util.js +0 -56
  103. package/lib/logsink/zip/zip-send.js +0 -177
  104. package/lib/net/netflag.js +0 -55
  105. package/lib/net/receiver.js +0 -66
  106. package/lib/net/sender.js +0 -141
  107. package/lib/net/tcp-return.js +0 -18
  108. package/lib/net/tcp-session.js +0 -286
  109. package/lib/net/tcpreq-client-proxy.js +0 -70
  110. package/lib/net/tcprequest-mgr.js +0 -58
  111. package/lib/observers/cluster-observer.js +0 -22
  112. package/lib/observers/express-observer.js +0 -215
  113. package/lib/observers/file-observer.js +0 -184
  114. package/lib/observers/grpc-observer.js +0 -336
  115. package/lib/observers/memcached-observer.js +0 -56
  116. package/lib/observers/mongo-observer.js +0 -317
  117. package/lib/observers/net-observer.js +0 -77
  118. package/lib/observers/promise-observer.js +0 -31
  119. package/lib/observers/schedule-observer.js +0 -67
  120. package/lib/observers/stream-observer.js +0 -19
  121. package/lib/observers/thrift-observer.js +0 -197
  122. package/lib/pack/activestack-pack.js +0 -55
  123. package/lib/pack/apenum.js +0 -8
  124. package/lib/pack/errorsnap-pack.js +0 -69
  125. package/lib/pack/event-pack.js +0 -54
  126. package/lib/pack/hitmap-pack.js +0 -63
  127. package/lib/pack/hitmap-pack1.js +0 -152
  128. package/lib/pack/netstat.js +0 -15
  129. package/lib/pack/otype.js +0 -7
  130. package/lib/pack/profile-pack.js +0 -49
  131. package/lib/pack/realtimeuser-pack.js +0 -41
  132. package/lib/pack/stat-general-pack.js +0 -96
  133. package/lib/pack/staterror-pack.js +0 -120
  134. package/lib/pack/stathttpc-pack.js +0 -66
  135. package/lib/pack/stathttpc-rec.js +0 -78
  136. package/lib/pack/statremote-pack.js +0 -46
  137. package/lib/pack/statservice-pack.js +0 -63
  138. package/lib/pack/statservice-pack1.js +0 -88
  139. package/lib/pack/statservice-rec.js +0 -292
  140. package/lib/pack/statservice-rec_dep.js +0 -151
  141. package/lib/pack/statsql-pack.js +0 -69
  142. package/lib/pack/statsql-rec.js +0 -100
  143. package/lib/pack/statuseragent-pack.js +0 -44
  144. package/lib/pack/tagctr.js +0 -15
  145. package/lib/pack/text-pack.js +0 -50
  146. package/lib/pack/time-count.js +0 -25
  147. package/lib/pack/websocket.js +0 -15
  148. package/lib/pack/zip-pack.js +0 -70
  149. package/lib/pii/pii-item.js +0 -31
  150. package/lib/pii/pii-mask.js +0 -174
  151. package/lib/plugin/plugin-loadermanager.js +0 -57
  152. package/lib/plugin/plugin.js +0 -75
  153. package/lib/service/tx-record.js +0 -332
  154. package/lib/stat/stat-error.js +0 -116
  155. package/lib/stat/stat-httpc.js +0 -98
  156. package/lib/stat/stat-remote-ip.js +0 -46
  157. package/lib/stat/stat-remote-ipurl.js +0 -88
  158. package/lib/stat/stat-sql.js +0 -113
  159. package/lib/stat/stat-tranx.js +0 -58
  160. package/lib/stat/stat-tx-caller.js +0 -160
  161. package/lib/stat/stat-tx-domain.js +0 -111
  162. package/lib/stat/stat-tx-referer.js +0 -112
  163. package/lib/stat/stat-useragent.js +0 -48
  164. package/lib/stat/timingsender.js +0 -76
  165. package/lib/step/activestack-step.js +0 -38
  166. package/lib/step/dbc-step.js +0 -36
  167. package/lib/step/http-stepx.js +0 -67
  168. package/lib/step/message-step.js +0 -40
  169. package/lib/step/method-stepx.js +0 -45
  170. package/lib/step/resultset-step.js +0 -40
  171. package/lib/step/securemsg-step.js +0 -44
  172. package/lib/step/socket-step.js +0 -46
  173. package/lib/step/sql-stepx.js +0 -68
  174. package/lib/step/sqlxtype.js +0 -16
  175. package/lib/step/step.js +0 -66
  176. package/lib/step/stepenum.js +0 -54
  177. package/lib/topology/link.js +0 -63
  178. package/lib/topology/nodeinfo.js +0 -123
  179. package/lib/topology/status-detector.js +0 -111
  180. package/lib/util/anylist.js +0 -103
  181. package/lib/util/cardinality/hyperloglog.js +0 -106
  182. package/lib/util/cardinality/murmurhash.js +0 -31
  183. package/lib/util/cardinality/registerset.js +0 -75
  184. package/lib/util/errordata.js +0 -21
  185. package/lib/util/iputil_x.js +0 -527
  186. package/lib/util/kube-util.js +0 -73
  187. package/lib/util/paramsecurity.js +0 -80
  188. package/lib/util/pre-process.js +0 -13
  189. package/lib/util/process-seq.js +0 -166
  190. package/lib/util/property-util.js +0 -36
  191. package/lib/util/request-queue.js +0 -70
  192. package/lib/util/requestdouble-queue.js +0 -72
  193. package/lib/util/resourceprofile.js +0 -157
  194. package/lib/util/stop-watch.js +0 -30
  195. package/lib/util/system-util.js +0 -10
  196. 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;