whatap 0.5.26 → 1.0.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +78 -32
  2. package/agent/darwin/arm64/whatap_nodejs +0 -0
  3. package/agent/linux/amd64/whatap_nodejs +0 -0
  4. package/agent/linux/arm64/whatap_nodejs +0 -0
  5. package/build.txt +4 -0
  6. package/lib/conf/config-default.js +3 -10
  7. package/lib/conf/configure.js +349 -369
  8. package/lib/conf/license.js +1 -1
  9. package/lib/control/packagectr-helper.js +3 -34
  10. package/lib/core/agent.js +882 -176
  11. package/lib/core/interceptor.js +6 -6
  12. package/lib/core/shimmer.js +36 -82
  13. package/lib/counter/counter-manager.js +8 -79
  14. package/lib/counter/task/activetransaction.js +17 -68
  15. package/lib/io/data-inputx.js +3 -13
  16. package/lib/io/data-outputx.js +206 -268
  17. package/lib/logger.js +6 -6
  18. package/lib/net/security-master.js +20 -139
  19. package/lib/observers/apollo-server-observer.js +27 -33
  20. package/lib/observers/global-observer.js +80 -155
  21. package/lib/observers/http-observer.js +236 -666
  22. package/lib/observers/ioredis-observer.js +294 -0
  23. package/lib/observers/maria-observer.js +362 -204
  24. package/lib/observers/mongodb-observer.js +226 -169
  25. package/lib/observers/mongoose-observer.js +323 -518
  26. package/lib/observers/mssql-observer.js +418 -177
  27. package/lib/observers/mysql-observer.js +449 -342
  28. package/lib/observers/mysql2-observer.js +358 -396
  29. package/lib/observers/oracle-observer.js +384 -559
  30. package/lib/observers/pgsql-observer.js +489 -231
  31. package/lib/observers/prisma-observer.js +92 -303
  32. package/lib/observers/process-observer.js +35 -79
  33. package/lib/observers/redis-observer.js +331 -166
  34. package/lib/observers/socket.io-observer.js +187 -226
  35. package/lib/observers/websocket-observer.js +301 -175
  36. package/lib/pack/counter-pack.js +0 -3
  37. package/lib/pack/log-sink-pack.js +52 -14
  38. package/lib/pack/tagcount-pack.js +4 -4
  39. package/lib/{counter/task → system}/gc-action.js +74 -27
  40. package/lib/trace/trace-context-manager.js +25 -113
  41. package/lib/trace/trace-context.js +7 -21
  42. package/lib/trace/trace-httpc.js +11 -17
  43. package/lib/trace/trace-sql.js +21 -29
  44. package/lib/udp/async_sender.js +119 -0
  45. package/lib/udp/index.js +17 -0
  46. package/lib/udp/packet_enum.js +52 -0
  47. package/lib/udp/packet_queue.js +69 -0
  48. package/lib/udp/packet_type_enum.js +33 -0
  49. package/lib/udp/param_def.js +72 -0
  50. package/lib/udp/udp_session.js +336 -0
  51. package/lib/util/escape-literal-sql.js +5 -5
  52. package/lib/util/hashutil.js +18 -18
  53. package/lib/util/keygen.js +3 -0
  54. package/lib/util/linkedset.js +2 -1
  55. package/lib/util/nodeutil.js +1 -2
  56. package/lib/util/sql-util.js +178 -0
  57. package/lib/util/trace-helper.js +91 -0
  58. package/lib/util/transfer.js +58 -0
  59. package/lib/value/map-value.js +2 -3
  60. package/package.json +5 -10
  61. package/whatap.conf +4 -1
  62. package/lib/conf/conf-sys-mon.js +0 -101
  63. package/lib/control/cmd-config.js +0 -24
  64. package/lib/control/control-handler.js +0 -367
  65. package/lib/core/request-agent.js +0 -27
  66. package/lib/counter/meter/meter-activex.js +0 -67
  67. package/lib/counter/meter/meter-httpc.js +0 -57
  68. package/lib/counter/meter/meter-resource.js +0 -9
  69. package/lib/counter/meter/meter-service.js +0 -168
  70. package/lib/counter/meter/meter-socket.io.js +0 -51
  71. package/lib/counter/meter/meter-sql.js +0 -71
  72. package/lib/counter/meter/meter-users.js +0 -58
  73. package/lib/counter/meter.js +0 -183
  74. package/lib/counter/task/agentinfo.js +0 -107
  75. package/lib/counter/task/gcstat.js +0 -34
  76. package/lib/counter/task/heapmem.js +0 -25
  77. package/lib/counter/task/httpc.js +0 -76
  78. package/lib/counter/task/metering-info.js +0 -125
  79. package/lib/counter/task/proc-cpu.js +0 -29
  80. package/lib/counter/task/realtimeuser.js +0 -31
  81. package/lib/counter/task/res/systemECSTask.js +0 -39
  82. package/lib/counter/task/res/systemKubeTask.js +0 -53
  83. package/lib/counter/task/res/util/awsEcsClientThread.js +0 -218
  84. package/lib/counter/task/res/util/linuxProcStatUtil.js +0 -14
  85. package/lib/counter/task/res-sys-cpu.js +0 -62
  86. package/lib/counter/task/service.js +0 -202
  87. package/lib/counter/task/socketio.js +0 -30
  88. package/lib/counter/task/sql.js +0 -105
  89. package/lib/counter/task/systemperf.js +0 -43
  90. package/lib/data/datapack-sender.js +0 -289
  91. package/lib/data/dataprofile-agent.js +0 -162
  92. package/lib/data/datatext-agent.js +0 -135
  93. package/lib/data/event-level.js +0 -15
  94. package/lib/data/test.js +0 -49
  95. package/lib/data/zipprofile.js +0 -197
  96. package/lib/env/constants.js +0 -21
  97. package/lib/error/error-handler.js +0 -437
  98. package/lib/kube/kube-client.js +0 -144
  99. package/lib/lang/text-types.js +0 -58
  100. package/lib/logsink/line-log-util.js +0 -87
  101. package/lib/logsink/line-log.js +0 -12
  102. package/lib/logsink/log-sender.js +0 -78
  103. package/lib/logsink/log-tracer.js +0 -40
  104. package/lib/logsink/sender-util.js +0 -56
  105. package/lib/logsink/zip/zip-send.js +0 -177
  106. package/lib/net/netflag.js +0 -55
  107. package/lib/net/receiver.js +0 -66
  108. package/lib/net/sender.js +0 -141
  109. package/lib/net/tcp-return.js +0 -18
  110. package/lib/net/tcp-session.js +0 -286
  111. package/lib/net/tcpreq-client-proxy.js +0 -70
  112. package/lib/net/tcprequest-mgr.js +0 -58
  113. package/lib/observers/cluster-observer.js +0 -22
  114. package/lib/observers/express-observer.js +0 -215
  115. package/lib/observers/file-observer.js +0 -184
  116. package/lib/observers/grpc-observer.js +0 -336
  117. package/lib/observers/memcached-observer.js +0 -56
  118. package/lib/observers/mongo-observer.js +0 -317
  119. package/lib/observers/net-observer.js +0 -77
  120. package/lib/observers/promise-observer.js +0 -31
  121. package/lib/observers/schedule-observer.js +0 -67
  122. package/lib/observers/stream-observer.js +0 -19
  123. package/lib/observers/thrift-observer.js +0 -197
  124. package/lib/pack/activestack-pack.js +0 -55
  125. package/lib/pack/apenum.js +0 -8
  126. package/lib/pack/errorsnap-pack.js +0 -69
  127. package/lib/pack/event-pack.js +0 -54
  128. package/lib/pack/hitmap-pack.js +0 -63
  129. package/lib/pack/hitmap-pack1.js +0 -152
  130. package/lib/pack/netstat.js +0 -15
  131. package/lib/pack/otype.js +0 -7
  132. package/lib/pack/profile-pack.js +0 -49
  133. package/lib/pack/realtimeuser-pack.js +0 -41
  134. package/lib/pack/stat-general-pack.js +0 -96
  135. package/lib/pack/staterror-pack.js +0 -120
  136. package/lib/pack/stathttpc-pack.js +0 -66
  137. package/lib/pack/stathttpc-rec.js +0 -78
  138. package/lib/pack/statremote-pack.js +0 -46
  139. package/lib/pack/statservice-pack.js +0 -63
  140. package/lib/pack/statservice-pack1.js +0 -88
  141. package/lib/pack/statservice-rec.js +0 -292
  142. package/lib/pack/statservice-rec_dep.js +0 -151
  143. package/lib/pack/statsql-pack.js +0 -69
  144. package/lib/pack/statsql-rec.js +0 -100
  145. package/lib/pack/statuseragent-pack.js +0 -44
  146. package/lib/pack/tagctr.js +0 -15
  147. package/lib/pack/text-pack.js +0 -50
  148. package/lib/pack/time-count.js +0 -25
  149. package/lib/pack/websocket.js +0 -15
  150. package/lib/pack/zip-pack.js +0 -70
  151. package/lib/pii/pii-item.js +0 -31
  152. package/lib/pii/pii-mask.js +0 -174
  153. package/lib/plugin/plugin-loadermanager.js +0 -57
  154. package/lib/plugin/plugin.js +0 -75
  155. package/lib/service/tx-record.js +0 -332
  156. package/lib/stat/stat-error.js +0 -116
  157. package/lib/stat/stat-httpc.js +0 -98
  158. package/lib/stat/stat-remote-ip.js +0 -46
  159. package/lib/stat/stat-remote-ipurl.js +0 -88
  160. package/lib/stat/stat-sql.js +0 -113
  161. package/lib/stat/stat-tranx.js +0 -58
  162. package/lib/stat/stat-tx-caller.js +0 -160
  163. package/lib/stat/stat-tx-domain.js +0 -111
  164. package/lib/stat/stat-tx-referer.js +0 -112
  165. package/lib/stat/stat-useragent.js +0 -48
  166. package/lib/stat/timingsender.js +0 -76
  167. package/lib/step/activestack-step.js +0 -38
  168. package/lib/step/dbc-step.js +0 -36
  169. package/lib/step/http-stepx.js +0 -67
  170. package/lib/step/message-step.js +0 -40
  171. package/lib/step/method-stepx.js +0 -45
  172. package/lib/step/resultset-step.js +0 -40
  173. package/lib/step/securemsg-step.js +0 -44
  174. package/lib/step/socket-step.js +0 -46
  175. package/lib/step/sql-stepx.js +0 -68
  176. package/lib/step/sqlxtype.js +0 -16
  177. package/lib/step/step.js +0 -66
  178. package/lib/step/stepenum.js +0 -54
  179. package/lib/topology/link.js +0 -63
  180. package/lib/topology/nodeinfo.js +0 -123
  181. package/lib/topology/status-detector.js +0 -111
  182. package/lib/util/anylist.js +0 -103
  183. package/lib/util/cardinality/hyperloglog.js +0 -106
  184. package/lib/util/cardinality/murmurhash.js +0 -31
  185. package/lib/util/cardinality/registerset.js +0 -75
  186. package/lib/util/errordata.js +0 -21
  187. package/lib/util/iputil_x.js +0 -527
  188. package/lib/util/kube-util.js +0 -73
  189. package/lib/util/paramsecurity.js +0 -80
  190. package/lib/util/pre-process.js +0 -13
  191. package/lib/util/process-seq.js +0 -166
  192. package/lib/util/property-util.js +0 -36
  193. package/lib/util/request-queue.js +0 -70
  194. package/lib/util/requestdouble-queue.js +0 -72
  195. package/lib/util/resourceprofile.js +0 -157
  196. package/lib/util/stop-watch.js +0 -30
  197. package/lib/util/system-util.js +0 -10
  198. package/lib/util/userid-util.js +0 -57
@@ -1,17 +1,18 @@
1
1
  var TraceContextManager = require('../trace/trace-context-manager'),
2
- DataTextAgent = require('../data/datatext-agent'),
3
- SqlStepX = require('../step/sql-stepx'),
4
- DBCStep = require('../step/dbc-step'),
5
2
  HashUtil = require('../util/hashutil'),
3
+ Long = require('long'),
6
4
  Logger = require('../logger'),
7
5
  conf = require('../conf/configure'),
8
- ParamSecurity = require('../util/paramsecurity');
9
- const shimmer = require('../core/shimmer');
10
- const {Buffer} = require("buffer");
6
+ DateUtil = require('../util/dateutil'),
7
+ AsyncSender = require('../udp/async_sender'),
8
+ PacketTypeEnum = require('../udp/packet_type_enum'),
9
+ shimmer = require('../core/shimmer');
10
+ const { AsyncResource } = require('async_hooks');
11
11
 
12
12
  var RedisObserver = function (agent) {
13
13
  this.agent = agent;
14
- this.packages = ['redis', 'ioredis'];
14
+ this.aop = agent.aop;
15
+ this.packages = ['redis'];
15
16
  };
16
17
 
17
18
  RedisObserver.prototype.inject = function (mod, modName) {
@@ -21,197 +22,361 @@ RedisObserver.prototype.inject = function (mod, modName) {
21
22
  mod.__whatap_observe__ = true;
22
23
  Logger.initPrint("RedisObserver");
23
24
 
24
- if (modName === 'redis') {
25
- this._injectRedis(mod);
26
- } else if (modName === 'ioredis') {
27
- this._injectIORedis(mod);
25
+ if (conf.sql_enabled === false) {
26
+ return;
28
27
  }
29
- };
30
28
 
31
- RedisObserver.prototype._injectRedis = function (mod) {
29
+ var self = this;
32
30
  var dbc_hash = 0;
33
- var dbc = '';
34
-
35
- const selectCommand = new Set(['get', 'hGet', 'hmGet']);
36
- const insertCommand = new Set(['set', 'hSet', 'hset', 'hmSet', 'hmset', 'zAdd', 'zadd', 'lSet', 'lset']);
37
- const updateCommand = new Set(['set', 'lSet', 'lset', 'hSet', 'hset', 'zAdd', 'zadd']);
38
- const deleteCommand = new Set(['del', 'lRem', 'sRem', 'srem', 'hDel', 'hdel', 'zRem', 'zrem']);
39
- const commands = new Set([...selectCommand, ...insertCommand, ...updateCommand, ...deleteCommand]);
40
-
41
- shimmer.wrap(mod, 'createClient', function (original) {
42
- return function wrappedCreateClient() {
43
- if (dbc_hash === 0) {
44
- if (arguments.length > 0) {
45
- var info = (arguments[0] || {});
46
- dbc = info.url || '';
47
- dbc_hash = HashUtil.hashFromString(dbc);
48
-
49
- DataTextAgent.DBC.add(dbc_hash, dbc);
50
- DataTextAgent.METHOD.add(dbc_hash, dbc);
51
- DataTextAgent.ERROR.add(dbc_hash, dbc);
52
- }
31
+ var dbc = 'redis://';
32
+
33
+ // Redis 명령어 매핑 ( 번만 정의)
34
+ var REDIS_COMMANDS = {
35
+ // 기본 명령어들
36
+ 'ping': 'PING', 'get': 'GET', 'set': 'SET', 'del': 'DEL', 'exists': 'EXISTS', 'keys': 'KEYS',
37
+ 'setex': 'SETEX', 'setEx': 'SETEX', 'expire': 'EXPIRE', 'expireAt': 'EXPIREAT', 'expireat': 'EXPIREAT',
38
+ 'ttl': 'TTL', 'incr': 'INCR', 'decr': 'DECR', 'incrBy': 'INCRBY', 'incrby': 'INCRBY',
39
+ 'decrBy': 'DECRBY', 'decrby': 'DECRBY', 'mGet': 'MGET', 'mget': 'MGET', 'mSet': 'MSET', 'mset': 'MSET',
40
+
41
+ // Hash 명령어들
42
+ 'hSet': 'HSET', 'hset': 'HSET', 'hGet': 'HGET', 'hget': 'HGET', 'hGetAll': 'HGETALL', 'hgetall': 'HGETALL',
43
+ 'hMGet': 'HMGET', 'hmget': 'HMGET', 'hMSet': 'HMSET', 'hmset': 'HMSET', 'hDel': 'HDEL', 'hdel': 'HDEL',
44
+
45
+ // List 명령어들
46
+ 'lPush': 'LPUSH', 'lpush': 'LPUSH', 'rPush': 'RPUSH', 'rpush': 'RPUSH', 'lPop': 'LPOP', 'lpop': 'LPOP',
47
+ 'rPop': 'RPOP', 'rpop': 'RPOP', 'lRange': 'LRANGE', 'lrange': 'LRANGE', 'lLen': 'LLEN', 'llen': 'LLEN',
48
+ 'lSet': 'LSET', 'lset': 'LSET', 'lRem': 'LREM', 'lrem': 'LREM',
49
+
50
+ // Set 명령어들
51
+ 'sAdd': 'SADD', 'sadd': 'SADD', 'sMembers': 'SMEMBERS', 'smembers': 'SMEMBERS', 'sRem': 'SREM', 'srem': 'SREM',
52
+ 'sCard': 'SCARD', 'scard': 'SCARD',
53
+
54
+ // Sorted Set 명령어들
55
+ 'zAdd': 'ZADD', 'zadd': 'ZADD', 'zRange': 'ZRANGE', 'zrange': 'ZRANGE', 'zRem': 'ZREM', 'zrem': 'ZREM'
56
+ };
57
+
58
+ // DBC 정보 설정
59
+ function setupDbcInfo(connectionOptions) {
60
+ if (dbc_hash === 0 && connectionOptions) {
61
+ dbc = 'redis://';
62
+ dbc += connectionOptions.host || 'localhost';
63
+ dbc += ':';
64
+ dbc += connectionOptions.port || 6379;
65
+ dbc += '/';
66
+ dbc += connectionOptions.db || 0;
67
+ dbc_hash = HashUtil.hashFromString(dbc);
68
+ }
69
+ }
70
+
71
+ // 에러 처리 함수
72
+ function handleRedisError(ctx, err) {
73
+ if (!err) return;
74
+
75
+ try {
76
+ var errorClass = err.code || err.name || 'RedisError';
77
+ var errorMessage = err.message || 'redis error';
78
+
79
+ if (!ctx.error) ctx.error = 1;
80
+ ctx.status = 500;
81
+ var errors = [errorClass, errorMessage];
82
+
83
+ AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
84
+ } catch (e) {
85
+ Logger.printError('WHATAP-216', 'Error handling Redis error', e, false);
86
+ }
87
+ }
88
+
89
+ // 통합 모니터링 함수 (한 번만 정의, 모든 명령어에서 재사용)
90
+ function createRedisWrapper(originalFunction, commandName) {
91
+ return function wrappedMethod() {
92
+ var ctx = TraceContextManager.getCurrentContext();
93
+ if (!ctx) {
94
+ return originalFunction.apply(this, arguments);
53
95
  }
54
96
 
55
- var client = original.apply(this, arguments);
97
+ var args = Array.prototype.slice.call(arguments);
98
+ var callback = null;
99
+ var hasCallback = false;
100
+
101
+ // 콜백 함수 찾기
102
+ if (args.length > 0 && typeof args[args.length - 1] === 'function') {
103
+ callback = args[args.length - 1];
104
+ hasCallback = true;
105
+ }
56
106
 
57
- const commandsArray = Array.from(commands);
58
- commandsArray.forEach(cmdName => {
59
- shimmer.wrap(client, cmdName, function (originalMethod) {
60
- const wrappedFunction = function wrappedSendCommand() {
61
- try{
62
- var ctx = TraceContextManager.getCurrentContext();
63
- if (!ctx) {
64
- return originalMethod.apply(this, arguments);
107
+ const asyncResource = new AsyncResource('redis-command');
108
+
109
+ return asyncResource.runInAsyncScope(() => {
110
+ // DB 연결 패킷 전송
111
+ ctx.start_time = Date.now();
112
+ ctx.elapsed = 0;
113
+ ctx.active_sqlhash = true;
114
+ AsyncSender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, [dbc]);
115
+
116
+ // Redis 명령 시작
117
+ var command_start_time = Date.now();
118
+ ctx.footprint(`Redis ${commandName} Start`);
119
+
120
+ // Redis에서는 args에서 쿼리 조합 (Python APM 방식 참고)
121
+ var queryParts = [commandName];
122
+ if (args && args.length > 0) {
123
+ // 콜백 함수 제외하고 실제 Redis 인자들만 처리
124
+ var redisArgs = hasCallback ? args.slice(0, -1) : args;
125
+ // 각 인자를 문자열로 변환하고 20자로 제한
126
+ var argStrings = redisArgs.map(function(arg) {
127
+ var argStr = String(arg);
128
+ return argStr.length > 20 ? argStr.substring(0, 20) + '...' : argStr;
129
+ });
130
+ queryParts = queryParts.concat(argStrings);
131
+ }
132
+ var commandText = `Redis ` + queryParts.join(' ');
133
+
134
+ if (hasCallback) {
135
+ // 비동기 콜백 처리
136
+ const callbackResource = new AsyncResource('redis-callback');
137
+ args[args.length - 1] = asyncResource.bind(function(err, result) {
138
+ var command_elapsed = Date.now() - command_start_time;
139
+ var resultCount = 0;
140
+
141
+ if (err) {
142
+ handleRedisError(ctx, err);
143
+ } else if (result !== undefined) {
144
+ if (Array.isArray(result)) {
145
+ resultCount = result.length;
146
+ } else if (typeof result === 'string' || typeof result === 'number') {
147
+ resultCount = 1;
65
148
  }
149
+ }
66
150
 
67
- var dbc_step = new DBCStep();
68
- dbc_step.hash = dbc_hash;
69
- ctx.profile.push(dbc_step);
70
-
71
- ctx.footprint('Redis Command Start');
72
-
73
- var command = wrappedFunction._commandName || 'unknown';
74
- var sql = `${command.toUpperCase()}`;
75
- var sql_step = new SqlStepX();
76
- sql_step.start_time = ctx.getElapsedTime();
77
- sql_step.dbc = dbc_hash;
78
-
79
- if(conf.getProperty('profile_redis_param_enabled', false)){
80
- if (arguments[0] && typeof arguments[0] === 'string')
81
- sql += ' ' + arguments[0];
82
- else if (arguments[0] && Array.isArray(arguments[0]))
83
- sql += ' ' + JSON.stringify(arguments[0]);
84
- else if (arguments[0] && typeof arguments[0] === 'object'){
85
- sql += ' ' + JSON.stringify(Object.keys(arguments[0]))
86
- }
151
+ ctx.elapsed = command_elapsed;
152
+ ctx.active_sqlhash = false;
153
+ AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, String(resultCount)]);
154
+ ctx.footprint(`Redis ${commandName} Done`);
87
155
 
88
- if(arguments[1] && arguments[1].length > 0){
89
- var args = Array.isArray(arguments[1]) ? arguments[1] : [arguments[1]];
90
- sql_step.setTrue(1);
91
- var crc = {value: 0};
92
- const result = args.map((arg) => {
93
- if (typeof arg === 'string') {
94
- return `'${arg}'`
156
+ if (callback && typeof callback === 'function') {
157
+ return callbackResource.bind(callback).apply(this, arguments);
158
+ }
159
+ });
160
+
161
+ try {
162
+ return originalFunction.apply(this, args);
163
+ } catch (err) {
164
+ handleRedisError(ctx, err);
165
+ throw err;
166
+ }
167
+ } else {
168
+ // Promise 또는 동기 처리
169
+ try {
170
+ var result = originalFunction.apply(this, args);
171
+
172
+ // Promise 결과 처리
173
+ if (result && typeof result.then === 'function') {
174
+ return result.then(
175
+ function(res) {
176
+ var command_elapsed = Date.now() - command_start_time;
177
+ var resultCount = 0;
178
+
179
+ if (res !== undefined) {
180
+ if (Array.isArray(res)) {
181
+ resultCount = res.length;
182
+ } else if (typeof res === 'string' || typeof res === 'number') {
183
+ resultCount = 1;
95
184
  }
96
- return arg
97
- }).toString()
98
- sql_step.p2 = toParamBytes(result, crc);
99
- sql_step.pcrc = crc.value;
100
- }
101
- }
102
- sql_step.hash = HashUtil.hashFromString(sql);
103
- ctx.profile.push(sql_step);
185
+ }
104
186
 
105
- DataTextAgent.SQL.add(sql_step.hash, sql);
187
+ ctx.elapsed = command_elapsed;
188
+ ctx.active_sqlhash = false;
189
+ AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, String(resultCount)]);
190
+ ctx.footprint(`Redis ${commandName} Done`);
191
+ return res;
192
+ },
193
+ function(err) {
194
+ handleRedisError(ctx, err);
195
+ var command_elapsed = Date.now() - command_start_time;
196
+ ctx.elapsed = command_elapsed;
197
+ ctx.active_sqlhash = false;
198
+ AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, '0']);
199
+ ctx.footprint(`Redis ${commandName} Done`);
200
+ throw err;
201
+ }
202
+ );
203
+ } else {
204
+ // 동기 결과 처리
205
+ var command_elapsed = Date.now() - command_start_time;
206
+ var resultCount = 0;
106
207
 
107
- var result = originalMethod.apply(this, arguments);
108
- sql_step.elapsed = ctx.getElapsedTime();
208
+ if (result !== undefined) {
209
+ if (Array.isArray(result)) {
210
+ resultCount = result.length;
211
+ } else if (typeof result === 'string' || typeof result === 'number') {
212
+ resultCount = 1;
213
+ }
214
+ }
109
215
 
110
- ctx.footprint('Redis Command Done');
216
+ ctx.elapsed = command_elapsed;
217
+ AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, String(resultCount)]);
218
+ ctx.footprint(`Redis ${commandName} Done`);
111
219
  return result;
112
- }catch (e) {
113
- Logger.printError("WHATAP-605", "Redis CRUD error", e, false);
114
- return originalMethod.apply(this, arguments);
115
220
  }
116
- };
117
- wrappedFunction._commandName = cmdName;
118
- return wrappedFunction;
119
- });
221
+ } catch (commandError) {
222
+ handleRedisError(ctx, commandError);
223
+ var command_elapsed = Date.now() - command_start_time;
224
+ ctx.elapsed = command_elapsed;
225
+ AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, '0']);
226
+ ctx.footprint(`Redis ${commandName} Done`);
227
+ throw commandError;
228
+ }
229
+ }
120
230
  });
121
- return client;
122
231
  };
123
- });
232
+ }
124
233
 
125
- return this;
126
- };
234
+ function wrapRedisMethods(client) {
235
+ var wrappedCount = 0;
236
+ var skippedCount = 0;
237
+ var notFoundCount = 0;
238
+ var alreadyWrappedCount = 0;
127
239
 
128
- RedisObserver.prototype._injectIORedis = function (mod) {
129
- var ioredis_dbc_hash = 0;
130
- var ioredis_dbc = '';
240
+ // 클라이언트에 실제로 존재하는 메서드들 확인
241
+ var availableMethods = Object.getOwnPropertyNames(client)
242
+ .concat(Object.getOwnPropertyNames(Object.getPrototypeOf(client)))
243
+ .filter(name => typeof client[name] === 'function' && !name.startsWith('_'))
244
+ .sort();
131
245
 
132
- const selectCommand = new Set(['get', 'hget', 'hmget', 'zrange', 'smembers', 'lrange']);
133
- const insertCommand = new Set(['set', 'hset', 'hmset', 'zadd', 'lset', 'sadd', 'lpush', 'rpush', 'evalsha']);
134
- const updateCommand = new Set(['set', 'lset', 'hset', 'zadd']);
135
- const deleteCommand = new Set(['del', 'lrem', 'srem', 'hdel', 'zrem']);
136
- const commands = new Set([...selectCommand, ...insertCommand, ...updateCommand, ...deleteCommand]);
246
+ // 번의 루프로 모든 메서드 처리
247
+ Object.keys(REDIS_COMMANDS).forEach(function(methodName) {
137
248
 
138
- shimmer.wrap(mod.prototype, 'sendCommand', function (original) {
139
- return function wrappedSendCommand(command) {
140
- // Get trace context
141
- var ctx = TraceContextManager.getCurrentContext();
142
- if (!ctx) {
143
- return original.apply(this, arguments);
249
+ if (!client[methodName]) {
250
+ notFoundCount++;
251
+ skippedCount++;
252
+ return;
253
+ }
254
+ if (client[methodName].__whatap_wrapped__) {
255
+ alreadyWrappedCount++;
256
+ skippedCount++;
257
+ return;
144
258
  }
145
259
 
146
- if (ioredis_dbc_hash === 0 && this.options) {
147
- ioredis_dbc = 'redis://';
148
- ioredis_dbc += this.options.host || '';
149
- ioredis_dbc += ':';
150
- ioredis_dbc += this.options.port || '';
151
- ioredis_dbc_hash = HashUtil.hashFromString(ioredis_dbc);
152
- DataTextAgent.DBC.add(ioredis_dbc_hash, ioredis_dbc);
260
+ try {
261
+ // 원본 함수 참조 저장
262
+ var originalFunction = client[methodName];
263
+ client[methodName] = createRedisWrapper(originalFunction, REDIS_COMMANDS[methodName]);
264
+ client[methodName].__whatap_wrapped__ = true;
265
+ wrappedCount++;
266
+ } catch (e) {
267
+ skippedCount++;
268
+ Logger.printError('WHATAP-217', `Error wrapping ${methodName}`, e, false);
153
269
  }
270
+ });
154
271
 
155
- if (command && command.name && commands.has(command.name.toLowerCase())) {
156
- var dbc_step = new DBCStep();
157
- dbc_step.hash = ioredis_dbc_hash;
158
- dbc_step.start_time = ctx.getElapsedTime();
159
- ctx.profile.push(dbc_step);
160
-
161
- try {
162
- var sql_step = new SqlStepX();
163
- var sql = command.name.toUpperCase();
164
- if(conf.getProperty('profile_redis_param_enabled', false)){
165
- var args = command.args;
166
- var key = args.shift();
167
- if (key && typeof key === 'string')
168
- sql += ' ' + key;
169
-
170
- if(args && args.length > 0){
171
- sql_step.setTrue(1);
172
- var crc = {value: 0};
173
- const result = args.map((arg) => {
174
- if (typeof arg === 'string') {
175
- return `'${arg}'`
176
- }
177
- return arg;
178
- }).toString()
179
- sql_step.p2 = toParamBytes(result, crc);
180
- sql_step.pcrc = crc.value;
272
+ return wrappedCount > 0;
273
+ }
274
+
275
+ // createClient 래핑
276
+ if (mod.createClient && !mod.createClient.__whatap_wrapped__) {
277
+ shimmer.wrap(mod, 'createClient', function(original) {
278
+ return function wrappedCreateClient() {
279
+ var args = Array.prototype.slice.call(arguments);
280
+ var ctx = TraceContextManager.getCurrentContext();
281
+
282
+ if (ctx) {
283
+ ctx.start_time = Date.now();
284
+ ctx.footprint('IORedis Client Created');
285
+ ctx.db_opening = true;
286
+ }
287
+
288
+ if (args.length > 0) {
289
+ var options = args[0] || {};
290
+ // URL에서 연결 정보 추출
291
+ if (typeof options === 'string') {
292
+ try {
293
+ var url = new URL(options);
294
+ setupDbcInfo({
295
+ host: url.hostname,
296
+ port: parseInt(url.port) || 6379,
297
+ db: url.pathname.slice(1) || 0
298
+ });
299
+ } catch (e) {
300
+ setupDbcInfo({ host: 'localhost', port: 6379, db: 0 });
301
+ }
302
+ } else if (options.url) {
303
+ try {
304
+ var url = new URL(options.url);
305
+ setupDbcInfo({
306
+ host: url.hostname,
307
+ port: parseInt(url.port) || 6379,
308
+ db: options.database || url.pathname.slice(1) || 0
309
+ });
310
+ } catch (e) {
311
+ setupDbcInfo(options);
181
312
  }
313
+ } else {
314
+ setupDbcInfo(options);
182
315
  }
316
+ }
183
317
 
184
- sql_step.hash = HashUtil.hashFromString(sql);
185
- sql_step.start_time = ctx.getElapsedTime();
186
- DataTextAgent.SQL.add(sql_step.hash, sql);
187
- ctx.profile.push(sql_step);
318
+ var client = original.apply(this, args);
188
319
 
189
- var originalPromise = original.apply(this, arguments);
190
- return originalPromise.finally(function () {
191
- dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
192
- sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
193
- });
194
- } catch (e) {
195
- Logger.printError("WHATAP-605", "ioredis CRUD error", e, false);
196
- return original.apply(this, arguments);
197
- }
198
- }
320
+ // Redis 클라이언트 후킹
321
+ if (client) {
322
+ var wrapAttempts = 0;
323
+ var maxAttempts = 3;
324
+ var wrapCompleted = false; // 완료 플래그 추가
199
325
 
200
- return original.apply(this, arguments);
201
- };
202
- });
326
+ function attemptBatchWrap(reason) {
327
+ if (wrapCompleted) {
328
+ return true;
329
+ }
203
330
 
204
- return this;
205
- };
331
+ wrapAttempts++;
206
332
 
207
- var toParamBytes = function (p, crc) {
208
- if (p == null || p.length === 0) {
209
- return null;
210
- }
211
- try {
212
- return ParamSecurity.encrypt(Buffer.from(p, 'utf8'), crc);
213
- } catch (e) {
214
- return null;
333
+ if (wrapRedisMethods(client)) {
334
+ wrapCompleted = true; // 완료 마킹
335
+ return true;
336
+ }
337
+
338
+ if (wrapAttempts >= maxAttempts) {
339
+ return false;
340
+ }
341
+
342
+ return false;
343
+ }
344
+
345
+ // 이벤트 기반 래핑
346
+ if (client.on && typeof client.on === 'function') {
347
+ client.on('connect', function() {
348
+ attemptBatchWrap('Redis connect event');
349
+ });
350
+
351
+ client.on('ready', function() {
352
+ attemptBatchWrap('Redis ready event');
353
+ });
354
+ }
355
+
356
+ // 즉시 시도
357
+ if (!attemptBatchWrap('Immediate attempt')) {
358
+ // 지연 시도
359
+ setImmediate(function() {
360
+ if (!attemptBatchWrap('setImmediate attempt')) {
361
+ // 1초 후 마지막 시도
362
+ setTimeout(function() {
363
+ attemptBatchWrap('Final timeout attempt');
364
+ }, 1000);
365
+ }
366
+ });
367
+ }
368
+ }
369
+
370
+ if (ctx) {
371
+ ctx.elapsed = Date.now() - ctx.start_time;
372
+ ctx.footprint('Redis Connecting Done');
373
+ ctx.db_opening = false;
374
+ }
375
+
376
+ return client;
377
+ };
378
+ });
379
+ mod.createClient.__whatap_wrapped__ = true;
215
380
  }
216
381
  };
217
382