whatap 0.5.7 → 0.5.9

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.
@@ -267,7 +267,11 @@ var ConfigDefault = {
267
267
  "grpc_profile_ignore_method": bool("grpc_profile_ignore_method", true),
268
268
 
269
269
  "oname_port_postfix_enabled": bool("oname_port_postfix_enabled", false),
270
- "ignore_http_lost_connection": bool("ignore_http_lost_connection", false)
270
+ "ignore_http_lost_connection": bool("ignore_http_lost_connection", false),
271
+
272
+ "profile_graphql_enabled": bool("profile_graphql_enabled", true),
273
+ "profile_graphql_variable_enabled": bool("profile_graphql_variable_enabled", false),
274
+ "ignore_graphql_operation": str("ignore_graphql_operation", '')
271
275
  };
272
276
 
273
277
  ConfigDefault._hook_method_ignore_prefix = ConfigDefault.hook_method_ignore_prefixes.split(',');
package/lib/core/agent.js CHANGED
@@ -30,7 +30,10 @@ var Interceptor = require('./interceptor').Interceptor,
30
30
  PromiseObserver = require('../observers/promise-observer').PromiseObserver,
31
31
  PgSqlObserver = require('../observers/pgsql-observer').PgSqlObserver,
32
32
  ScheduleObserver = require('../observers/schedule-observer').ScheduleObserver,
33
- GRpcObserver = require('../observers/grpc-observer').GRpcObserver;
33
+ // GRpcObserver = require('../observers/grpc-observer').GRpcObserver,
34
+ ApolloObserver = require('../observers/apollo-server-observer').ApolloServerObserver,
35
+ PrismaObserver = require('../observers/prisma-observer').PrismaObserver;
36
+
34
37
 
35
38
  var Configuration = require('./../conf/configure'),
36
39
  SecurityMaster = require('./../net/security-master'),
@@ -112,6 +115,12 @@ NodeAgent.prototype.init = function(cb) {
112
115
  self.findRoot();
113
116
 
114
117
  Logger.initializer.process();
118
+
119
+ // 최초 1회 실행 후 1시간마다 실행
120
+ const ONE_HOUR = 60 * 60 * 1000;
121
+ Logger.clearOldLog();
122
+ setInterval(Logger.clearOldLog, ONE_HOUR);
123
+
115
124
  Logger.print('WHATAP-001', 'Start initialize WhaTap Agent... Root[' + self._conf['app.root'] + ']', true);
116
125
 
117
126
  if(self._conf['app.root'] == null || self._conf['app.root'].length == 0) {
@@ -268,6 +277,8 @@ NodeAgent.prototype.loadObserves = function() {
268
277
  observes.push(PgSqlObserver);
269
278
  observes.push(ScheduleObserver);
270
279
  // observes.push(GRpcObserver);
280
+ observes.push(ApolloObserver);
281
+ observes.push(PrismaObserver);
271
282
 
272
283
  var packageToObserve = {};
273
284
  observes.forEach(function(observeObj) {
package/lib/logger.js CHANGED
@@ -27,7 +27,7 @@
27
27
  var now = DateUtil.currentTime();
28
28
  if(now > this.last + DateUtil.MILLIS_PER_HOUR) {
29
29
  this.last = now;
30
- Logger.clearOldLog();
30
+ // Logger.clearOldLog();
31
31
  }
32
32
  if(this.lastFileRotation !== conf.log_rotation_enabled
33
33
  || this.lastDateUnit !== DateUtil.getDateUnit()
@@ -210,7 +210,6 @@
210
210
  log_prefix = WHATAP_CONF + "-";
211
211
 
212
212
  fs.readdir(dir, function (err, files) {
213
-
214
213
  for (var i = 0; i < files.length; i++) {
215
214
  var stat = fs.statSync(path.join(dir, files[i]));
216
215
  if (stat.isDirectory()) {
@@ -232,12 +231,12 @@
232
231
  var fileUnit = DateUtil.getDateUnit(d);
233
232
  try {
234
233
  if (nowUnit - fileUnit > conf.log_keep_days) {
234
+ let filePath = `${dir}/${files[i]}`;
235
235
  fs.rmSync(filePath);
236
236
  }
237
237
  } catch (e) {
238
238
  }
239
239
  }
240
-
241
240
  });
242
241
  },
243
242
  read : function( file, endpos, length , callback) {
@@ -0,0 +1,153 @@
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
+ var TraceContextManager = require('../trace/trace-context-manager'),
8
+ conf = require('../conf/configure'),
9
+ Logger = require('../logger');
10
+ const HashUtil = require('../util/hashutil');
11
+ const DataTextAgent = require('../data/datatext-agent');
12
+ const MessageStep = require('../step/message-step');
13
+ const shimmer = require('../core/shimmer');
14
+
15
+ var profile_graphql_enabled = conf.getProperty('profile_graphql_enabled', true);
16
+ var profile_graphql_variable_enabled = conf.getProperty('profile_graphql_variable_enabled', false);
17
+ var ignore_graphql_operation = conf.getProperty('ignore_graphql_operation', '');
18
+
19
+ conf.on('profile_graphql_enabled', function(newProperty) {
20
+ profile_graphql_enabled = newProperty;
21
+ });
22
+ conf.on('profile_graphql_variable_enabled', function(newProperty) {
23
+ profile_graphql_variable_enabled = newProperty;
24
+ });
25
+ conf.on('ignore_graphql_operation', function(newProperty) {
26
+ ignore_graphql_operation = newProperty;
27
+ });
28
+
29
+ var ApolloServerObserver = function(agent) {
30
+ this.agent = agent;
31
+ this.packages = ['@apollo/server'];
32
+ };
33
+
34
+ function checkIgnoreOperation(ignore_operation, operation_name) {
35
+ try {
36
+ let ignore_operation_set = null;
37
+ if (ignore_operation) {
38
+ ignore_operation_set = new Set(ignore_operation.split(','));
39
+ } else {
40
+ ignore_operation_set = null;
41
+ }
42
+ if (ignore_operation_set && ignore_operation_set.has(operation_name)) {
43
+ return true;
44
+ }
45
+ } catch (e) {
46
+ Logger.printError('WHATAP-803', 'GraphQL checkIgnoreOperation error: ' + e, false);
47
+ }
48
+ return false;
49
+ }
50
+
51
+ function wrapExecuteHTTPGraphQLRequest(original) {
52
+ return async function executeHTTPGraphQLRequest() {
53
+ if (!profile_graphql_enabled) {
54
+ return original.apply(this, arguments);
55
+ }
56
+
57
+ try {
58
+ var ctx = TraceContextManager.getCurrentContext();
59
+ if (!ctx) {
60
+ return original.apply(this, arguments);
61
+ }
62
+
63
+ const [requestContext] = arguments;
64
+ const { httpGraphQLRequest } = requestContext;
65
+ const { body } = httpGraphQLRequest;
66
+
67
+ const operationName = body.operationName;
68
+ if (checkIgnoreOperation(ignore_graphql_operation, operationName)) {
69
+ TraceContextManager.end(ctx._id)
70
+ return original.apply(this, arguments);
71
+ }
72
+
73
+ const originalServiceName = ctx.service_name || '';
74
+ if(operationName){
75
+ ctx.service_name = `${originalServiceName}?operationName=${operationName}`;
76
+ ctx.service_hash = HashUtil.hashFromString(ctx.service_name);
77
+ }
78
+
79
+ // Operation Type Step (query)
80
+ if(body.query && body.query.trim()){
81
+ var step_type = new MessageStep();
82
+ step_type.hash = HashUtil.hashFromString('Type');
83
+ step_type.start_time = ctx.getElapsedTime();
84
+ step_type.desc = body.query.trim().startsWith('mutation') ? 'mutation' : 'query';
85
+ DataTextAgent.MESSAGE.add(step_type.hash, "Type");
86
+ ctx.profile.push(step_type);
87
+ }
88
+
89
+ // Operation Name Step
90
+ if(operationName){
91
+ var step_operation = new MessageStep();
92
+ step_operation.hash = HashUtil.hashFromString('Operation');
93
+ step_operation.start_time = ctx.getElapsedTime();
94
+ step_operation.desc = operationName || 'anonymous';
95
+ DataTextAgent.MESSAGE.add(step_operation.hash, "Operation");
96
+ ctx.profile.push(step_operation);
97
+ }
98
+
99
+ // Variables Step
100
+ if (profile_graphql_variable_enabled && body.variables && Object.keys(body.variables).length > 0) {
101
+ var step_variables = new MessageStep();
102
+ step_variables.hash = HashUtil.hashFromString('Variables');
103
+ step_variables.start_time = ctx.getElapsedTime();
104
+ step_variables.desc = JSON.stringify(Object.keys(body.variables));
105
+ DataTextAgent.MESSAGE.add(step_variables.hash, "Variables");
106
+ ctx.profile.push(step_variables);
107
+ }
108
+
109
+ const response = await original.apply(this, arguments);
110
+
111
+ // 에러 처리
112
+ if (response.body.kind === 'complete' && response.body.string.includes('"errors"')) {
113
+ try {
114
+ const result = JSON.parse(response.body.string);
115
+ if (result.errors && result.errors.length > 0) {
116
+ const errorMessages = result.errors.map(error => error.message).join('\n');
117
+ ctx.statusCode = 500;
118
+ ctx.error_message = errorMessages;
119
+ }
120
+ } catch (e) {
121
+ Logger.printError('WHATAP-802', 'GraphQL error parsing failed', e, false);
122
+ }
123
+ }
124
+
125
+ return response;
126
+
127
+ } catch (err) {
128
+ Logger.printError('WHATAP-801', 'GraphQL executeHTTPGraphQLRequest error: ' + err, false);
129
+ if (ctx) {
130
+ ctx.statusCode = 500;
131
+ ctx.error_message = err.stack;
132
+ }
133
+ return original.apply(this, arguments);
134
+ }
135
+ };
136
+ }
137
+
138
+ ApolloServerObserver.prototype.inject = function(mod, moduleName) {
139
+ if (mod.__whatap_observe__) {
140
+ return;
141
+ }
142
+ mod.__whatap_observe__ = true;
143
+ Logger.initPrint("ApolloServerObserver");
144
+
145
+ if (profile_graphql_enabled) {
146
+ // Hook ApolloServer
147
+ if (mod.ApolloServer && mod.ApolloServer.prototype) {
148
+ shimmer.wrap(mod.ApolloServer.prototype, 'executeHTTPGraphQLRequest', wrapExecuteHTTPGraphQLRequest);
149
+ }
150
+ }
151
+ };
152
+
153
+ exports.ApolloServerObserver = ApolloServerObserver;
@@ -0,0 +1,1045 @@
1
+ /**
2
+ * Copyright 2025 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
+ const TraceContextManager = require("../trace/trace-context-manager");
8
+ const ParsedSql = require("../trace/parsed-sql");
9
+ const SqlStepX = require("../step/sql-stepx");
10
+ const DBCStep = require("../step/dbc-step");
11
+ const ResultSetStep = require("../step/resultset-step");
12
+ const DataTextAgent = require("../data/datatext-agent");
13
+ const StatSql = require("../stat/stat-sql");
14
+ const MeterSql = require("../counter/meter/meter-sql");
15
+ const conf = require("../conf/configure");
16
+ const IntKeyMap = require("../util/intkey-map");
17
+ const EscapeLiteralSQL = require("../util/escape-literal-sql");
18
+ const HashUtil = require("../util/hashutil");
19
+ const StatError = require("../stat/stat-error");
20
+ const TextTypes = require("../lang/text-types");
21
+ const ParamSecurity = require("../util/paramsecurity");
22
+ const Logger = require("../logger");
23
+ const Buffer = require("buffer").Buffer;
24
+ const DateUtil = require("../util/dateutil");
25
+ const TraceSQL = require("../trace/trace-sql");
26
+ const shimmer = require("../core/shimmer");
27
+
28
+ var PrismaObserver = function(agent) {
29
+ this.agent = agent;
30
+ this.packages = ["@prisma/client"];
31
+ };
32
+
33
+ var dbc_hash = 0;
34
+ var dbc = "";
35
+
36
+ PrismaObserver.prototype.inject = function(mod, moduleName) {
37
+ if (mod.__whatap_observe__) {
38
+ return;
39
+ }
40
+
41
+ mod.__whatap_observe__ = true;
42
+ Logger.initPrint("PrismaObserver");
43
+
44
+ const self = this;
45
+
46
+ if (conf.sql_enabled === false) {
47
+ return;
48
+ }
49
+
50
+ // Prisma Client 초기화 메서드 후킹
51
+ if (mod.PrismaClient) {
52
+ // 직접 PrismaClient 생성자 후킹
53
+ shimmer.wrap(mod, 'PrismaClient', function(originalConstructor) {
54
+ return function() {
55
+ // 원래 생성자 호출
56
+ const instance = new originalConstructor(...arguments);
57
+
58
+ // 이 시점에서 instance가 생성된 Prisma 클라이언트 인스턴스
59
+ self.patchPrismaInstance(instance);
60
+
61
+ return instance;
62
+ };
63
+ });
64
+ }
65
+ };
66
+
67
+ // 각 Prisma 인스턴스에 패치 적용
68
+ PrismaObserver.prototype.patchPrismaInstance = function(prismaInstance) {
69
+ if (prismaInstance.__whatap_observe__) {
70
+ return;
71
+ }
72
+ prismaInstance.__whatap_observe__ = true;
73
+
74
+ // 모든 DB 연결 정보 추출 및 초기화
75
+ this.setupConnectionInfo(prismaInstance);
76
+
77
+ // $connect 후킹
78
+ this.hookConnect(prismaInstance);
79
+
80
+ // Raw 쿼리 메서드 후킹
81
+ // this.hookRawQueryMethods(prismaInstance);
82
+
83
+ // $use 미들웨어 후킹 (모델 메서드 추적)
84
+ this.hookUseMiddleware(prismaInstance);
85
+
86
+ // 각 모델에 대한 직접 메서드 후킹 (더 안정적인 추적을 위해)
87
+ // this.hookModelMethods(prismaInstance);
88
+ };
89
+
90
+ // 연결 정보 설정
91
+ PrismaObserver.prototype.setupConnectionInfo = function(prismaInstance) {
92
+ try {
93
+ // 연결 정보 가져오기 시도
94
+ const url = prismaInstance._engineConfig?.datasources?.db?.url ||
95
+ prismaInstance._baseDmmf?.datamodel?.datasources?.[0]?.url?.value ||
96
+ process.env.DATABASE_URL ||
97
+ 'prisma:unknown';
98
+
99
+ if (url && url !== "prisma:unknown") {
100
+ const dbUrl = new URL(url);
101
+ const protocol = dbUrl.protocol.replace(':', '');
102
+
103
+ // 프로토콜에 따라 접두사 설정 (postgresql -> pgsql로 변환할 수도 있음)
104
+ const dbProtocol = protocol === 'pgsql' ? 'postgresql' : protocol;
105
+
106
+ // MySQL 관찰자와 동일한 형식으로 구성
107
+ dbc = `${dbProtocol}://`;
108
+ dbc += dbUrl.username || '';
109
+ dbc += "@";
110
+ dbc += dbUrl.hostname || '';
111
+ dbc += '/';
112
+ dbc += dbUrl.pathname.replace('/', '') || '';
113
+ dbc_hash = HashUtil.hashFromString(dbc);
114
+
115
+ DataTextAgent.DBC.add(dbc_hash, dbc);
116
+ DataTextAgent.METHOD.add(dbc_hash, dbc);
117
+ DataTextAgent.ERROR.add(dbc_hash, dbc);
118
+ }
119
+
120
+ } catch (e) {
121
+ Logger.printError("WHATAP-301", "Failed to extract connection info", e, false);
122
+ dbc = "prisma:unknown";
123
+ dbc_hash = HashUtil.hashFromString(dbc);
124
+ }
125
+ };
126
+
127
+ // Connect 메서드 후킹
128
+ PrismaObserver.prototype.hookConnect = function(prismaInstance) {
129
+ const self = this;
130
+
131
+ shimmer.wrap(prismaInstance, "$connect", function(original) {
132
+ return async function() {
133
+ const ctx = TraceContextManager.getCurrentContext();
134
+ if (!ctx || ctx.db_opening) {
135
+ return original.apply(this, arguments);
136
+ }
137
+
138
+ ctx.db_opening = true;
139
+ ctx.footprint("Prisma Connecting Start");
140
+
141
+ const dbc_step = new DBCStep();
142
+ dbc_step.start_time = ctx.getElapsedTime();
143
+
144
+ try {
145
+ const result = await original.apply(this, arguments);
146
+
147
+ dbc_step.hash = dbc_hash;
148
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
149
+
150
+ ctx.db_opening = false;
151
+ ctx.footprint("Prisma Connecting Done");
152
+ ctx.profile.push(dbc_step);
153
+
154
+ return result;
155
+ } catch (err) {
156
+ ctx.db_opening = false;
157
+
158
+ dbc_step.hash = dbc_hash;
159
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
160
+ dbc_step.error = StatError.addError("prisma-connection", err.message, ctx.service_hash);
161
+
162
+ if (ctx.error.isZero()) {
163
+ ctx.error = dbc_step.error;
164
+ }
165
+
166
+ ctx.profile.push(dbc_step);
167
+ Logger.printError("WHATAP-302", "Connection Error", err);
168
+ throw err;
169
+ }
170
+ };
171
+ });
172
+ };
173
+
174
+ // Raw 쿼리 메서드 후킹
175
+ PrismaObserver.prototype.hookRawQueryMethods = function(prismaInstance) {
176
+ const self = this;
177
+ const queryMethods = ["$queryRaw", "$executeRaw", "$queryRawUnsafe", "$executeRawUnsafe"];
178
+
179
+ queryMethods.forEach(method => {
180
+ if (typeof prismaInstance[method] === 'function') {
181
+ shimmer.wrap(prismaInstance, method, function(original) {
182
+ return async function() {
183
+ const ctx = TraceContextManager.getCurrentContext();
184
+ if (!ctx) {
185
+ return original.apply(this, arguments);
186
+ }
187
+
188
+ const sql_step = new SqlStepX();
189
+ sql_step.start_time = ctx.getElapsedTime();
190
+ ctx.profile.push(sql_step);
191
+ ctx.footprint(`Prisma ${method} Start`);
192
+
193
+ ctx.sql_count = (ctx.sql_count || 0) + 1;
194
+
195
+ // SQL 쿼리 추출
196
+ let sql = "";
197
+ let psql = null;
198
+
199
+ if (arguments.length > 0) {
200
+ if (typeof arguments[0] === "object" && arguments[0].sql) {
201
+ // Tagged template으로 전달된 경우
202
+ sql = arguments[0].sql;
203
+ } else if (typeof arguments[0] === "string") {
204
+ // Raw string으로 전달된 경우
205
+ sql = arguments[0];
206
+ }
207
+ }
208
+
209
+ // SQL 파싱
210
+ if (sql && sql.length > 0) {
211
+ try {
212
+ psql = escapeLiteral(sql);
213
+ } catch (e) {
214
+ Logger.printError("WHATAP-303", "escapeliteral error", e, false);
215
+ }
216
+ } else {
217
+ sql = "";
218
+ psql = escapeLiteral(sql);
219
+ }
220
+
221
+ if (psql != null) {
222
+ sql_step.hash = psql.sql;
223
+ }
224
+ sql_step.dbc = dbc_hash;
225
+
226
+ var els = new EscapeLiteralSQL(sql);
227
+ els.process();
228
+
229
+ ctx.active_sqlhash = sql_step.hash;
230
+ ctx.active_dbc = sql_step.dbc;
231
+
232
+ // 파라미터 정보 추출
233
+ if (conf.profile_sql_param_enabled) {
234
+ var params = Array.from(arguments).slice(1);
235
+ sql_step.setTrue(1);
236
+ var crc = { value: 0 };
237
+ sql_step.p1 = toParamBytes(psql.param, crc);
238
+
239
+ if (params.length > 0) {
240
+ const result = params.map((param) => {
241
+ if (typeof param === "string") {
242
+ return `'${param}'`;
243
+ }
244
+ return param;
245
+ }).toString();
246
+ sql_step.p2 = toParamBytes(result, crc);
247
+ }
248
+
249
+ sql_step.pcrc = crc.value;
250
+ }
251
+
252
+ try {
253
+ const result = await original.apply(this, arguments);
254
+
255
+ // SELECT 쿼리의 결과셋 처리
256
+ if (method === "$queryRaw" || method === "$queryRawUnsafe") {
257
+ if (Array.isArray(result)) {
258
+ var result_step = new ResultSetStep();
259
+ result_step.start_time = ctx.getElapsedTime();
260
+ result_step.elapsed = 0;
261
+ result_step.fetch = result.length;
262
+ result_step.sqlhash = psql.sql;
263
+ result_step.dbc = dbc_hash;
264
+ ctx.profile.push(result_step);
265
+
266
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + result.length : result.length;
267
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
268
+
269
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
270
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
271
+
272
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
273
+ }
274
+ }
275
+
276
+ // UPDATE/INSERT/DELETE 쿼리 결과 처리
277
+ if ((method === "$executeRaw" || method === "$executeRawUnsafe") && typeof result === "number") {
278
+ sql_step.updated = result;
279
+ }
280
+
281
+ self._finishQuery(ctx, sql_step);
282
+ return result;
283
+ } catch (err) {
284
+ Logger.printError("WHATAP-304", `${method} error: ${err.message}`, err);
285
+
286
+ self._handleError(ctx, sql_step, err);
287
+
288
+ if (conf.trace_sql_error_stack && conf.trace_sql_error_depth) {
289
+ var traceDepth = conf.trace_sql_error_depth;
290
+ var errorStack = err.stack.split("\n");
291
+
292
+ if (errorStack.length > traceDepth) {
293
+ errorStack = errorStack.slice(0, traceDepth + 1);
294
+ }
295
+
296
+ ctx.error_message = errorStack.join("\n");
297
+ sql_step.error = ctx.error = StatError.addError("prisma-" + (err.code || "unknown"), err.message, ctx.service_hash, TextTypes.SQL, null);
298
+ }
299
+
300
+ throw err;
301
+ }
302
+ };
303
+ });
304
+ }
305
+ });
306
+ };
307
+
308
+ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
309
+ const self = this;
310
+
311
+ // 원본 $use 함수를 저장
312
+ const originalUse = prismaInstance.$use;
313
+
314
+ // 우리만의 미들웨어 추가
315
+ if (typeof originalUse === 'function') {
316
+ // 간단한 미들웨어 추가 (모든 쿼리를 추적)
317
+ prismaInstance.$use(async (params, next) => {
318
+ var result;
319
+ const ctx = TraceContextManager.getCurrentContext();
320
+ if (!ctx) {
321
+ return next(params);
322
+ }
323
+
324
+ var dbc_step = new DBCStep();
325
+ dbc_hash = HashUtil.hashFromString(dbc);
326
+ DataTextAgent.DBC.add(dbc_hash, dbc);
327
+ DataTextAgent.METHOD.add(dbc_hash, dbc);
328
+ DataTextAgent.ERROR.add(dbc_hash, dbc)
329
+
330
+ dbc_step.hash = dbc_hash;
331
+ dbc_step.start_time = ctx.getElapsedTime();
332
+ ctx.profile.push(dbc_step);
333
+
334
+ const sql_step = new SqlStepX();
335
+ sql_step.start_time = ctx.getElapsedTime();
336
+ ctx.profile.push(sql_step);
337
+
338
+ const modelName = params.model || 'unknown';
339
+ const action = params.action || 'unknown';
340
+
341
+ ctx.footprint(`Prisma ${modelName}.${action} Start`);
342
+ ctx.sql_count = (ctx.sql_count || 0) + 1;
343
+
344
+ // 사용된 모델과 액션으로 SQL 문자열 구성
345
+ const queryInfo = `Prisma ${action.toUpperCase()} ${modelName}`;
346
+ const queryHash = HashUtil.hashFromString(queryInfo);
347
+
348
+ DataTextAgent.SQL.add(queryHash, queryInfo);
349
+ sql_step.hash = queryHash;
350
+ sql_step.dbc = dbc_hash;
351
+
352
+ ctx.active_sqlhash = sql_step.hash;
353
+ ctx.active_dbc = sql_step.dbc;
354
+
355
+ // 쿼리 파라미터 정보 추출
356
+ if (conf.profile_sql_param_enabled) {
357
+ const paramsString = JSON.stringify(params.args || {});
358
+ sql_step.setTrue(1);
359
+ var crc = { value: 0 };
360
+ sql_step.p1 = toParamBytes(paramsString, crc);
361
+ sql_step.pcrc = crc.value;
362
+ }
363
+
364
+ try {
365
+ result = await next(params);
366
+
367
+ if (action === "queryRaw" || action === "executeRaw" || action === "queryRawUnsafe" || action === "executeRawUnsafe") {
368
+ // SQL 문자열 추출
369
+ let sqlString = "";
370
+ if (params.args && params.args.length > 0) {
371
+ // 첫 번째 인자가 문자열 배열인 경우 (tagged template)
372
+ if (params.args[0] && params.args[0].strings && Array.isArray(params.args[0].strings)) {
373
+ sqlString = params.args[0].strings.join('');
374
+ }
375
+ // 첫 번째 인자가 직접 문자열인 경우
376
+ else if (typeof params.args[0] === 'string') {
377
+ sqlString = params.args[0];
378
+ }
379
+ }
380
+
381
+ // SQL 문자열이 있으면 처리
382
+ if (sqlString && sqlString.length > 0) {
383
+ try {
384
+ var psql = escapeLiteral(sqlString);
385
+ if (psql != null) {
386
+ sql_step.hash = psql.sql;
387
+ // CRUD 타입 설정 (S: Select, U: Update 등)
388
+ // if (psql.type) {
389
+ // sql_step.crud = psql.type.charCodeAt(0);
390
+ // }
391
+ }
392
+
393
+ // 추가 SQL 정보 처리
394
+ var els = new EscapeLiteralSQL(sqlString);
395
+ els.process();
396
+
397
+ // SQL 파라미터 처리
398
+ if (conf.profile_sql_param_enabled) {
399
+ var params = params.args.slice(1);
400
+ sql_step.setTrue(1);
401
+ var crc = {value: 0};
402
+ sql_step.p1 = toParamBytes(psql.param, crc);
403
+
404
+ if (params && params.length > 0) {
405
+ const result = params.map((param) => {
406
+ if (typeof param === 'string') {
407
+ return `'${param}'`;
408
+ }
409
+ return param;
410
+ }).toString();
411
+ sql_step.p2 = toParamBytes(result, crc);
412
+ }
413
+ sql_step.pcrc = crc.value;
414
+ }
415
+ } catch (e) {
416
+ Logger.printError("WHATAP-305", "escapeLiteral error", e, false);
417
+ }
418
+ }
419
+
420
+ // Raw 쿼리 결과셋 처리
421
+ if ((action === "queryRaw" || action === "queryRawUnsafe") && result) {
422
+ let recordCount = 0;
423
+
424
+ if (Array.isArray(result)) {
425
+ recordCount = result.length;
426
+ } else if (result && typeof result === "object") {
427
+ recordCount = 1;
428
+ }
429
+
430
+ if (recordCount > 0) {
431
+ // 가장 최근에 처리된 SQL 해시 사용 (Raw 쿼리에서 업데이트된 경우)
432
+ const sqlHashToUse = psql ? psql.sql : queryHash;
433
+
434
+ var result_step = new ResultSetStep();
435
+ result_step.start_time = ctx.getElapsedTime();
436
+ result_step.elapsed = 0;
437
+ result_step.fetch = recordCount;
438
+ result_step.sqlhash = sqlHashToUse;
439
+ result_step.dbc = dbc_hash;
440
+ ctx.profile.push(result_step);
441
+
442
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
443
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
444
+
445
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
446
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
447
+
448
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
449
+ }
450
+ }
451
+ }
452
+
453
+ // 결과셋 처리 (findMany, findFirst, findUnique 등)
454
+ if (["findMany", "findFirst", "findUnique"].includes(action)) {
455
+ let recordCount = 0;
456
+
457
+ if (Array.isArray(result)) {
458
+ recordCount = result.length;
459
+ } else if (result && typeof result === "object") {
460
+ recordCount = 1;
461
+ }
462
+
463
+ if (recordCount > 0) {
464
+ var result_step = new ResultSetStep();
465
+ result_step.start_time = ctx.getElapsedTime();
466
+ result_step.elapsed = 0;
467
+ result_step.fetch = recordCount;
468
+ result_step.sqlhash = queryHash;
469
+ result_step.dbc = dbc_hash;
470
+ ctx.profile.push(result_step);
471
+
472
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
473
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
474
+
475
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
476
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
477
+
478
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
479
+ }
480
+ }
481
+
482
+ // 수정된 레코드 수 처리 (create, update, delete 등)
483
+ if (["create", "createMany", "update", "updateMany", "delete", "deleteMany"].includes(action)) {
484
+ if (result && result.count !== undefined) {
485
+ sql_step.updated = result.count;
486
+ } else if (result && typeof result === "object") {
487
+ sql_step.updated = 1;
488
+ }
489
+ }
490
+
491
+ // findUnique, create 등의 메서드에 대한 SQL 형식의 정보 구성
492
+ if (modelName !== 'unknown' && action !== 'queryRaw' && action !== 'executeRaw' &&
493
+ action !== 'queryRawUnsafe' && action !== 'executeRawUnsafe') {
494
+
495
+ try {
496
+ // 모델 메서드를 SQL 스타일로 변환
497
+ let sqlStyleQuery = "";
498
+
499
+ // 액션에 따라 SQL 명령 결정
500
+ if (action.startsWith('find')) {
501
+ sqlStyleQuery = "SELECT ";
502
+
503
+ // select 필드가 있는 경우 해당 필드 사용
504
+ if (params.args && params.args.select) {
505
+ const fields = Object.keys(params.args.select)
506
+ .filter(key => params.args.select[key] === true);
507
+
508
+ if (fields.length > 0) {
509
+ sqlStyleQuery += fields.join(", ");
510
+ } else {
511
+ sqlStyleQuery += "*";
512
+ }
513
+ } else {
514
+ sqlStyleQuery += "*";
515
+ }
516
+
517
+ sqlStyleQuery += ` FROM ${modelName}`;
518
+
519
+ // where 조건 추가
520
+ if (params.args && params.args.where) {
521
+ const conditions = [];
522
+ for (const [key, value] of Object.entries(params.args.where)) {
523
+ if (typeof value === 'object' && value !== null) {
524
+ // 복합 조건(contains, startsWith 등)
525
+ for (const [op, val] of Object.entries(value)) {
526
+ let sqlOp = "";
527
+
528
+ // Prisma 연산자를 SQL 연산자로 변환
529
+ switch (op) {
530
+ case 'equals': sqlOp = '='; break;
531
+ case 'not': sqlOp = '!='; break;
532
+ case 'in': sqlOp = 'IN'; break;
533
+ case 'notIn': sqlOp = 'NOT IN'; break;
534
+ case 'lt': sqlOp = '<'; break;
535
+ case 'lte': sqlOp = '<='; break;
536
+ case 'gt': sqlOp = '>'; break;
537
+ case 'gte': sqlOp = '>='; break;
538
+ case 'contains': sqlOp = 'LIKE'; break;
539
+ case 'startsWith': sqlOp = 'LIKE'; break;
540
+ case 'endsWith': sqlOp = 'LIKE'; break;
541
+ default: sqlOp = op;
542
+ }
543
+
544
+ let sqlVal = val;
545
+ if (op === 'contains') {
546
+ sqlVal = `'%${val}%'`;
547
+ } else if (op === 'startsWith') {
548
+ sqlVal = `'${val}%'`;
549
+ } else if (op === 'endsWith') {
550
+ sqlVal = `'%${val}'`;
551
+ } else if (typeof val === 'string') {
552
+ sqlVal = `'${val}'`;
553
+ } else if (Array.isArray(val)) {
554
+ sqlVal = `(${val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ')})`;
555
+ }
556
+
557
+ conditions.push(`${key} ${sqlOp} ${sqlVal}`);
558
+ }
559
+ } else {
560
+ // 단순 조건
561
+ let formattedValue = typeof value === 'string' ? `'${value}'` : value;
562
+ conditions.push(`${key} = ${formattedValue}`);
563
+ }
564
+ }
565
+
566
+ if (conditions.length > 0) {
567
+ sqlStyleQuery += ` WHERE ${conditions.join(' AND ')}`;
568
+ }
569
+ }
570
+
571
+ // 정렬 조건 추가
572
+ if (params.args && params.args.orderBy) {
573
+ const orderClauses = [];
574
+
575
+ if (Array.isArray(params.args.orderBy)) {
576
+ for (const item of params.args.orderBy) {
577
+ for (const [field, dir] of Object.entries(item)) {
578
+ orderClauses.push(`${field} ${dir}`);
579
+ }
580
+ }
581
+ } else {
582
+ for (const [field, dir] of Object.entries(params.args.orderBy)) {
583
+ orderClauses.push(`${field} ${dir}`);
584
+ }
585
+ }
586
+
587
+ if (orderClauses.length > 0) {
588
+ sqlStyleQuery += ` ORDER BY ${orderClauses.join(', ')}`;
589
+ }
590
+ }
591
+
592
+ // 페이징 정보 추가
593
+ if (params.args) {
594
+ if (params.args.skip !== undefined) {
595
+ sqlStyleQuery += ` OFFSET ${params.args.skip}`;
596
+ }
597
+
598
+ if (params.args.take !== undefined) {
599
+ sqlStyleQuery += ` LIMIT ${params.args.take}`;
600
+ }
601
+ }
602
+
603
+ } else if (action.startsWith('create')) {
604
+ sqlStyleQuery = `INSERT INTO ${modelName}`;
605
+
606
+ if (params.args && params.args.data) {
607
+ const columns = [];
608
+ const values = [];
609
+
610
+ for (const [key, val] of Object.entries(params.args.data)) {
611
+ columns.push(key);
612
+ if (typeof val === 'string') {
613
+ values.push(`'${val}'`);
614
+ } else if (val === null) {
615
+ values.push('NULL');
616
+ } else if (typeof val === 'object') {
617
+ values.push(`'${JSON.stringify(val)}'`);
618
+ } else {
619
+ values.push(val);
620
+ }
621
+ }
622
+
623
+ sqlStyleQuery += ` (${columns.join(', ')}) VALUES (${values.join(', ')})`;
624
+ }
625
+
626
+ } else if (action.startsWith('update')) {
627
+ sqlStyleQuery = `UPDATE ${modelName}`;
628
+
629
+ if (params.args && params.args.data) {
630
+ const setExpressions = [];
631
+
632
+ for (const [key, val] of Object.entries(params.args.data)) {
633
+ let formattedValue;
634
+ if (typeof val === 'string') {
635
+ formattedValue = `'${val}'`;
636
+ } else if (val === null) {
637
+ formattedValue = 'NULL';
638
+ } else if (typeof val === 'object') {
639
+ formattedValue = `'${JSON.stringify(val)}'`;
640
+ } else {
641
+ formattedValue = val;
642
+ }
643
+
644
+ setExpressions.push(`${key} = ${formattedValue}`);
645
+ }
646
+
647
+ if (setExpressions.length > 0) {
648
+ sqlStyleQuery += ` SET ${setExpressions.join(', ')}`;
649
+ }
650
+ }
651
+
652
+ // where 조건 추가
653
+ if (params.args && params.args.where) {
654
+ const conditions = [];
655
+ for (const [key, value] of Object.entries(params.args.where)) {
656
+ if (typeof value === 'object' && value !== null) {
657
+ for (const [op, val] of Object.entries(value)) {
658
+ let sqlOp = op === 'equals' ? '=' : op;
659
+ let sqlVal = typeof val === 'string' ? `'${val}'` : val;
660
+ conditions.push(`${key} ${sqlOp} ${sqlVal}`);
661
+ }
662
+ } else {
663
+ let formattedValue = typeof value === 'string' ? `'${value}'` : value;
664
+ conditions.push(`${key} = ${formattedValue}`);
665
+ }
666
+ }
667
+
668
+ if (conditions.length > 0) {
669
+ sqlStyleQuery += ` WHERE ${conditions.join(' AND ')}`;
670
+ }
671
+ }
672
+
673
+ } else if (action.startsWith('delete')) {
674
+ sqlStyleQuery = `DELETE FROM ${modelName}`;
675
+
676
+ // where 조건 추가
677
+ if (params.args && params.args.where) {
678
+ const conditions = [];
679
+ for (const [key, value] of Object.entries(params.args.where)) {
680
+ if (typeof value === 'object' && value !== null) {
681
+ for (const [op, val] of Object.entries(value)) {
682
+ let sqlOp = op === 'equals' ? '=' : op;
683
+ let sqlVal = typeof val === 'string' ? `'${val}'` : val;
684
+ conditions.push(`${key} ${sqlOp} ${sqlVal}`);
685
+ }
686
+ } else {
687
+ let formattedValue = typeof value === 'string' ? `'${value}'` : value;
688
+ conditions.push(`${key} = ${formattedValue}`);
689
+ }
690
+ }
691
+
692
+ if (conditions.length > 0) {
693
+ sqlStyleQuery += ` WHERE ${conditions.join(' AND ')}`;
694
+ }
695
+ }
696
+ } else {
697
+ // 기타 액션은 기본 형식으로
698
+ sqlStyleQuery = `${action.toUpperCase()} ${modelName}`;
699
+
700
+ if (params.args) {
701
+ sqlStyleQuery += ` ${JSON.stringify(params.args)}`;
702
+ }
703
+ }
704
+
705
+ // 생성된 SQL 쿼리에 escapeLiteral 적용하여 처리
706
+ try {
707
+ var psql = escapeLiteral(sqlStyleQuery);
708
+ if (psql != null) {
709
+ sql_step.hash = psql.sql;
710
+ // CRUD 타입 설정
711
+ if (psql.type) {
712
+ sql_step.crud = psql.type.charCodeAt(0);
713
+ }
714
+ }
715
+
716
+ // 추가 SQL 정보 처리
717
+ var els = new EscapeLiteralSQL(sqlStyleQuery);
718
+ els.process();
719
+
720
+ // SQL 파라미터 처리
721
+ if (conf.profile_sql_param_enabled) {
722
+ sql_step.setTrue(1);
723
+ var crc = {value: 0};
724
+ sql_step.p1 = toParamBytes(psql.param, crc);
725
+
726
+ // 원래 인자를 파라미터로 추가
727
+ const paramsString = JSON.stringify(params.args || {});
728
+ sql_step.p2 = toParamBytes(paramsString, crc);
729
+ sql_step.pcrc = crc.value;
730
+ }
731
+ } catch (e) {
732
+ Logger.printError("WHATAP-306", "escapeLiteral error for model method", e, false);
733
+ }
734
+ } catch (e) {
735
+ Logger.printError("WHATAP-307", "Error creating SQL-style query", e, false);
736
+ }
737
+ }
738
+
739
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
740
+ ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
741
+
742
+ TraceSQL.isSlowSQL(ctx);
743
+
744
+ MeterSql.add(dbc_hash, sql_step.elapsed, false);
745
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, false, 0);
746
+
747
+ ctx.footprint(`Prisma ${modelName}.${action} Done`);
748
+
749
+ return result;
750
+ } catch (err) {
751
+ Logger.printError("WHATAP-308", `Middleware error in ${modelName}.${action}: ${err.message}`, err, false);
752
+
753
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
754
+ ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
755
+
756
+ TraceSQL.isSlowSQL(ctx);
757
+
758
+ MeterSql.add(dbc_hash, sql_step.elapsed, true);
759
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, true, 0);
760
+
761
+ // 에러 처리
762
+ try {
763
+ sql_step.error = StatError.addError("prisma-" + (err.code || "unknown"), err.message, ctx.service_hash, TextTypes.SQL, sql_step.hash);
764
+
765
+ if (ctx.error.isZero()) {
766
+ ctx.error = sql_step.error;
767
+ }
768
+
769
+ if (conf.trace_sql_error_stack && conf.trace_sql_error_depth) {
770
+ var traceDepth = conf.trace_sql_error_depth;
771
+ var errorStack = err.stack.split("\n");
772
+
773
+ if (errorStack.length > traceDepth) {
774
+ errorStack = errorStack.slice(0, traceDepth + 1);
775
+ }
776
+
777
+ ctx.error_message = errorStack.join("\n");
778
+ }
779
+ } catch (e) {
780
+ Logger.printError("WHATAP-309", "Error handling failed", e, false);
781
+ }
782
+
783
+ ctx.footprint(`Prisma ${modelName}.${action} Error`);
784
+ throw err;
785
+ }
786
+ });
787
+ }
788
+ };
789
+
790
+ // 모델 메서드 직접 후킹
791
+ // PrismaObserver.prototype.hookModelMethods = function(prismaInstance) {
792
+ // const self = this;
793
+ //
794
+ // // Prisma에서 모든 모델 가져오기
795
+ // let models = [];
796
+ // try {
797
+ // // DMMF를 통해 모델 이름 얻기
798
+ // if (prismaInstance._baseDmmf && prismaInstance._baseDmmf.modelMap) {
799
+ // models = Object.keys(prismaInstance._baseDmmf.modelMap);
800
+ // } else if (prismaInstance._dmmf && prismaInstance._dmmf.modelMap) {
801
+ // models = Object.keys(prismaInstance._dmmf.modelMap);
802
+ // }
803
+ // } catch (e) {
804
+ // Logger.printError("WHATAP-PRISMA", "Failed to get models", e, false);
805
+ // }
806
+ //
807
+ // // 모델 메서드 목록
808
+ // const methods = [
809
+ // "findUnique", "findFirst", "findMany",
810
+ // "create", "createMany",
811
+ // "update", "updateMany",
812
+ // "upsert",
813
+ // "delete", "deleteMany",
814
+ // "count", "aggregate", "groupBy"
815
+ // ];
816
+ //
817
+ // Logger.print("WHATAP-PRISMA", `Found models: ${models.join(', ')}`, false);
818
+ //
819
+ // // 각 모델에 대해 메서드 후킹
820
+ // models.forEach(model => {
821
+ // if (prismaInstance[model]) {
822
+ // methods.forEach(method => {
823
+ // if (typeof prismaInstance[model][method] === 'function') {
824
+ // shimmer.wrap(prismaInstance[model], method, function(original) {
825
+ // return async function() {
826
+ // const ctx = TraceContextManager.getCurrentContext();
827
+ // if (!ctx) {
828
+ // return original.apply(this, arguments);
829
+ // }
830
+ //
831
+ // Logger.print("WHATAP-PRISMA", `Direct model method called: ${model}.${method}`, false);
832
+ //
833
+ // const sql_step = new SqlStepX();
834
+ // sql_step.start_time = ctx.getElapsedTime();
835
+ // ctx.profile.push(sql_step);
836
+ // ctx.footprint(`Prisma ${model}.${method} Start (Direct)`);
837
+ //
838
+ // ctx.sql_count = (ctx.sql_count || 0) + 1;
839
+ //
840
+ // // 쿼리 정보
841
+ // const queryInfo = `${method.toUpperCase()} ${model}`;
842
+ // const queryHash = HashUtil.hashFromString(queryInfo);
843
+ //
844
+ // DataTextAgent.SQL.add(queryHash, queryInfo);
845
+ // sql_step.hash = queryHash;
846
+ // sql_step.dbc = dbc_hash;
847
+ //
848
+ // ctx.active_sqlhash = sql_step.hash;
849
+ // ctx.active_dbc = sql_step.dbc;
850
+ //
851
+ // // 쿼리 파라미터
852
+ // if (conf.profile_sql_param_enabled) {
853
+ // const argsString = JSON.stringify(arguments[0] || {});
854
+ // sql_step.setTrue(1);
855
+ // var crc = { value: 0 };
856
+ // sql_step.p1 = toParamBytes(argsString, crc);
857
+ // sql_step.pcrc = crc.value;
858
+ // }
859
+ //
860
+ // try {
861
+ // const result = await original.apply(this, arguments);
862
+ //
863
+ // // 결과셋 처리
864
+ // if (["findMany", "findFirst", "findUnique"].includes(method)) {
865
+ // let recordCount = 0;
866
+ //
867
+ // if (Array.isArray(result)) {
868
+ // recordCount = result.length;
869
+ // } else if (result && typeof result === "object") {
870
+ // recordCount = 1;
871
+ // }
872
+ //
873
+ // if (recordCount > 0) {
874
+ // var result_step = new ResultSetStep();
875
+ // result_step.start_time = ctx.getElapsedTime();
876
+ // result_step.elapsed = 0;
877
+ // result_step.fetch = recordCount;
878
+ // result_step.sqlhash = queryHash;
879
+ // result_step.dbc = dbc_hash;
880
+ // ctx.profile.push(result_step);
881
+ //
882
+ // ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
883
+ // ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
884
+ //
885
+ // MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
886
+ // StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
887
+ //
888
+ // TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
889
+ // }
890
+ // }
891
+ //
892
+ // // 수정된 레코드 처리
893
+ // if (["create", "createMany", "update", "updateMany", "delete", "deleteMany"].includes(method)) {
894
+ // if (result && result.count !== undefined) {
895
+ // sql_step.updated = result.count;
896
+ // } else if (result && typeof result === "object") {
897
+ // sql_step.updated = 1;
898
+ // }
899
+ // }
900
+ //
901
+ // sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
902
+ // ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
903
+ //
904
+ // TraceSQL.isSlowSQL(ctx);
905
+ //
906
+ // MeterSql.add(dbc_hash, sql_step.elapsed, false);
907
+ // StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, false, 0);
908
+ //
909
+ // ctx.footprint(`Prisma ${model}.${method} Done (Direct)`);
910
+ // Logger.print("WHATAP-PRISMA", `Direct model method completed: ${model}.${method}`, false);
911
+ //
912
+ // return result;
913
+ // } catch (err) {
914
+ // Logger.printError("WHATAP-PRISMA", `Direct model method error in ${model}.${method}: ${err.message}`, err);
915
+ //
916
+ // sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
917
+ // ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
918
+ //
919
+ // TraceSQL.isSlowSQL(ctx);
920
+ //
921
+ // MeterSql.add(dbc_hash, sql_step.elapsed, true);
922
+ // StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, true, 0);
923
+ //
924
+ // try {
925
+ // sql_step.error = StatError.addError("prisma-" + (err.code || "unknown"), err.message, ctx.service_hash, TextTypes.SQL, sql_step.hash);
926
+ //
927
+ // if (ctx.error.isZero()) {
928
+ // ctx.error = sql_step.error;
929
+ // }
930
+ //
931
+ // if (conf.trace_sql_error_stack && conf.trace_sql_error_depth) {
932
+ // var traceDepth = conf.trace_sql_error_depth;
933
+ // var errorStack = err.stack.split("\n");
934
+ //
935
+ // if (errorStack.length > traceDepth) {
936
+ // errorStack = errorStack.slice(0, traceDepth + 1);
937
+ // }
938
+ //
939
+ // ctx.error_message = errorStack.join("\n");
940
+ // }
941
+ // } catch (e) {
942
+ // Logger.printError("WHATAP-PRISMA", "Error handling failed", e, false);
943
+ // }
944
+ //
945
+ // ctx.footprint(`Prisma ${model}.${method} Error (Direct)`);
946
+ // throw err;
947
+ // }
948
+ // };
949
+ // });
950
+ // }
951
+ // });
952
+ // }
953
+ // });
954
+ // };
955
+
956
+ PrismaObserver.prototype._finishQuery = function(ctx, sql_step) {
957
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
958
+ ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
959
+
960
+ TraceSQL.isSlowSQL(ctx);
961
+
962
+ MeterSql.add(dbc_hash, sql_step.elapsed, false);
963
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, false, 0);
964
+
965
+ ctx.footprint("Prisma Query Done");
966
+ };
967
+
968
+ PrismaObserver.prototype._handleError = function(ctx, sql_step, err) {
969
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
970
+ ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
971
+
972
+ TraceSQL.isSlowSQL(ctx);
973
+
974
+ MeterSql.add(dbc_hash, sql_step.elapsed, true);
975
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, true, 0);
976
+
977
+ try {
978
+ sql_step.error = StatError.addError("prisma-" + (err.code || "unknown"), err.message, ctx.service_hash, TextTypes.SQL, sql_step.hash);
979
+
980
+ if (ctx.error.isZero()) {
981
+ ctx.error = sql_step.error;
982
+ }
983
+ } catch (e) {
984
+ Logger.printError("WHATAP-310", "Error handling failed", e, false);
985
+ }
986
+
987
+ ctx.footprint("Prisma Query Error");
988
+ };
989
+
990
+ var toParamBytes = function(p, crc) {
991
+ if (p == null || p.length === 0) {
992
+ return null;
993
+ }
994
+ try {
995
+ return ParamSecurity.encrypt(Buffer.from(p, "utf8"), crc);
996
+ } catch (e) {
997
+ return null;
998
+ }
999
+ };
1000
+
1001
+ var checkedSql = new IntKeyMap(2000).setMax(2000);
1002
+ var nonLiteSql = new IntKeyMap(5000).setMax(5000);
1003
+ var date = DateUtil.yyyymmdd();
1004
+
1005
+ function escapeLiteral(sql) {
1006
+ if (sql == null) {
1007
+ sql = "";
1008
+ }
1009
+
1010
+ if (date !== DateUtil.yyyymmdd()) {
1011
+ checkedSql.clear();
1012
+ nonLiteSql.clear();
1013
+ date = DateUtil.yyyymmdd();
1014
+ Logger.print("WHATAP-SQL-CLEAR", "PrismaObserver CLEAR OK!!!!!!!!!!!!!!!!", false);
1015
+ }
1016
+
1017
+ var sqlHash = HashUtil.hashFromString(sql);
1018
+ var psql = nonLiteSql.get(sqlHash);
1019
+
1020
+ if (psql != null) {
1021
+ return psql;
1022
+ }
1023
+ psql = checkedSql.get(sqlHash);
1024
+
1025
+ if (psql != null) {
1026
+ return psql;
1027
+ }
1028
+
1029
+ var els = new EscapeLiteralSQL(sql);
1030
+ els.process();
1031
+
1032
+ var hash = HashUtil.hashFromString(els.getParsedSql());
1033
+ DataTextAgent.SQL.add(hash, els.getParsedSql());
1034
+
1035
+ if (hash === sqlHash) {
1036
+ psql = new ParsedSql(els.sqlType, hash, null);
1037
+ nonLiteSql.put(sqlHash, psql);
1038
+ } else {
1039
+ psql = new ParsedSql(els.sqlType, hash, els.getParameter());
1040
+ checkedSql.put(sqlHash, psql);
1041
+ }
1042
+ return psql;
1043
+ }
1044
+
1045
+ exports.PrismaObserver = PrismaObserver;
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.7",
5
- "releaseDate": "20250207",
4
+ "version": "0.5.9",
5
+ "releaseDate": "20250305",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},