whatap 0.5.20 → 0.5.22

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.
@@ -139,7 +139,7 @@ ControlHandler.prototype.handle = async function (p) {
139
139
  case ParamDef.GET_ENV:
140
140
  try {
141
141
  var mv = new MapValue();
142
- let ignore_env_variable = conf.get('ignore_env_variable_set', '');
142
+ let ignore_env_variable = conf.getProperty('ignore_env_variable_set', '');
143
143
  let ignore_env_variable_set = ignore_env_variable ? new Set(ignore_env_variable.split(',').map(item => item.trim())) : new Set();
144
144
  for (var k in process.env) {
145
145
  if(ignore_env_variable_set.has(k)) {
@@ -779,12 +779,20 @@ HttpObserver.prototype.inject = function( mod, moduleName ) {
779
779
  });
780
780
  });
781
781
 
782
- aop.before(mod.Server.prototype, ['on', 'addListener'], function (obj, args) {
783
- if(args[0] !== 'request') {return;}
784
-
785
- args[args.length-1] = self.__createTransactionObserver(args[args.length-1],
786
- moduleName === 'https', obj);
787
- });
782
+ shimmer.wrap(mod.Server.prototype, 'emit', function wrapEmit(originalEmit) {
783
+ return function wrappedEmit(eventName, ...args) {
784
+ if (eventName !== 'request') {
785
+ return originalEmit.apply(this, [eventName, ...args]);
786
+ }
787
+ let [req, res] = args;
788
+ return self.__createTransactionObserver(
789
+ (req, res) => originalEmit.call(this, eventName, req, res),
790
+ moduleName === 'https',
791
+ this
792
+ )(req, res);
793
+ };
794
+ }
795
+ );
788
796
 
789
797
  if( conf.getProperty('httpc_enabled', true) === false ) { return; }
790
798
 
@@ -60,6 +60,7 @@ MongoObserver.prototype.inject = function( mod, moduleName ) {
60
60
  self.agent.aop.before(mod.Collection.prototype, command, function (obj, args) {
61
61
  var ctx = TraceContextManager.getCurrentContext();
62
62
  if(ctx == null) { return; }
63
+ if(ctx && ctx.__mongoose_traced__) { return; }
63
64
 
64
65
  ctx.footprint('Mongodb Command Start: ' + command );
65
66
  var sql_step;
@@ -94,6 +95,7 @@ MongoObserver.prototype.inject = function( mod, moduleName ) {
94
95
  self.agent.aop.both(mod.Collection.prototype, command, function (obj, args, lctx) {
95
96
  var ctx = lctx.context;
96
97
  if(ctx == null) { return; }
98
+ if(ctx && ctx.__mongoose_traced__) { return; }
97
99
 
98
100
  ctx.footprint('Mongodb Command Start: ' + command );
99
101
  var sql_step;
@@ -192,6 +194,7 @@ MongoObserver.prototype.inject = function( mod, moduleName ) {
192
194
  //args[0] where, args[1] set
193
195
  var ctx = TraceContextManager.getCurrentContext();
194
196
  if(ctx == null) {return;}
197
+ if(ctx && ctx.__mongoose_traced__) { return; }
195
198
 
196
199
  ctx.footprint('Mongodb Command Start: '+command);
197
200
  var sql_step;
@@ -239,6 +242,7 @@ MongoObserver.prototype.inject = function( mod, moduleName ) {
239
242
  //args[0] where
240
243
  var ctx = TraceContextManager.getCurrentContext();
241
244
  if(ctx == null) { return; }
245
+ if(ctx && ctx.__mongoose_traced__) { return; }
242
246
 
243
247
  ctx.footprint('Mongodb Command Start: '+command);
244
248
  var sql_step;
@@ -278,6 +282,7 @@ MongoObserver.prototype.inject = function( mod, moduleName ) {
278
282
  self.agent.aop.both(mod.Collection.prototype, command, function (obj, args) {
279
283
  var ctx = TraceContextManager.getCurrentContext();
280
284
  if(ctx == null) { return; }
285
+ if(ctx && ctx.__mongoose_traced__) { return; }
281
286
 
282
287
  ctx.footprint('Mongodb Command Start: '+command);
283
288
  var sql_step;
@@ -4,6 +4,9 @@ var TraceContextManager = require('../trace/trace-context-manager'),
4
4
  HashUtil = require('../util/hashutil');
5
5
  const DBCStep = require("../step/dbc-step");
6
6
  const Logger = require("../logger");
7
+ const ParamSecurity = require("../util/paramsecurity");
8
+ const {Buffer} = require("buffer");
9
+ const conf = require('../conf/configure');
7
10
  const shimmer = require('../core/shimmer');
8
11
 
9
12
  var MongooseObserver = function (agent) {
@@ -13,454 +16,569 @@ var MongooseObserver = function (agent) {
13
16
 
14
17
  var dbc_step, dbc, conn_dbc_hash;
15
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
+
16
48
  MongooseObserver.prototype.inject = function (mod, modName) {
17
49
  var self = this;
18
- var hookCommand = [
19
- "create", // Create: Create a new document
20
- "insertMany", // Create: Create multiple documents at once
21
- "find", // Read: Find documents that match criteria
22
- "findById", // Read: Find a document by its ID
23
- "findOne", // Read: Find the first document that matches criteria
24
- "countDocuments", // Read: Count the number of documents that match criteria
25
- "distinct", // Read: Find distinct values for a given field
26
- "updateMany", // Update: Update multiple documents that match criteria
27
- "updateOne", // Update: Update a single document that matches criteria
28
- "replaceOne", // Update: Replace one document with another
29
- "findOneAndUpdate", // Update: Find the first document that matches criteria and update it
30
- "findByIdAndUpdate", // Update: Find a document by its ID and update it
31
- "deleteOne", // Delete: Delete a single document that matches criteria
32
- "deleteMany", // Delete: Delete multiple documents that match criteria
33
- "findOneAndDelete", // Delete: Find the first document that matches criteria and delete it
34
- "findByIdAndDelete" // Delete: Find a document by its ID and delete it
35
- ];
36
-
37
- // mongoose.connect 함수를 shimmer로 래핑
38
- shimmer.wrap(mod, 'connect', function(original) {
39
- return function wrappedConnect() {
40
- var args = Array.prototype.slice.call(arguments);
41
-
42
- if(!args[0]) {
43
- return original.apply(this, arguments);
44
- }
45
50
 
46
- if(dbc) {
47
- return original.apply(this, arguments);
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);
48
57
  }
58
+ return originalConnect.apply(this, arguments);
59
+ };
60
+ });
49
61
 
50
- dbc = args[0];
51
- conn_dbc_hash = HashUtil.hashFromString(dbc);
52
-
53
- var result = original.apply(this, arguments);
62
+ // Model 생성 후킹
63
+ shimmer.wrap(mod, 'model', function(originalModel) {
64
+ return function wrappedModel() {
65
+ const model = originalModel.apply(this, arguments);
54
66
 
55
- // Promise 체인으로 연결 성공 후 Model을 hook
56
- result.then(connection => {
57
- // Model 생성 시점을 intercept하기 위해 mongoose.model을 hook
58
- self.hookModelCreation(mod);
67
+ // 생성된 Model에 메서드 패치 적용
68
+ self.patchModelMethods(model);
59
69
 
60
- }).catch(e => {
61
- Logger.printError("WHATAP-612", "Mongodb connection error", e, false);
62
- });
63
-
64
- return result;
70
+ return model;
65
71
  };
66
72
  });
67
-
68
- // 이미 생성된 모델들도 처리하기 위해 mongoose.model 함수를 hook
69
- this.hookModelCreation(mod);
70
73
  };
71
74
 
72
- // Model 생성을 intercept하여 각 Model hook을 적용
73
- MongooseObserver.prototype.hookModelCreation = function(mongoose) {
75
+ MongooseObserver.prototype.patchModelMethods = function(Model) {
76
+ if(Model.__whatap_observe__) {return;}
77
+ Model.__whatap_observe__ = true;
74
78
  var self = this;
75
79
 
76
- // mongoose.model 함수를 wrap하여 새로 생성되는 모델들을 intercept
77
- if (!mongoose._whatapHooked) {
78
- shimmer.wrap(mongoose, 'model', function(original) {
79
- return function wrappedModel() {
80
- var result = original.apply(this, arguments);
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
+ });
81
88
 
82
- // 새로 생성된 Model에 hook 적용
83
- if (result && typeof result === 'function') {
84
- self.wrapModelMethods(result);
85
- }
89
+ // 이중 필터 메서드 패치
90
+ collectionMethodsWithTwoFilters.forEach(function(methodName) {
91
+ if (Model[methodName]) {
92
+ shimmer.wrap(Model, methodName, function(originalMethod) {
93
+ return self.createDoubleFilterWrapper(originalMethod, methodName);
94
+ });
95
+ }
96
+ });
86
97
 
87
- return result;
88
- };
98
+ // aggregate 메서드 패치
99
+ if (Model.aggregate) {
100
+ shimmer.wrap(Model, 'aggregate', function(originalAggregate) {
101
+ return self.createAggregateWrapper(originalAggregate);
89
102
  });
90
- mongoose._whatapHooked = true;
91
103
  }
92
104
 
93
- // 이미 존재하는 모델들에도 hook 적용
94
- var modelNames = mongoose.modelNames();
95
- modelNames.forEach(function(modelName) {
96
- var Model = mongoose.model(modelName);
97
- self.wrapModelMethods(Model);
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
+ }
98
112
  });
99
113
  };
100
114
 
101
- // Model의 메서드들을 wrap
102
- MongooseObserver.prototype.wrapModelMethods = function(Model) {
115
+ MongooseObserver.prototype.createSingleFilterWrapper = function(originalMethod, methodName) {
103
116
  var self = this;
104
117
 
105
- if (Model._whatapHooked) {
106
- return; // 이미 hook된 경우 스킵
107
- }
118
+ return function wrappedSingleFilter() {
119
+ const ctx = TraceContextManager.getCurrentContext();
120
+ if (!ctx) {
121
+ return originalMethod.apply(this, arguments);
122
+ }
123
+ ctx.__mongoose_traced__ = true;
108
124
 
109
- // aggregate 메서드 hook (Model에 직접 정의됨)
110
- if (typeof Model.aggregate === 'function') {
111
- shimmer.wrap(Model, 'aggregate', function(original) {
112
- return function wrappedAggregate() {
113
- return self.wrapAggregateExecution(original, this, arguments);
114
- };
115
- });
116
- }
125
+ const dbc_step = self.createDBCStep(ctx);
126
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
127
+ ctx.profile.push(dbc_step);
117
128
 
118
- // CRUD 메서드들 hook (대부분 Model에 직접 정의됨)
119
- var hookCommand = [
120
- "create", "insertMany", "find", "findById", "findOne",
121
- "countDocuments", "distinct", "updateMany", "updateOne",
122
- "replaceOne", "findOneAndUpdate", "findByIdAndUpdate",
123
- "deleteOne", "deleteMany", "findOneAndDelete", "findByIdAndDelete"
124
- ];
125
-
126
- hookCommand.forEach(function(methodName) {
127
- if (typeof Model[methodName] === 'function') {
128
- shimmer.wrap(Model, methodName, function(original) {
129
- return function wrappedMethod() {
130
- return self.wrapMethodExecution(original, this, arguments, methodName);
131
- };
132
- });
133
- }
134
- });
129
+ // SQL Step 생성
130
+ const sql_step = new SqlStepX();
131
+ sql_step.start_time = ctx.getElapsedTime();
135
132
 
136
- Model._whatapHooked = true;
137
- };
133
+ const result = originalMethod.apply(this, arguments);
134
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
138
135
 
139
- // aggregate 실행을 wrap하는 함수
140
- MongooseObserver.prototype.wrapAggregateExecution = function(original, thisArg, args) {
141
- var argsArray = Array.prototype.slice.call(args);
142
- var ctx = TraceContextManager.getCurrentContext();
136
+ try {
137
+ const filterObj = arguments[0];
138
+ const parseResult = self.parseSingleFilter(methodName, filterObj);
143
139
 
144
- if(!ctx) {
145
- return original.apply(thisArg, argsArray);
146
- }
140
+ // SQL 문자열 생성: "ModelName methodName where=[field1,field2]"
141
+ const modelName = this.modelName || 'Unknown';
142
+ let sql = `${modelName} ${methodName}`;
147
143
 
148
- // DBC 정보 등록
149
- if(dbc && conn_dbc_hash){
150
- DataTextAgent.DBC.add(conn_dbc_hash, dbc);
151
- DataTextAgent.METHOD.add(conn_dbc_hash, dbc);
152
- DataTextAgent.ERROR.add(conn_dbc_hash, dbc);
153
- }
144
+ if (parseResult.whereFields.length > 0) {
145
+ sql += ` where=[${parseResult.whereFields.join(',')}]`;
146
+ }
154
147
 
155
- // DBC Step 생성
156
- var dbc_step = new DBCStep();
157
- dbc_step.hash = conn_dbc_hash;
158
- dbc_step.start_time = ctx.getElapsedTime();
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;
165
+ }
159
166
 
160
- // SQL Step 생성
161
- var sql_step = new SqlStepX();
162
- sql_step.start_time = ctx.getElapsedTime();
167
+ ctx.profile.push(sql_step);
163
168
 
164
- var result;
165
- var hasError = false;
169
+ } catch (e) {
170
+ Logger.printError("WHATAP-611", "Mongodb single filter query error", e, false);
171
+ }
166
172
 
167
- try {
168
- // 원본 함수 실행
169
- result = original.apply(thisArg, argsArray);
170
-
171
- // Query 객체인 경우 exec 메서드를 wrap하여 실행 시점의 에러를 캐치
172
- if (result && typeof result.exec === 'function') {
173
- // Query 객체의 exec 메서드를 wrap
174
- var originalExec = result.exec;
175
- result.exec = function(callback) {
176
- var execResult = originalExec.call(this, callback);
177
-
178
- // Promise 체인에 catch 추가
179
- if (execResult && typeof execResult.catch === 'function') {
180
- execResult = execResult.catch(function(error) {
181
- // 에러 정보를 컨텍스트에 설정
182
- ctx.error_message = error.message || error.toString();
183
- ctx.error_class = error.name || error.constructor?.name || 'MongooseAggregateError';
184
-
185
- // 에러 정보를 DataTextAgent에도 추가
186
- if (conn_dbc_hash) {
187
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
188
- }
173
+ return result;
174
+ };
175
+ };
189
176
 
190
- Logger.printError("WHATAP-611", "Mongodb aggregate execution error", error, false);
191
- throw error; // 에러를 다시 던져서 원래 동작 유지
192
- });
193
- }
177
+ MongooseObserver.prototype.createDoubleFilterWrapper = function(originalMethod, methodName) {
178
+ var self = this;
194
179
 
195
- return execResult;
196
- };
197
-
198
- // then 메서드도 wrap (Promise 체인 사용 시)
199
- var originalThen = result.then;
200
- if (originalThen) {
201
- result.then = function(onResolve, onReject) {
202
- return originalThen.call(this, onResolve, function(error) {
203
- // 에러 정보를 컨텍스트에 설정
204
- ctx.error_message = error.message || error.toString();
205
- ctx.error_class = error.name || error.constructor?.name || 'MongooseAggregateError';
206
-
207
- // 에러 정보를 DataTextAgent에도 추가
208
- if (conn_dbc_hash) {
209
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
210
- }
180
+ return function wrappedDoubleFilter() {
181
+ const ctx = TraceContextManager.getCurrentContext();
182
+ if (!ctx) {
183
+ return originalMethod.apply(this, arguments);
184
+ }
185
+ ctx.__mongoose_traced__ = true;
211
186
 
212
- Logger.printError("WHATAP-611", "Mongodb aggregate execution error", error, false);
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);
213
191
 
214
- // 원래 reject 핸들러가 있으면 호출, 없으면 에러를 다시 던짐
215
- if (onReject) {
216
- return onReject(error);
217
- } else {
218
- throw error;
219
- }
220
- });
221
- };
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(',')}]`;
222
210
  }
223
- }
224
211
 
225
- // Promise인 경우 에러 처리를 위해 catch 추가 (직접 Promise를 반환하는 경우)
226
- else if (result && typeof result.then === 'function') {
227
- result = result.catch(function(error) {
228
- // 에러 정보를 컨텍스트에 설정
229
- ctx.error_message = error.message || error.toString();
230
- ctx.error_class = error.name || error.constructor?.name || 'MongooseAggregateError';
212
+ if (parseResult.updateFields.length > 0) {
213
+ sql += ` field=[${parseResult.updateFields.join(',')}]`;
214
+ }
231
215
 
232
- // 에러 정보를 DataTextAgent에도 추가
233
- if (conn_dbc_hash) {
234
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
235
- }
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}'`;
227
+ }
228
+ return val;
229
+ }).toString();
230
+ sql_step.p2 = toParamBytes(valuesString, crc);
231
+ sql_step.pcrc = crc.value;
232
+ }
236
233
 
237
- Logger.printError("WHATAP-611", "Mongodb aggregate execution error", error, false);
238
- throw error; // 에러를 다시 던져서 원래 동작 유지
239
- });
234
+ ctx.profile.push(sql_step);
235
+
236
+ } catch (e) {
237
+ Logger.printError("WHATAP-611", "Mongodb double filter query error", e, false);
240
238
  }
241
239
 
242
- } catch(error) {
243
- hasError = true;
244
- // 동기 에러 처리
245
- ctx.error_message = error.message || error.toString();
246
- ctx.error_class = error.name || error.constructor?.name || 'MongooseAggregateError';
240
+ return result;
241
+ };
242
+ };
247
243
 
248
- // 에러 정보를 DataTextAgent에도 추가
249
- if (conn_dbc_hash) {
250
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
251
- }
244
+ MongooseObserver.prototype.createAggregateWrapper = function(originalAggregate) {
245
+ var self = this;
252
246
 
253
- Logger.printError("WHATAP-611", "Mongodb aggregate execution error", error, false);
254
- throw error; // 에러를 다시 던져서 원래 동작 유지
255
- }
247
+ return function wrappedAggregate() {
248
+ const ctx = TraceContextManager.getCurrentContext();
249
+ if (!ctx) {
250
+ return originalAggregate.apply(this, arguments);
251
+ }
252
+ ctx.__mongoose_traced__ = true;
256
253
 
257
- // 결과 처리
258
- try {
254
+ const dbc_step = self.createDBCStep(ctx);
259
255
  dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
260
256
  ctx.profile.push(dbc_step);
261
257
 
262
- // aggregate의 경우 op 속성이 없을 수 있음
263
- var operation = result && result.op ? result.op : 'aggregate';
264
- ctx.footprint('Mongodb Command Start: ' + operation);
258
+ // SQL Step 생성
259
+ const sql_step = new SqlStepX();
260
+ sql_step.start_time = ctx.getElapsedTime();
265
261
 
266
- // Model 이름 가져오기
267
- var modelName = thisArg.modelName || 'Unknown';
262
+ const result = originalAggregate.apply(this, arguments);
263
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
268
264
 
269
- var sql = modelName + ' aggregate';
270
- var param = "";
265
+ try {
266
+ const pipeline = arguments[0];
267
+ const parseResult = self.parseAggregatePipeline(pipeline);
271
268
 
272
- if(Array.isArray(argsArray[0])) {
273
- argsArray[0].forEach(function (val, i) {
274
- if(i > 0 && param.length > 0) {
275
- param += ",";
276
- }
277
- if(val && val.hasOwnProperty('$match')) {
278
- var keys = Object.keys(val['$match']);
279
- param += keys.join(',');
280
- }
281
- if(val && val.hasOwnProperty('$group')) {
282
- var keys = Object.keys(val['$group']);
283
- param += keys.join(',');
284
- }
285
- });
269
+ // SQL 문자열 생성
270
+ const modelName = this.modelName || 'Unknown';
271
+ let sql = `${modelName} aggregate`;
272
+
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}'`;
297
+ }
298
+ return val;
299
+ }).toString();
300
+ sql_step.p2 = toParamBytes(valuesString, crc);
301
+ sql_step.pcrc = crc.value;
302
+ }
303
+
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;
311
+ };
312
+ };
313
+
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);
286
321
  }
287
- sql += ' field=['+param+']';
322
+ ctx.__mongoose_traced__ = true;
288
323
 
289
- sql_step.hash = HashUtil.hashFromString(sql);
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);
290
333
  sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
291
- DataTextAgent.SQL.add(sql_step.hash, sql);
292
- ctx.profile.push(sql_step);
293
334
 
294
- } catch(e) {
295
- Logger.printError("WHATAP-611", "Mongodb aggregate query processing error", e, false);
296
- }
335
+ try {
336
+ const docs = arguments[0];
337
+ const parseResult = self.parseInsertDocuments(methodName, docs);
297
338
 
298
- return result;
299
- };
339
+ // SQL 문자열 생성
340
+ const modelName = this.modelName || 'Unknown';
341
+ let sql = `${modelName} ${methodName}`;
300
342
 
301
- // 일반 CRUD 메서드 실행을 wrap하는 함수
302
- MongooseObserver.prototype.wrapMethodExecution = function(original, thisArg, args, methodName) {
303
- var argsArray = Array.prototype.slice.call(args);
343
+ if (parseResult.insertFields.length > 0) {
344
+ sql += ` field=[${parseResult.insertFields.join(',')}]`;
345
+ }
304
346
 
305
- // 번째 인자가 객체가 아니면 무시 (일부 메서드 제외)
306
- if(argsArray[0] && typeof argsArray[0] !== "object" &&
307
- !['findById', 'findByIdAndUpdate', 'findByIdAndDelete'].includes(methodName)) {
308
- return original.apply(thisArg, argsArray);
309
- }
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}'`;
359
+ }
360
+ return val;
361
+ }).toString();
362
+ sql_step.p2 = toParamBytes(valuesString, crc);
363
+ sql_step.pcrc = crc.value;
364
+ }
310
365
 
311
- var ctx = TraceContextManager.getCurrentContext();
312
- if(!ctx) {
313
- return original.apply(thisArg, argsArray);
314
- }
366
+ ctx.profile.push(sql_step);
315
367
 
316
- // DBC 정보 등록
317
- if(dbc && conn_dbc_hash){
368
+ } catch (e) {
369
+ Logger.printError("WHATAP-611", "Mongodb insert query error", e, false);
370
+ }
371
+
372
+ return result;
373
+ };
374
+ };
375
+
376
+ MongooseObserver.prototype.createDBCStep = function(ctx) {
377
+ if (dbc && conn_dbc_hash) {
318
378
  DataTextAgent.DBC.add(conn_dbc_hash, dbc);
319
379
  DataTextAgent.METHOD.add(conn_dbc_hash, dbc);
320
380
  DataTextAgent.ERROR.add(conn_dbc_hash, dbc);
321
381
  }
322
382
 
323
- // DBC Step 생성
324
383
  var dbc_step = new DBCStep();
325
384
  dbc_step.hash = conn_dbc_hash;
326
385
  dbc_step.start_time = ctx.getElapsedTime();
327
386
 
328
- // SQL Step 생성
329
- var sql_step = new SqlStepX();
330
- sql_step.start_time = ctx.getElapsedTime();
387
+ return dbc_step;
388
+ };
331
389
 
332
- var result;
333
- var hasError = false;
390
+ // 비동기 결과 처리
391
+ MongooseObserver.prototype.handleAsyncResult = function(result, ctx, methodName) {
392
+ ctx.footprint('Mongodb Command Start: ' + methodName);
334
393
 
335
- try {
336
- // 원본 함수 실행
337
- result = original.apply(thisArg, argsArray);
338
-
339
- // Query 객체인 경우 exec 메서드를 wrap하여 실행 시점의 에러를 캐치
340
- if (result && typeof result.exec === 'function') {
341
- // Query 객체의 exec 메서드를 wrap
342
- var originalExec = result.exec;
343
- result.exec = function(callback) {
344
- var execResult = originalExec.call(this, callback);
345
-
346
- // Promise 체인에 catch 추가
347
- if (execResult && typeof execResult.catch === 'function') {
348
- execResult = execResult.catch(function(error) {
349
- // 에러 정보를 컨텍스트에 설정
350
- ctx.error_message = error.message || error.toString();
351
- ctx.error_class = error.name || error.constructor?.name || 'MongooseError';
352
-
353
- // 에러 정보를 DataTextAgent에도 추가
354
- if (conn_dbc_hash) {
355
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
356
- }
394
+ // Promise인 경우 처리 (대부분의 Mongoose 메서드가 Promise 반환)
395
+ if (result && typeof result.then === 'function') {
396
+ // 이미 실행은 완료되었으므로 특별한 처리 불필요
397
+ return result;
398
+ }
357
399
 
358
- Logger.printError("WHATAP-611", "Mongodb " + methodName + " execution error", error, false);
359
- throw error; // 에러를 다시 던져서 원래 동작 유지
360
- });
361
- }
400
+ return result;
401
+ };
362
402
 
363
- return execResult;
364
- };
365
-
366
- // then 메서드도 wrap (Promise 체인 사용 시)
367
- var originalThen = result.then;
368
- if (originalThen) {
369
- result.then = function(onResolve, onReject) {
370
- return originalThen.call(this, onResolve, function(error) {
371
- // 에러 정보를 컨텍스트에 설정
372
- ctx.error_message = error.message || error.toString();
373
- ctx.error_class = error.name || error.constructor?.name || 'MongooseError';
374
-
375
- // 에러 정보를 DataTextAgent에도 추가
376
- if (conn_dbc_hash) {
377
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
378
- }
403
+ // 단일 필터 파싱
404
+ MongooseObserver.prototype.parseSingleFilter = function(methodName, filterObj) {
405
+ const result = {
406
+ whereFields: [],
407
+ values: []
408
+ };
379
409
 
380
- Logger.printError("WHATAP-611", "Mongodb " + methodName + " execution error", error, false);
410
+ if (filterObj && typeof filterObj === 'object') {
411
+ this.extractFieldsAndValues(filterObj, result.whereFields, result.values);
412
+ }
381
413
 
382
- // 원래 reject 핸들러가 있으면 호출, 없으면 에러를 다시 던짐
383
- if (onReject) {
384
- return onReject(error);
385
- } else {
386
- throw error;
387
- }
388
- });
389
- };
390
- }
391
- } else if (result && typeof result.then === 'function') {
392
- // Promise인 경우 에러 처리를 위해 catch 추가 (직접 Promise를 반환하는 경우)
393
- result = result.catch(function(error) {
394
- // 에러 정보를 컨텍스트에 설정
395
- ctx.error_message = error.message || error.toString();
396
- ctx.error_class = error.name || error.constructor?.name || 'MongooseError';
397
-
398
- // 에러 정보를 DataTextAgent에도 추가
399
- if (conn_dbc_hash) {
400
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
401
- }
414
+ return result;
415
+ };
402
416
 
403
- Logger.printError("WHATAP-611", "Mongodb " + methodName + " execution error", error, false);
404
- throw error; // 에러를 다시 던져서 원래 동작 유지
405
- });
406
- }
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);
428
+ }
407
429
 
408
- } catch(error) {
409
- hasError = true;
410
- // 동기 에러 처리
411
- ctx.error_message = error.message || error.toString();
412
- ctx.error_class = error.name || error.constructor?.name || 'MongooseError';
430
+ // update 조건 파싱 - 수정된 로직
431
+ if (updateObj && typeof updateObj === 'object') {
432
+ this.extractUpdateFieldsOnly(updateObj, result.updateFields, result.values);
433
+ }
413
434
 
414
- // 에러 정보를 DataTextAgent에도 추가
415
- if (conn_dbc_hash) {
416
- DataTextAgent.ERROR.add(conn_dbc_hash, ctx.error_message);
417
- }
435
+ return result;
436
+ };
418
437
 
419
- Logger.printError("WHATAP-611", "Mongodb " + methodName + " execution error", error, false);
420
- throw error;
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
+ });
421
459
  }
422
460
 
423
- // 결과 처리
424
- try {
425
- dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
426
- ctx.profile.push(dbc_step);
427
-
428
- // operation 이름 가져오기
429
- var operation = (result && result.op) ? result.op : methodName;
430
- ctx.footprint('Mongodb Command Start: ' + operation);
461
+ return result;
462
+ };
431
463
 
432
- // Model 이름 가져오기
433
- var modelName = thisArg.modelName || 'Unknown';
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);
478
+ }
434
479
 
435
- var sql = modelName + ' ' + operation;
480
+ return result;
481
+ };
436
482
 
437
- if (argsArray[0]) {
438
- try {
439
- var keys = typeof argsArray[0] === 'object' ? Object.keys(argsArray[0]) : [argsArray[0]];
440
- sql += ' field=' + JSON.stringify(keys);
441
- } catch(e) {
442
- sql += ' field=[unknown]';
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
+ }
443
504
  }
444
505
  }
445
- if (argsArray[1]) {
446
- try {
447
- var keys = typeof argsArray[1] === 'object' ? Object.keys(argsArray[1]) : [argsArray[1]];
448
- sql += ' value=' + JSON.stringify(keys);
449
- } catch(e) {
450
- sql += ' value=[unknown]';
506
+ });
507
+ };
508
+
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]);
451
533
  }
452
534
  }
535
+ });
536
+ };
453
537
 
454
- sql_step.hash = HashUtil.hashFromString(sql);
455
- sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
456
- DataTextAgent.SQL.add(sql_step.hash, sql);
457
- ctx.profile.push(sql_step);
538
+ // Group 필드 추출
539
+ MongooseObserver.prototype.extractGroupFields = function(groupObj, fieldsArray) {
540
+ if (!groupObj || typeof groupObj !== 'object') return;
458
541
 
459
- } catch(e) {
460
- Logger.printError("WHATAP-611", "Mongodb query processing error", e, false);
461
- }
542
+ Object.keys(groupObj).forEach(key => {
543
+ if (key !== '_id') {
544
+ fieldsArray.push(key);
545
+ }
546
+ });
547
+ };
462
548
 
463
- return result;
549
+ // Project 필드 추출
550
+ MongooseObserver.prototype.extractProjectFields = function(projectObj, fieldsArray) {
551
+ if (!projectObj || typeof projectObj !== 'object') return;
552
+
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;
581
+ }
464
582
  };
465
583
 
466
584
  exports.MongooseObserver = MongooseObserver;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "whatap",
3
3
  "homepage": "http://www.whatap.io",
4
- "version": "0.5.20",
5
- "releaseDate": "20250626",
4
+ "version": "0.5.22",
5
+ "releaseDate": "20250630",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},