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
@@ -1,388 +1,583 @@
1
- /**
2
- * Copyright 2016 the WHATAP project authors. All rights reserved.
3
- * Use of this source code is governed by a license that
4
- * can be found in the LICENSE file.
5
- */
6
-
7
1
  var TraceContextManager = require('../trace/trace-context-manager'),
8
- HashUtil = require('../util/hashutil'),
9
- Logger = require('../logger'),
10
- conf = require('../conf/configure'),
11
- AsyncSender = require('../udp/async_sender'),
12
- PacketTypeEnum = require('../udp/packet_type_enum'),
13
- shimmer = require('../core/shimmer'),
14
- AsyncResource = require('async_hooks').AsyncResource;
2
+ SqlStepX = require('../step/sql-stepx'),
3
+ DataTextAgent = require('../data/datatext-agent'),
4
+ HashUtil = require('../util/hashutil');
5
+ const DBCStep = require("../step/dbc-step");
6
+ const Logger = require("../logger");
7
+ const ParamSecurity = require("../util/paramsecurity");
8
+ const {Buffer} = require("buffer");
9
+ const conf = require('../conf/configure');
10
+ const shimmer = require('../core/shimmer');
15
11
 
16
12
  var MongooseObserver = function (agent) {
17
13
  this.agent = agent;
18
14
  this.packages = ['mongoose'];
19
15
  };
20
16
 
21
- var dbc_hash = 0;
22
- var dbc = '';
23
-
24
- // Mongoose 명령어 매핑
25
- var MONGOOSE_COMMANDS = {
26
- 'create': 'CREATE',
27
- 'insertMany': 'INSERTMANY',
28
- 'find': 'FIND',
29
- 'findById': 'FINDBYID',
30
- 'findOne': 'FINDONE',
31
- 'countDocuments': 'COUNTDOCUMENTS',
32
- 'distinct': 'DISTINCT',
33
- 'updateMany': 'UPDATEMANY',
34
- 'updateOne': 'UPDATEONE',
35
- 'replaceOne': 'REPLACEONE',
36
- 'findOneAndUpdate': 'FINDONEANDUPDATE',
37
- 'findByIdAndUpdate': 'FINDBYIDANDUPDATE',
38
- 'deleteOne': 'DELETEONE',
39
- 'deleteMany': 'DELETEMANY',
40
- 'findOneAndDelete': 'FINDONEANDDELETE',
41
- 'findByIdAndDelete': 'FINDBYIDANDDELETE',
42
- 'aggregate': 'AGGREGATE'
17
+ var dbc_step, dbc, conn_dbc_hash;
18
+
19
+ // 단일 필터 메서드 (첫 번째 인자가 filter)
20
+ const collectionMethodsWithFilter = [
21
+ 'find',
22
+ // 'findById',
23
+ 'findOne',
24
+ 'countDocuments',
25
+ 'distinct',
26
+ 'deleteOne',
27
+ 'deleteMany'
28
+ ];
29
+
30
+ // 이중 필터 메서드 (첫 번째: filter, 두 번째: update)
31
+ const collectionMethodsWithTwoFilters = [
32
+ 'findOneAndUpdate',
33
+ // 'findByIdAndUpdate',
34
+ 'updateOne',
35
+ 'updateMany',
36
+ 'findOneAndDelete',
37
+ // 'findByIdAndDelete',
38
+ 'replaceOne'
39
+ ];
40
+
41
+ // 특수 메서드
42
+ const specialMethods = [
43
+ 'create',
44
+ 'insertMany',
45
+ 'aggregate'
46
+ ];
47
+
48
+ MongooseObserver.prototype.inject = function (mod, modName) {
49
+ var self = this;
50
+
51
+ // mongoose.connect 후킹 (연결 정보 저장)
52
+ shimmer.wrap(mod, 'connect', function(originalConnect) {
53
+ return function wrappedConnect() {
54
+ if (arguments[0]) {
55
+ dbc = arguments[0];
56
+ conn_dbc_hash = HashUtil.hashFromString(dbc);
57
+ }
58
+ return originalConnect.apply(this, arguments);
59
+ };
60
+ });
61
+
62
+ // Model 생성 후킹
63
+ shimmer.wrap(mod, 'model', function(originalModel) {
64
+ return function wrappedModel() {
65
+ const model = originalModel.apply(this, arguments);
66
+
67
+ // 생성된 Model에 메서드 패치 적용
68
+ self.patchModelMethods(model);
69
+
70
+ return model;
71
+ };
72
+ });
43
73
  };
44
74
 
45
- // DBC 정보 설정
46
- function setupDbcInfo(connectionString) {
47
- if (dbc_hash === 0 && connectionString) {
48
- dbc = connectionString;
49
- dbc_hash = HashUtil.hashFromString(dbc);
50
- Logger.print('WHATAP-MONGOOSE-DBC', 'DBC setup: ' + dbc, false);
51
- }
52
- }
75
+ MongooseObserver.prototype.patchModelMethods = function(Model) {
76
+ if(Model.__whatap_observe__) {return;}
77
+ Model.__whatap_observe__ = true;
78
+ var self = this;
53
79
 
54
- // Mongoose 에러 처리
55
- function handleMongooseError(ctx, err) {
56
- if (!err) return;
80
+ // 단일 필터 메서드 패치
81
+ collectionMethodsWithFilter.forEach(function(methodName) {
82
+ if (Model[methodName]) {
83
+ shimmer.wrap(Model, methodName, function(originalMethod) {
84
+ return self.createSingleFilterWrapper(originalMethod, methodName);
85
+ });
86
+ }
87
+ });
57
88
 
58
- try {
59
- var errorClass = err.code || err.name || 'MongooseError';
60
- var errorMessage = err.message || 'mongoose error';
61
- var errorStack = '';
62
-
63
- if (conf.trace_sql_error_stack && conf.trace_sql_error_depth && err.stack) {
64
- var traceDepth = conf.trace_sql_error_depth;
65
- var stackLines = err.stack.split("\n");
66
- if (stackLines.length > traceDepth) {
67
- stackLines = stackLines.slice(0, traceDepth + 1);
68
- }
69
- errorStack = stackLines.join("\n");
89
+ // 이중 필터 메서드 패치
90
+ collectionMethodsWithTwoFilters.forEach(function(methodName) {
91
+ if (Model[methodName]) {
92
+ shimmer.wrap(Model, methodName, function(originalMethod) {
93
+ return self.createDoubleFilterWrapper(originalMethod, methodName);
94
+ });
70
95
  }
96
+ });
97
+
98
+ // aggregate 메서드 패치
99
+ if (Model.aggregate) {
100
+ shimmer.wrap(Model, 'aggregate', function(originalAggregate) {
101
+ return self.createAggregateWrapper(originalAggregate);
102
+ });
103
+ }
71
104
 
72
- var shouldAddError = false;
73
- if (conf._is_trace_ignore_err_cls_contains === true && errorClass &&
74
- errorClass.toString().indexOf(conf.trace_ignore_err_cls_contains) < 0) {
75
- shouldAddError = true;
76
- } else if (conf._is_trace_ignore_err_msg_contains === true && errorMessage &&
77
- errorMessage.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
78
- shouldAddError = true;
79
- } else if (conf._is_trace_ignore_err_cls_contains === false &&
80
- conf._is_trace_ignore_err_msg_contains === false) {
81
- shouldAddError = true;
105
+ // create, insertMany 패치
106
+ ['create', 'insertMany'].forEach(function(methodName) {
107
+ if (Model[methodName]) {
108
+ shimmer.wrap(Model, methodName, function(originalMethod) {
109
+ return self.createInsertWrapper(originalMethod, methodName);
110
+ });
111
+ }
112
+ });
113
+ };
114
+
115
+ MongooseObserver.prototype.createSingleFilterWrapper = function(originalMethod, methodName) {
116
+ var self = this;
117
+
118
+ return function wrappedSingleFilter() {
119
+ const ctx = TraceContextManager.getCurrentContext();
120
+ if (!ctx) {
121
+ return originalMethod.apply(this, arguments);
82
122
  }
123
+ ctx.__mongoose_traced__ = true;
124
+
125
+ const dbc_step = self.createDBCStep(ctx);
126
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
127
+ ctx.profile.push(dbc_step);
128
+
129
+ // SQL Step 생성
130
+ const sql_step = new SqlStepX();
131
+ sql_step.start_time = ctx.getElapsedTime();
132
+
133
+ const result = originalMethod.apply(this, arguments);
134
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
135
+
136
+ try {
137
+ const filterObj = arguments[0];
138
+ const parseResult = self.parseSingleFilter(methodName, filterObj);
83
139
 
84
- if (shouldAddError) {
85
- if (!ctx.error) ctx.error = 1;
86
- ctx.status = 500;
87
- var errors = [errorClass];
88
- if (errorMessage) {
89
- errors.push(errorMessage);
140
+ // SQL 문자열 생성: "ModelName methodName where=[field1,field2]"
141
+ const modelName = this.modelName || 'Unknown';
142
+ let sql = `${modelName} ${methodName}`;
143
+
144
+ if (parseResult.whereFields.length > 0) {
145
+ sql += ` where=[${parseResult.whereFields.join(',')}]`;
90
146
  }
91
- if (errorStack || err.stack) {
92
- errors.push(errorStack || err.stack);
147
+
148
+ // SQL Step 완성
149
+ sql_step.hash = HashUtil.hashFromString(sql);
150
+ DataTextAgent.SQL.add(sql_step.hash, sql);
151
+
152
+ // 파라미터 암호화 처리
153
+ if (conf.getProperty('profile_mongodb_param_enabled', false) === true &&
154
+ parseResult.values.length > 0) {
155
+ sql_step.setTrue(1);
156
+ var crc = {value: 0};
157
+ const valuesString = parseResult.values.map(val => {
158
+ if (typeof val === 'string') {
159
+ return `'${val}'`;
160
+ }
161
+ return val;
162
+ }).toString();
163
+ sql_step.p2 = toParamBytes(valuesString, crc);
164
+ sql_step.pcrc = crc.value;
93
165
  }
94
166
 
95
- AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
167
+ ctx.profile.push(sql_step);
168
+
169
+ } catch (e) {
170
+ Logger.printError("WHATAP-611", "Mongodb single filter query error", e, false);
96
171
  }
97
- } catch (e) {
98
- Logger.printError('WHATAP-245', 'Error handling Mongoose error', e, false);
99
- }
100
- }
101
-
102
- // Model 메서드 래퍼 생성
103
- function createModelMethodWrapper(methodName) {
104
- return function(original) {
105
- return function wrappedMethod() {
106
- var ctx = TraceContextManager.getCurrentContext();
107
- if (!ctx) {
108
- return original.apply(this, arguments);
172
+
173
+ return result;
174
+ };
175
+ };
176
+
177
+ MongooseObserver.prototype.createDoubleFilterWrapper = function(originalMethod, methodName) {
178
+ var self = this;
179
+
180
+ return function wrappedDoubleFilter() {
181
+ const ctx = TraceContextManager.getCurrentContext();
182
+ if (!ctx) {
183
+ return originalMethod.apply(this, arguments);
184
+ }
185
+ ctx.__mongoose_traced__ = true;
186
+
187
+ // DBC Step 생성
188
+ const dbc_step = self.createDBCStep(ctx);
189
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
190
+ ctx.profile.push(dbc_step);
191
+
192
+ // SQL Step 생성
193
+ const sql_step = new SqlStepX();
194
+ sql_step.start_time = ctx.getElapsedTime();
195
+
196
+ const result = originalMethod.apply(this, arguments);
197
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
198
+
199
+ try {
200
+ const filterObj = arguments[0];
201
+ const updateObj = arguments[1];
202
+ const parseResult = self.parseDoubleFilter(methodName, filterObj, updateObj);
203
+
204
+ // SQL 문자열 생성: "ModelName methodName where=[field1] field=[field2,field3]"
205
+ const modelName = this.modelName || 'Unknown';
206
+ let sql = `${modelName} ${methodName}`;
207
+
208
+ if (parseResult.whereFields.length > 0) {
209
+ sql += ` where=[${parseResult.whereFields.join(',')}]`;
109
210
  }
110
211
 
111
- var args = Array.prototype.slice.call(arguments);
112
- var modelName = this.modelName || this.collection?.collectionName || 'unknown';
113
- var commandName = MONGOOSE_COMMANDS[methodName] || methodName.toUpperCase();
114
-
115
- const asyncResource = new AsyncResource('mongoose-command');
116
-
117
- return asyncResource.runInAsyncScope(() => {
118
- // DB 연결 패킷 전송
119
- ctx.start_time = Date.now();
120
- ctx.elapsed = 0;
121
- AsyncSender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, [dbc]);
122
-
123
- var command_start_time = Date.now();
124
- ctx.footprint(`Mongoose ${commandName} Start`);
125
-
126
- // 쿼리 텍스트 구성
127
- var queryParts = [commandName, modelName];
128
- var fieldNames = [];
129
-
130
- try {
131
- if (methodName === 'aggregate' && Array.isArray(args[0])) {
132
- // aggregate 파이프라인 처리
133
- var pipelineFields = [];
134
- args[0].forEach(function(stage) {
135
- if (stage.$match) {
136
- pipelineFields = pipelineFields.concat(Object.keys(stage.$match));
137
- }
138
- if (stage.$group) {
139
- pipelineFields = pipelineFields.concat(Object.keys(stage.$group));
140
- }
141
- });
142
- if (pipelineFields.length > 0) {
143
- queryParts.push('field=[' + pipelineFields.join(',') + ']');
144
- }
145
- } else {
146
- // 첫 번째 인자에서 필드명 추출
147
- if (args[0] && typeof args[0] === 'object' && !Array.isArray(args[0])) {
148
- fieldNames = Object.keys(args[0]);
149
- }
150
- if (fieldNames.length > 0) {
151
- queryParts.push('field=[' + fieldNames.join(',') + ']');
152
- }
212
+ if (parseResult.updateFields.length > 0) {
213
+ sql += ` field=[${parseResult.updateFields.join(',')}]`;
214
+ }
153
215
 
154
- // 번째 인자에서 업데이트 필드 추출 (update 계열 메서드)
155
- if (args[1] && typeof args[1] === 'object' &&
156
- (methodName.includes('update') || methodName.includes('Update'))) {
157
- var updateFields = [];
158
- if (args[1].$set) {
159
- updateFields = updateFields.concat(Object.keys(args[1].$set));
160
- } else {
161
- updateFields = Object.keys(args[1]);
162
- }
163
- if (updateFields.length > 0) {
164
- queryParts.push('value=[' + updateFields.join(',') + ']');
165
- }
166
- }
216
+ // SQL Step 완성
217
+ sql_step.hash = HashUtil.hashFromString(sql);
218
+ DataTextAgent.SQL.add(sql_step.hash, sql);
219
+
220
+ // 파라미터 암호화 처리 (where 값 + update 값)
221
+ if (conf.getProperty('profile_mongodb_param_enabled', false) === true && parseResult.values.length > 0) {
222
+ sql_step.setTrue(1);
223
+ var crc = {value: 0};
224
+ const valuesString = parseResult.values.map(val => {
225
+ if (typeof val === 'string') {
226
+ return `'${val}'`;
167
227
  }
168
- } catch (e) {
169
- Logger.printError('WHATAP-246', 'Error extracting field names', e, false);
170
- }
228
+ return val;
229
+ }).toString();
230
+ sql_step.p2 = toParamBytes(valuesString, crc);
231
+ sql_step.pcrc = crc.value;
232
+ }
171
233
 
172
- var commandText = 'MongoDB ' + queryParts.join(' ');
234
+ ctx.profile.push(sql_step);
173
235
 
174
- function executeCallback(err, result) {
175
- try {
176
- var command_elapsed = Date.now() - command_start_time;
236
+ } catch (e) {
237
+ Logger.printError("WHATAP-611", "Mongodb double filter query error", e, false);
238
+ }
177
239
 
178
- if (err) {
179
- handleMongooseError(ctx, err);
180
- }
240
+ return result;
241
+ };
242
+ };
181
243
 
182
- ctx.elapsed = command_elapsed;
183
- AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, '0']);
184
- ctx.footprint(`Mongoose ${commandName} Done`);
185
- } catch (e) {
186
- Logger.printError('WHATAP-247', 'Error in Mongoose callback', e, false);
187
- }
188
- }
244
+ MongooseObserver.prototype.createAggregateWrapper = function(originalAggregate) {
245
+ var self = this;
189
246
 
190
- // 콜백 처리
191
- var hasCallback = false;
192
- for (var i = args.length - 1; i >= 0; i--) {
193
- if (typeof args[i] === 'function') {
194
- hasCallback = true;
195
- var originalCallback = args[i];
196
-
197
- args[i] = asyncResource.bind(function() {
198
- var callbackArgs = Array.prototype.slice.call(arguments);
199
- executeCallback(callbackArgs[0], callbackArgs[1]);
200
-
201
- if (originalCallback && typeof originalCallback === 'function') {
202
- return originalCallback.apply(this, callbackArgs);
203
- }
204
- });
205
- break;
206
- }
207
- }
247
+ return function wrappedAggregate() {
248
+ const ctx = TraceContextManager.getCurrentContext();
249
+ if (!ctx) {
250
+ return originalAggregate.apply(this, arguments);
251
+ }
252
+ ctx.__mongoose_traced__ = true;
208
253
 
209
- try {
210
- var result = original.apply(this, args);
211
-
212
- // Promise 기반 처리
213
- if (!hasCallback && result && typeof result.then === 'function') {
214
- return result.then(function(res) {
215
- executeCallback(null, res);
216
- return res;
217
- }).catch(function(err) {
218
- executeCallback(err, null);
219
- throw err;
220
- });
221
- }
254
+ const dbc_step = self.createDBCStep(ctx);
255
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
256
+ ctx.profile.push(dbc_step);
257
+
258
+ // SQL Step 생성
259
+ const sql_step = new SqlStepX();
260
+ sql_step.start_time = ctx.getElapsedTime();
261
+
262
+ const result = originalAggregate.apply(this, arguments);
263
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
264
+
265
+ try {
266
+ const pipeline = arguments[0];
267
+ const parseResult = self.parseAggregatePipeline(pipeline);
268
+
269
+ // SQL 문자열 생성
270
+ const modelName = this.modelName || 'Unknown';
271
+ let sql = `${modelName} aggregate`;
222
272
 
223
- // 동기적 결과 처리
224
- if (!hasCallback) {
225
- executeCallback(null, result);
273
+ if (parseResult.matchFields.length > 0) {
274
+ sql += ` where=[${parseResult.matchFields.join(',')}]`;
275
+ }
276
+
277
+ if (parseResult.groupFields.length > 0) {
278
+ sql += ` group=[${parseResult.groupFields.join(',')}]`;
279
+ }
280
+
281
+ if (parseResult.projectFields.length > 0) {
282
+ sql += ` field=[${parseResult.projectFields.join(',')}]`;
283
+ }
284
+
285
+ // SQL Step 완성
286
+ sql_step.hash = HashUtil.hashFromString(sql);
287
+ DataTextAgent.SQL.add(sql_step.hash, sql);
288
+
289
+ // 파라미터 암호화 처리 (주로 $match 조건값들)
290
+ if (conf.getProperty('profile_mongodb_param_enabled', false) === true &&
291
+ parseResult.values.length > 0) {
292
+ sql_step.setTrue(1);
293
+ var crc = {value: 0};
294
+ const valuesString = parseResult.values.map(val => {
295
+ if (typeof val === 'string') {
296
+ return `'${val}'`;
226
297
  }
298
+ return val;
299
+ }).toString();
300
+ sql_step.p2 = toParamBytes(valuesString, crc);
301
+ sql_step.pcrc = crc.value;
302
+ }
227
303
 
228
- return result;
229
- } catch (executeError) {
230
- executeCallback(executeError, null);
231
- throw executeError;
232
- }
233
- });
234
- };
304
+ ctx.profile.push(sql_step);
305
+
306
+ } catch (e) {
307
+ Logger.printError("WHATAP-611", "Mongodb aggregate query error", e, false);
308
+ }
309
+
310
+ return result;
235
311
  };
236
- }
312
+ };
237
313
 
238
- // Connection wrapper 함수
239
- var createConnectWrapper = function() {
240
- return function(original) {
241
- return function wrappedConnect() {
242
- var args = Array.prototype.slice.call(arguments);
243
- var connectionString = args[0];
244
- var ctx = TraceContextManager.getCurrentContext();
314
+ MongooseObserver.prototype.createInsertWrapper = function(originalMethod, methodName) {
315
+ var self = this;
316
+
317
+ return function wrappedInsert() {
318
+ const ctx = TraceContextManager.getCurrentContext();
319
+ if (!ctx) {
320
+ return originalMethod.apply(this, arguments);
321
+ }
322
+ ctx.__mongoose_traced__ = true;
323
+
324
+ const dbc_step = self.createDBCStep(ctx);
325
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
326
+ ctx.profile.push(dbc_step);
327
+
328
+ // SQL Step 생성
329
+ const sql_step = new SqlStepX();
330
+ sql_step.start_time = ctx.getElapsedTime();
331
+
332
+ const result = originalMethod.apply(this, arguments);
333
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
245
334
 
246
- setupDbcInfo(connectionString);
335
+ try {
336
+ const docs = arguments[0];
337
+ const parseResult = self.parseInsertDocuments(methodName, docs);
247
338
 
248
- if (!ctx) {
249
- return original.apply(this, arguments);
339
+ // SQL 문자열 생성
340
+ const modelName = this.modelName || 'Unknown';
341
+ let sql = `${modelName} ${methodName}`;
342
+
343
+ if (parseResult.insertFields.length > 0) {
344
+ sql += ` field=[${parseResult.insertFields.join(',')}]`;
250
345
  }
251
346
 
252
- const connectionResource = new AsyncResource('mongoose-connect');
253
-
254
- return connectionResource.runInAsyncScope(() => {
255
- ctx.start_time = Date.now();
256
- ctx.footprint('Mongoose Connecting Start');
257
- ctx.db_opening = true;
258
-
259
- // 콜백 래핑
260
- for (var i = args.length - 1; i >= 0; i--) {
261
- if (typeof args[i] === 'function') {
262
- var originalCallback = args[i];
263
- args[i] = connectionResource.bind(function() {
264
- var callbackArgs = Array.prototype.slice.call(arguments);
265
- var err = callbackArgs[0];
266
-
267
- if (ctx) {
268
- ctx.elapsed = Date.now() - ctx.start_time;
269
- ctx.db_opening = false;
270
-
271
- if (err) {
272
- handleMongooseError(ctx, err);
273
- ctx.footprint('Mongoose Connecting Error');
274
- } else {
275
- ctx.footprint('Mongoose Connecting Done');
276
- }
277
- }
278
-
279
- return originalCallback.apply(this, callbackArgs);
280
- });
281
- break;
347
+ // SQL Step 완성
348
+ sql_step.hash = HashUtil.hashFromString(sql);
349
+ DataTextAgent.SQL.add(sql_step.hash, sql);
350
+
351
+ // 파라미터 암호화 처리
352
+ if (conf.getProperty('profile_mongodb_param_enabled', false) === true &&
353
+ parseResult.values.length > 0) {
354
+ sql_step.setTrue(1);
355
+ var crc = {value: 0};
356
+ const valuesString = parseResult.values.map(val => {
357
+ if (typeof val === 'string') {
358
+ return `'${val}'`;
282
359
  }
283
- }
360
+ return val;
361
+ }).toString();
362
+ sql_step.p2 = toParamBytes(valuesString, crc);
363
+ sql_step.pcrc = crc.value;
364
+ }
284
365
 
285
- var result = original.apply(this, args);
366
+ ctx.profile.push(sql_step);
286
367
 
287
- // Promise 기반 처리
288
- if (result && typeof result.then === 'function') {
289
- return result.then(connectionResource.bind(function(connection) {
290
- if (ctx) {
291
- ctx.elapsed = Date.now() - ctx.start_time;
292
- ctx.db_opening = false;
293
- ctx.footprint('Mongoose Connecting Done');
294
- }
368
+ } catch (e) {
369
+ Logger.printError("WHATAP-611", "Mongodb insert query error", e, false);
370
+ }
295
371
 
296
- // 연결 완료 후 Model 래핑
297
- if (connection && connection.Model) {
298
- wrapModelMethods(connection.Model);
299
- }
372
+ return result;
373
+ };
374
+ };
300
375
 
301
- return connection;
302
- })).catch(connectionResource.bind(function(err) {
303
- if (ctx) {
304
- ctx.elapsed = Date.now() - ctx.start_time;
305
- ctx.db_opening = false;
306
- ctx.footprint('Mongoose Connecting Error');
307
- handleMongooseError(ctx, err);
308
- }
309
- throw err;
310
- }));
311
- }
376
+ MongooseObserver.prototype.createDBCStep = function(ctx) {
377
+ if (dbc && conn_dbc_hash) {
378
+ DataTextAgent.DBC.add(conn_dbc_hash, dbc);
379
+ DataTextAgent.METHOD.add(conn_dbc_hash, dbc);
380
+ DataTextAgent.ERROR.add(conn_dbc_hash, dbc);
381
+ }
312
382
 
313
- return result;
314
- });
315
- };
316
- };
383
+ var dbc_step = new DBCStep();
384
+ dbc_step.hash = conn_dbc_hash;
385
+ dbc_step.start_time = ctx.getElapsedTime();
386
+
387
+ return dbc_step;
317
388
  };
318
389
 
319
- // Model 메서드 래핑 함수
320
- function wrapModelMethods(ModelConstructor) {
321
- if (ModelConstructor && ModelConstructor.prototype && !ModelConstructor.__whatap_wrapped__) {
322
- Object.keys(MONGOOSE_COMMANDS).forEach(function(methodName) {
323
- if (ModelConstructor.prototype[methodName] &&
324
- !ModelConstructor.prototype[methodName].__whatap_wrapped__) {
390
+ // 비동기 결과 처리
391
+ MongooseObserver.prototype.handleAsyncResult = function(result, ctx, methodName) {
392
+ ctx.footprint('Mongodb Command Start: ' + methodName);
325
393
 
326
- shimmer.wrap(ModelConstructor.prototype, methodName,
327
- createModelMethodWrapper(methodName));
328
- ModelConstructor.prototype[methodName].__whatap_wrapped__ = true;
329
- }
330
- });
331
- ModelConstructor.__whatap_wrapped__ = true;
332
- Logger.print('WHATAP-MONGOOSE-MODEL', 'Model methods wrapped', false);
394
+ // Promise인 경우 처리 (대부분의 Mongoose 메서드가 Promise 반환)
395
+ if (result && typeof result.then === 'function') {
396
+ // 이미 실행은 완료되었으므로 특별한 처리 불필요
397
+ return result;
398
+ }
399
+
400
+ return result;
401
+ };
402
+
403
+ // 단일 필터 파싱
404
+ MongooseObserver.prototype.parseSingleFilter = function(methodName, filterObj) {
405
+ const result = {
406
+ whereFields: [],
407
+ values: []
408
+ };
409
+
410
+ if (filterObj && typeof filterObj === 'object') {
411
+ this.extractFieldsAndValues(filterObj, result.whereFields, result.values);
333
412
  }
334
- }
335
413
 
336
- MongooseObserver.prototype.inject = function(mod, moduleName) {
337
- if (mod.__whatap_observe__) {
338
- return;
414
+ return result;
415
+ };
416
+
417
+ // 이중 필터 파싱 - 수정된 버전
418
+ MongooseObserver.prototype.parseDoubleFilter = function(methodName, filterObj, updateObj) {
419
+ const result = {
420
+ whereFields: [],
421
+ updateFields: [],
422
+ values: []
423
+ };
424
+
425
+ // where 조건 파싱
426
+ if (filterObj && typeof filterObj === 'object') {
427
+ this.extractFieldsAndValues(filterObj, result.whereFields, result.values);
339
428
  }
340
- mod.__whatap_observe__ = true;
341
- Logger.initPrint("MongooseObserver");
342
429
 
343
- if (conf.sql_enabled === false) {
344
- return;
430
+ // update 조건 파싱 - 수정된 로직
431
+ if (updateObj && typeof updateObj === 'object') {
432
+ this.extractUpdateFieldsOnly(updateObj, result.updateFields, result.values);
345
433
  }
346
434
 
347
- var self = this;
435
+ return result;
436
+ };
348
437
 
349
- // mongoose.connect 래핑
350
- if (mod.connect && !mod.connect.__whatap_wrapped__) {
351
- shimmer.wrap(mod, 'connect', createConnectWrapper());
352
- mod.connect.__whatap_wrapped__ = true;
438
+ // Aggregate pipeline 파싱
439
+ MongooseObserver.prototype.parseAggregatePipeline = function(pipeline) {
440
+ const result = {
441
+ matchFields: [],
442
+ groupFields: [],
443
+ projectFields: [],
444
+ values: []
445
+ };
446
+
447
+ if (Array.isArray(pipeline)) {
448
+ pipeline.forEach(stage => {
449
+ if (stage.$match) {
450
+ this.extractFieldsAndValues(stage.$match, result.matchFields, result.values);
451
+ }
452
+ if (stage.$group) {
453
+ this.extractGroupFields(stage.$group, result.groupFields);
454
+ }
455
+ if (stage.$project) {
456
+ this.extractProjectFields(stage.$project, result.projectFields);
457
+ }
458
+ });
353
459
  }
354
460
 
355
- // 기본 Model이 이미 존재하는 경우 래핑
356
- if (mod.Model) {
357
- wrapModelMethods(mod.Model);
461
+ return result;
462
+ };
463
+
464
+ // Insert documents 파싱
465
+ MongooseObserver.prototype.parseInsertDocuments = function(methodName, docs) {
466
+ const result = {
467
+ insertFields: [],
468
+ values: []
469
+ };
470
+
471
+ if (methodName === 'insertMany' && Array.isArray(docs)) {
472
+ // 첫 번째 문서의 필드들을 기준으로 함
473
+ if (docs.length > 0 && typeof docs[0] === 'object') {
474
+ this.extractFieldsAndValues(docs[0], result.insertFields, result.values);
475
+ }
476
+ } else if (docs && typeof docs === 'object') {
477
+ this.extractFieldsAndValues(docs, result.insertFields, result.values);
358
478
  }
359
479
 
360
- // mongoose.model() 메서드 래핑 (새로운 모델 생성 시)
361
- if (mod.model && !mod.model.__whatap_wrapped__) {
362
- var originalModel = mod.model;
363
- mod.model = function() {
364
- var model = originalModel.apply(this, arguments);
365
-
366
- // 새로 생성된 모델의 메서드들 래핑
367
- if (model && !model.__whatap_wrapped__) {
368
- Object.keys(MONGOOSE_COMMANDS).forEach(function(methodName) {
369
- if (model[methodName] && !model[methodName].__whatap_wrapped__) {
370
- shimmer.wrap(model, methodName, createModelMethodWrapper(methodName));
371
- model[methodName].__whatap_wrapped__ = true;
372
- }
373
- });
374
- model.__whatap_wrapped__ = true;
480
+ return result;
481
+ };
482
+
483
+ // 필드와 값 추출 (기본)
484
+ MongooseObserver.prototype.extractFieldsAndValues = function(obj, fieldsArray, valuesArray) {
485
+ if (!obj || typeof obj !== 'object') return;
486
+
487
+ Object.keys(obj).forEach(key => {
488
+ if (key.startsWith('$')) {
489
+ // MongoDB 연산자는 중첩 처리
490
+ if (typeof obj[key] === 'object') {
491
+ this.extractFieldsAndValues(obj[key], fieldsArray, valuesArray);
492
+ } else {
493
+ valuesArray.push(obj[key]);
494
+ }
495
+ } else {
496
+ fieldsArray.push(key);
497
+ if (obj[key] !== null && obj[key] !== undefined) {
498
+ if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
499
+ // 중첩 객체의 값들도 추출
500
+ this.extractNestedValues(obj[key], valuesArray);
501
+ } else {
502
+ valuesArray.push(obj[key]);
503
+ }
375
504
  }
505
+ }
506
+ });
507
+ };
376
508
 
377
- return model;
378
- };
509
+ // Update 필드만 추출 (수정된 버전) - where 필드 중복 방지
510
+ MongooseObserver.prototype.extractUpdateFieldsOnly = function(updateObj, fieldsArray, valuesArray) {
511
+ if (!updateObj || typeof updateObj !== 'object') return;
512
+
513
+ Object.keys(updateObj).forEach(key => {
514
+ if (key.startsWith('$')) {
515
+ // $set: {name: 'new', email: 'new'} 처리
516
+ if (key === '$set' || key === '$inc' || key === '$push' || key === '$pull' || key === '$unset') {
517
+ if (typeof updateObj[key] === 'object') {
518
+ Object.keys(updateObj[key]).forEach(field => {
519
+ fieldsArray.push(field);
520
+ const value = updateObj[key][field];
521
+ if (value !== null && value !== undefined) {
522
+ valuesArray.push(value);
523
+ }
524
+ });
525
+ }
526
+ }
527
+ // 다른 연산자들도 필요시 추가
528
+ } else {
529
+ // 직접 업데이트: {name: "new"} 형태
530
+ fieldsArray.push(key);
531
+ if (updateObj[key] !== null && updateObj[key] !== undefined) {
532
+ valuesArray.push(updateObj[key]);
533
+ }
534
+ }
535
+ });
536
+ };
379
537
 
380
- // 기존 속성들 복사
381
- Object.keys(originalModel).forEach(function(key) {
382
- mod.model[key] = originalModel[key];
383
- });
538
+ // Group 필드 추출
539
+ MongooseObserver.prototype.extractGroupFields = function(groupObj, fieldsArray) {
540
+ if (!groupObj || typeof groupObj !== 'object') return;
541
+
542
+ Object.keys(groupObj).forEach(key => {
543
+ if (key !== '_id') {
544
+ fieldsArray.push(key);
545
+ }
546
+ });
547
+ };
548
+
549
+ // Project 필드 추출
550
+ MongooseObserver.prototype.extractProjectFields = function(projectObj, fieldsArray) {
551
+ if (!projectObj || typeof projectObj !== 'object') return;
384
552
 
385
- mod.model.__whatap_wrapped__ = true;
553
+ Object.keys(projectObj).forEach(key => {
554
+ fieldsArray.push(key);
555
+ });
556
+ };
557
+
558
+ // 중첩 값 추출
559
+ MongooseObserver.prototype.extractNestedValues = function(obj, valuesArray) {
560
+ if (!obj || typeof obj !== 'object') return;
561
+
562
+ Object.values(obj).forEach(value => {
563
+ if (value !== null && value !== undefined) {
564
+ if (typeof value === 'object' && !Array.isArray(value)) {
565
+ this.extractNestedValues(value, valuesArray);
566
+ } else {
567
+ valuesArray.push(value);
568
+ }
569
+ }
570
+ });
571
+ };
572
+
573
+ var toParamBytes = function (p, crc) {
574
+ if (p == null || p.length === 0) {
575
+ return null;
576
+ }
577
+ try {
578
+ return ParamSecurity.encrypt(Buffer.from(p, 'utf8'), crc);
579
+ } catch (e) {
580
+ return null;
386
581
  }
387
582
  };
388
583