whatap 1.0.1 → 1.0.2

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