whatap 0.5.12 → 0.5.13

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.
package/lib/core/agent.js CHANGED
@@ -32,7 +32,9 @@ var Interceptor = require('./interceptor').Interceptor,
32
32
  ScheduleObserver = require('../observers/schedule-observer').ScheduleObserver,
33
33
  // GRpcObserver = require('../observers/grpc-observer').GRpcObserver,
34
34
  ApolloObserver = require('../observers/apollo-server-observer').ApolloServerObserver,
35
- PrismaObserver = require('../observers/prisma-observer').PrismaObserver;
35
+ PrismaObserver = require('../observers/prisma-observer').PrismaObserver,
36
+ OracleObserver = require('../observers/oracle-observer').OracleObserver;
37
+
36
38
 
37
39
 
38
40
  var Configuration = require('./../conf/configure'),
@@ -279,6 +281,7 @@ NodeAgent.prototype.loadObserves = function() {
279
281
  // observes.push(GRpcObserver);
280
282
  observes.push(ApolloObserver);
281
283
  observes.push(PrismaObserver);
284
+ observes.push(OracleObserver);
282
285
 
283
286
  var packageToObserve = {};
284
287
  observes.forEach(function(observeObj) {
@@ -513,6 +513,7 @@ HttpObserver.prototype.__endTransaction = function(error, ctx, req, res) {
513
513
  if(wtx.malloc < 0) { wtx.malloc = 0; }
514
514
  wtx.originUrl = ctx.originUrl;
515
515
 
516
+ wtx.cipher = HashUtil.hash(ParamSecurity.key);
516
517
  wtx.seq = ctx.txid;
517
518
  wtx.sqlCount = ctx.sql_count;
518
519
  wtx.sqlTime = ctx.sql_time;
@@ -0,0 +1,656 @@
1
+ /**
2
+ * Copyright 2023 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
+ ParsedSql = require('../trace/parsed-sql'),
9
+ SqlStepX = require('../step/sql-stepx'),
10
+ DBCStep = require('../step/dbc-step'),
11
+ ResultSetStep = require('../step/resultset-step'),
12
+ DataTextAgent = require('../data/datatext-agent'),
13
+ StatSql = require('../stat/stat-sql'),
14
+ MeterSql = require('../counter/meter/meter-sql'),
15
+ conf = require('../conf/configure'),
16
+ IntKeyMap = require('../util/intkey-map'),
17
+ EscapeLiteralSQL = require('../util/escape-literal-sql'),
18
+ HashUtil = require('../util/hashutil'),
19
+ StatError = require('../stat/stat-error'),
20
+ TextTypes = require('../lang/text-types'),
21
+ ParamSecurity = require('../util/paramsecurity'),
22
+ Logger = require('../logger'),
23
+ DateUtil = require('../util/dateutil'),
24
+ Buffer = require('buffer').Buffer,
25
+ TraceSQL = require('../trace/trace-sql');
26
+ var shimmer = require('../core/shimmer');
27
+
28
+ // Store connection info for reuse
29
+ var connectionAttributes = new WeakMap();
30
+ var poolAttributes = new WeakMap();
31
+
32
+ var OracleObserver = function (agent) {
33
+ this.agent = agent;
34
+ this.packages = ['oracledb'];
35
+ };
36
+
37
+ // 쿼리 파라미터를 바이트 배열로 변환
38
+ var toParamBytes = function (p, crc) {
39
+ if (p == null || p.length === 0) {
40
+ return null;
41
+ }
42
+ try {
43
+ return ParamSecurity.encrypt(Buffer.from(p, 'utf8'), crc);
44
+ } catch (e) {
45
+ return null;
46
+ }
47
+ };
48
+
49
+ var errorDelegate = function (ctx, step) {
50
+ return function (obj, args) {
51
+ if (ctx == null) {
52
+ return;
53
+ }
54
+
55
+ if (step == null) {
56
+ var laststep = ctx.profile.getLastSteps(1);
57
+ if (laststep == null || laststep.length === 0) {
58
+ return;
59
+ }
60
+ step = laststep[0];
61
+ }
62
+
63
+ MeterSql.add(step.dbc, step.elapsed, true);
64
+ StatSql.addSqlTime(ctx.service_hash, step.dbc, step.hash, step.elapsed, true, 0);
65
+
66
+ try {
67
+ var errorCode = args[0].errorNum || args[0].code || 'ORA-00000';
68
+ var errorMsg = args[0].message || 'Oracle error';
69
+
70
+ if (conf._is_trace_ignore_err_cls_contains === true && errorCode.toString().indexOf(conf.trace_ignore_err_cls_contains) < 0) {
71
+ step.error = StatError.addError('oracle-' + errorCode, errorMsg, ctx.service_hash, TextTypes.SQL, step.hash);
72
+ if (ctx.error.isZero()) {
73
+ ctx.error = step.error;
74
+ }
75
+ } else if (conf._is_trace_ignore_err_msg_contains === true && errorMsg.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
76
+ step.error = StatError.addError('oracle-' + errorCode, errorMsg, ctx.service_hash, TextTypes.SQL, step.hash);
77
+ if (ctx.error.isZero()) {
78
+ ctx.error = step.error;
79
+ }
80
+ } else if (conf._is_trace_ignore_err_cls_contains === false && conf._is_trace_ignore_err_msg_contains === false) {
81
+ step.error = StatError.addError('oracle-' + errorCode, errorMsg, ctx.service_hash, TextTypes.SQL, step.hash);
82
+ if (ctx.error.isZero()) {
83
+ ctx.error = step.error;
84
+ }
85
+ }
86
+
87
+ if (conf.trace_sql_error_stack && conf.trace_sql_error_depth) {
88
+ var traceDepth = conf.trace_sql_error_depth;
89
+ if (args[0].stack) {
90
+ var errorStack = args[0].stack.split("\n");
91
+ if (errorStack.length > traceDepth) {
92
+ errorStack = errorStack.slice(0, traceDepth + 1);
93
+ }
94
+ ctx.error_message = errorStack.join("\n");
95
+ }
96
+ }
97
+ } catch (e) {
98
+ Logger.printError('WHATAP-110', 'OracleObserver error delegate exception', e, false);
99
+ }
100
+ };
101
+ };
102
+
103
+ // 커넥션 풀 관리 함수
104
+ OracleObserver.prototype._wrapPoolOrConnection = function (obj, dbc) {
105
+ var self = this;
106
+
107
+ if (obj.__whatap_observed) {
108
+ return;
109
+ }
110
+ obj.__whatap_observed = true;
111
+
112
+ // execute 메소드 래핑
113
+ if (obj.execute && typeof obj.execute === 'function') {
114
+ shimmer.wrap(obj, 'execute', function (original) {
115
+ return function () {
116
+ var ctx = TraceContextManager.getCurrentContext();
117
+ var args = Array.from(arguments);
118
+
119
+ if (ctx == null || args.length === 0) {
120
+ return original.apply(this, args);
121
+ }
122
+
123
+ var sql = args[0];
124
+ if (sql == null) {
125
+ return original.apply(this, args);
126
+ }
127
+
128
+ // DBC 단계 생성
129
+ var dbc_hash = HashUtil.hashFromString(dbc);
130
+
131
+ // SQL 단계 생성
132
+ var sql_step = new SqlStepX();
133
+ sql_step.start_time = ctx.getElapsedTime();
134
+ ctx.profile.push(sql_step);
135
+
136
+ ctx.footprint('Oracle Query Start');
137
+ ctx.sql_count++;
138
+
139
+ var psql = null;
140
+ if (typeof sql === 'string' && sql.length > 0) {
141
+ try {
142
+ psql = escapeLiteral(sql);
143
+ } catch (e) {
144
+ Logger.printError('WHATAP-111', 'OracleObserver escapeliteral error', e, false);
145
+ }
146
+ } else {
147
+ sql = '';
148
+ psql = escapeLiteral(sql);
149
+ }
150
+
151
+ if (psql != null) {
152
+ sql_step.hash = psql.sql;
153
+ // sql_step.crud = psql.type.charCodeAt(0);
154
+ }
155
+ sql_step.dbc = dbc_hash;
156
+
157
+ var els = new EscapeLiteralSQL(sql);
158
+ els.process();
159
+
160
+ ctx.active_sqlhash = sql_step.hash;
161
+ ctx.active_dbc = sql_step.dbc;
162
+ //ctx.active_crud = sql_step.crud;
163
+
164
+ // 파라미터 처리
165
+ if (conf.profile_sql_param_enabled) {
166
+ var params = args.length > 1 ? args[1] : undefined;
167
+ if (params) {
168
+ sql_step.setTrue(1);
169
+ var crc = {value: 0};
170
+ sql_step.p1 = toParamBytes(psql.param, crc);
171
+
172
+ if (params != undefined) {
173
+ try {
174
+ // Oracle 파라미터는 객체 형태일 수 있음
175
+ let paramStr = '';
176
+ if (typeof params === 'object' && !Array.isArray(params)) {
177
+ paramStr = Object.entries(params).map(([key, value]) => {
178
+ if (typeof value === 'string') {
179
+ return `${key}='${value}'`;
180
+ }
181
+ return `${key}=${value}`;
182
+ }).join(', ');
183
+ } else if (Array.isArray(params)) {
184
+ paramStr = params.map((param) => {
185
+ if (typeof param === 'string') {
186
+ return `'${param}'`;
187
+ }
188
+ return param;
189
+ }).join(', ');
190
+ } else {
191
+ paramStr = String(params);
192
+ }
193
+ sql_step.p2 = toParamBytes(paramStr, crc);
194
+ } catch (e) {
195
+ Logger.printError('WHATAP-112', 'Oracle param processing error', e, false);
196
+ }
197
+ }
198
+ sql_step.pcrc = crc.value;
199
+ }
200
+ }
201
+
202
+ // Check if the last argument is a callback
203
+ var hasCallback = false;
204
+ var callbackIndex = -1;
205
+
206
+ if (args.length > 0 && typeof args[args.length - 1] === 'function') {
207
+ hasCallback = true;
208
+ callbackIndex = args.length - 1;
209
+
210
+ // Wrap callback to capture result
211
+ var originalCallback = args[callbackIndex];
212
+ args[callbackIndex] = function wrappedCallback(err, result) {
213
+ if (ctx == null) {
214
+ return originalCallback.apply(this, arguments);
215
+ }
216
+
217
+ TraceContextManager.resume(ctx._id);
218
+
219
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
220
+ ctx.sql_time += sql_step.elapsed;
221
+
222
+ if (err) {
223
+ errorDelegate(ctx, sql_step)(null, [err]);
224
+ ctx.footprint('Oracle Query Error');
225
+ } else {
226
+ ctx.footprint('Oracle Query Done');
227
+
228
+ // Process result for SELECT queries
229
+ if (result && result.rows && Array.isArray(result.rows) && psql != null && psql.type === 'S') {
230
+ var result_step = new ResultSetStep();
231
+ result_step.start_time = ctx.getElapsedTime();
232
+ result_step.elapsed = 0;
233
+ result_step.fetch = result.rows.length;
234
+ result_step.sqlhash = psql.sql;
235
+ result_step.dbc = dbc_hash;
236
+ ctx.profile.push(result_step);
237
+
238
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + result.rows.length : result.rows.length;
239
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
240
+
241
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
242
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
243
+
244
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
245
+ }
246
+
247
+ // Process affectedRows for UPDATE/INSERT/DELETE
248
+ if (result != null && psql != null && (psql.type === 'U' || psql.type === 'I' || psql.type === 'D')) {
249
+ if (result.rowsAffected !== undefined) {
250
+ sql_step.updated = result.rowsAffected || 0;
251
+ }
252
+ }
253
+ }
254
+
255
+ MeterSql.add(dbc_hash, sql_step.elapsed, !!err);
256
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc,
257
+ sql_step.hash, sql_step.elapsed, !!err, 0);
258
+
259
+ return originalCallback.apply(this, arguments);
260
+ };
261
+ }
262
+
263
+ try {
264
+ // Handle promise-based execution
265
+ if (!hasCallback) {
266
+ return original.apply(this, args)
267
+ .then(function (result) {
268
+ // 성공 처리
269
+ if (ctx == null) {
270
+ return result;
271
+ }
272
+
273
+ TraceContextManager.resume(ctx._id);
274
+
275
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
276
+ ctx.sql_time += sql_step.elapsed;
277
+
278
+ TraceSQL.isSlowSQL(ctx);
279
+
280
+ ctx.footprint('Oracle Query Done');
281
+
282
+ MeterSql.add(dbc_hash, sql_step.elapsed, false);
283
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc,
284
+ sql_step.hash, sql_step.elapsed, false, 0);
285
+
286
+ if (result && result.rows && Array.isArray(result.rows) && psql != null && psql.type === 'S') {
287
+ var result_step = new ResultSetStep();
288
+ result_step.start_time = ctx.getElapsedTime();
289
+ result_step.elapsed = 0;
290
+ result_step.fetch = result.rows.length;
291
+ result_step.sqlhash = psql.sql;
292
+ result_step.dbc = dbc_hash;
293
+ ctx.profile.push(result_step);
294
+
295
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + result.rows.length : result.rows.length;
296
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
297
+
298
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
299
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
300
+
301
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
302
+ }
303
+
304
+ if (result != null && psql != null && (psql.type === 'U' || psql.type === 'I' || psql.type === 'D')) {
305
+ if (result.rowsAffected !== undefined) {
306
+ sql_step.updated = result.rowsAffected || 0;
307
+ }
308
+ }
309
+
310
+ return result;
311
+ })
312
+ .catch(function (error) {
313
+ if (ctx == null) {
314
+ throw error;
315
+ }
316
+
317
+ TraceContextManager.resume(ctx._id);
318
+ errorDelegate(ctx, sql_step)(null, [error]);
319
+
320
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
321
+ ctx.sql_time += sql_step.elapsed;
322
+
323
+ ctx.footprint('Oracle Query Error');
324
+
325
+ MeterSql.add(dbc_hash, sql_step.elapsed, true);
326
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc,
327
+ sql_step.hash, sql_step.elapsed, true, 0);
328
+
329
+ throw error;
330
+ });
331
+ } else {
332
+ return original.apply(this, args);
333
+ }
334
+ } catch (err) {
335
+ if (ctx != null) {
336
+ errorDelegate(ctx, sql_step)(null, [err]);
337
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
338
+ ctx.sql_time += sql_step.elapsed;
339
+ ctx.footprint('Oracle Query Error');
340
+
341
+ MeterSql.add(dbc_hash, sql_step.elapsed, true);
342
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc,
343
+ sql_step.hash, sql_step.elapsed, true, 0);
344
+ }
345
+ throw err;
346
+ }
347
+ };
348
+ });
349
+ }
350
+ };
351
+
352
+ OracleObserver.prototype.inject = function (mod, moduleName) {
353
+ if (mod.__whatap_observe__) {
354
+ return;
355
+ }
356
+ mod.__whatap_observe__ = true;
357
+ Logger.initPrint("OracleObserver");
358
+
359
+ var self = this;
360
+
361
+ if (conf.sql_enabled === false) {
362
+ return;
363
+ }
364
+
365
+ shimmer.wrap(mod, 'getConnection', function (original) {
366
+ return function () {
367
+ var args = Array.from(arguments);
368
+ var connectionInfo = args[0] || {};
369
+ var ctx = TraceContextManager.getCurrentContext();
370
+
371
+ var connectInfo = connectionInfo.connectString || connectionInfo.connectionString || '';
372
+ var user = connectionInfo.user || '';
373
+ var dbc = 'oracle://' + user + '@' + connectInfo;
374
+ var dbc_hash = HashUtil.hashFromString(dbc);
375
+
376
+ DataTextAgent.DBC.add(dbc_hash, dbc);
377
+ DataTextAgent.METHOD.add(dbc_hash, dbc);
378
+ DataTextAgent.ERROR.add(dbc_hash, dbc);
379
+
380
+ var dbc_step = null;
381
+
382
+ if (ctx) {
383
+ dbc_step = new DBCStep();
384
+ dbc_step.start_time = ctx.getElapsedTime();
385
+ dbc_step.hash = dbc_hash;
386
+
387
+ ctx.footprint('Oracle Connecting Start');
388
+ ctx.db_opening = true;
389
+ ctx.profile.push(dbc_step);
390
+ }
391
+
392
+ if (arguments.length > 0 && typeof arguments[arguments.length - 1] === 'function') {
393
+ var callbackIndex = arguments.length - 1;
394
+ var originalCallback = arguments[callbackIndex];
395
+
396
+ arguments[callbackIndex] = function wrappedCallback(err, connection) {
397
+ if (ctx) {
398
+ TraceContextManager.resume(ctx._id);
399
+
400
+ if (err) {
401
+ ctx.footprint('Oracle Connecting Error');
402
+ if (dbc_step) {
403
+ dbc_step.error = StatError.addError('oracle-' + (err.errorNum || 'ERROR'),
404
+ err.message || 'Oracle connection error', ctx.service_hash);
405
+ }
406
+ } else {
407
+ ctx.footprint('Oracle Connecting Done');
408
+ }
409
+
410
+ ctx.db_opening = false;
411
+
412
+ if (dbc_step) {
413
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
414
+ }
415
+ }
416
+
417
+ if (connection) {
418
+ connectionAttributes.set(connection, connectionInfo);
419
+ self._wrapPoolOrConnection(connection, dbc);
420
+ }
421
+
422
+ return originalCallback.apply(this, arguments);
423
+ };
424
+
425
+ return original.apply(this, arguments);
426
+ } else {
427
+ return original.apply(this, args)
428
+ .then(function (connection) {
429
+ if (ctx) {
430
+ TraceContextManager.resume(ctx._id);
431
+ ctx.footprint('Oracle Connecting Done');
432
+ ctx.db_opening = false;
433
+
434
+ if (dbc_step) {
435
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
436
+ }
437
+ }
438
+
439
+ if (connection) {
440
+ connectionAttributes.set(connection, connectionInfo);
441
+ self._wrapPoolOrConnection(connection, dbc);
442
+ }
443
+
444
+ return connection;
445
+ })
446
+ .catch(function (err) {
447
+ if (ctx) {
448
+ TraceContextManager.resume(ctx._id);
449
+ ctx.footprint('Oracle Connecting Error');
450
+ ctx.db_opening = false;
451
+
452
+ if (dbc_step) {
453
+ dbc_step.error = StatError.addError('oracle-' + (err.errorNum || 'ERROR'),
454
+ err.message || 'Oracle connection error', ctx.service_hash);
455
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
456
+ }
457
+ }
458
+
459
+ throw err;
460
+ });
461
+ }
462
+ };
463
+ });
464
+
465
+ shimmer.wrap(mod, 'createPool', function (original) {
466
+ return function () {
467
+ var args = Array.from(arguments);
468
+ var poolAttrs = args[0] || {};
469
+
470
+ // Handle callback-style
471
+ if (args.length > 0 && typeof args[args.length - 1] === 'function') {
472
+ var callbackIndex = args.length - 1;
473
+ var originalCallback = args[callbackIndex];
474
+
475
+ args[callbackIndex] = function wrappedCallback(err, pool) {
476
+ if (pool) {
477
+ poolAttributes.set(pool, poolAttrs);
478
+ // Wrap Pool's getConnection method after pool is created
479
+ shimmer.wrap(pool, 'getConnection', function(originalGetConn) {
480
+ return self._wrapPoolGetConnection(originalGetConn, poolAttrs);
481
+ });
482
+ }
483
+
484
+ return originalCallback.apply(this, arguments);
485
+ };
486
+
487
+ return original.apply(this, args);
488
+ } else {
489
+ return original.apply(this, args)
490
+ .then(function(pool) {
491
+ if (pool) {
492
+ poolAttributes.set(pool, poolAttrs);
493
+ shimmer.wrap(pool, 'getConnection', function(originalGetConn) {
494
+ return self._wrapPoolGetConnection(originalGetConn, poolAttrs);
495
+ });
496
+ }
497
+ return pool;
498
+ });
499
+ }
500
+ };
501
+ });
502
+
503
+ if (mod.Pool && mod.Pool.prototype) {
504
+ shimmer.wrap(mod.Pool.prototype, 'getConnection', function(original) {
505
+ return self._wrapPoolGetConnection(original);
506
+ });
507
+ }
508
+ };
509
+
510
+ OracleObserver.prototype._wrapPoolGetConnection = function(originalGetConn, poolAttrs) {
511
+ var self = this;
512
+
513
+ return function wrappedGetConnection() {
514
+ var args = Array.from(arguments);
515
+ var ctx = TraceContextManager.getCurrentContext();
516
+
517
+ var poolInfo = poolAttrs || poolAttributes.get(this) || {};
518
+ var connectInfo = poolInfo.connectString || poolInfo.connectionString || '';
519
+ var user = poolInfo.user || '';
520
+ var dbc = 'oracle://' + user + '@' + connectInfo;
521
+ var dbc_hash = HashUtil.hashFromString(dbc);
522
+
523
+ DataTextAgent.DBC.add(dbc_hash, dbc);
524
+ DataTextAgent.METHOD.add(dbc_hash, dbc);
525
+ DataTextAgent.ERROR.add(dbc_hash, dbc);
526
+
527
+ var dbc_step = null;
528
+
529
+ if (ctx) {
530
+ dbc_step = new DBCStep();
531
+ dbc_step.start_time = ctx.getElapsedTime();
532
+ dbc_step.hash = dbc_hash;
533
+
534
+ ctx.footprint('Oracle Pool Connection Start');
535
+ ctx.db_opening = true;
536
+ ctx.profile.push(dbc_step);
537
+ }
538
+
539
+ if (args.length > 0 && typeof args[args.length - 1] === 'function') {
540
+ var callbackIndex = args.length - 1;
541
+ var originalCallback = args[callbackIndex];
542
+
543
+ args[callbackIndex] = function(err, connection) {
544
+ if (ctx) {
545
+ TraceContextManager.resume(ctx._id);
546
+
547
+ if (err) {
548
+ ctx.footprint('Oracle Pool Connection Error');
549
+ if (dbc_step) {
550
+ dbc_step.error = StatError.addError('oracle-' + (err.errorNum || 'ERROR'),
551
+ err.message || 'Oracle connection error', ctx.service_hash);
552
+ }
553
+ } else {
554
+ ctx.footprint('Oracle Pool Connection Done');
555
+ }
556
+
557
+ ctx.db_opening = false;
558
+
559
+ if (dbc_step) {
560
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
561
+ }
562
+ }
563
+
564
+ if (connection) {
565
+ connectionAttributes.set(connection, poolInfo);
566
+ self._wrapPoolOrConnection(connection, dbc);
567
+ }
568
+
569
+ return originalCallback.apply(this, arguments);
570
+ };
571
+
572
+ return originalGetConn.apply(this, args);
573
+ } else {
574
+ return originalGetConn.apply(this, args)
575
+ .then(function(connection) {
576
+ if (ctx) {
577
+ TraceContextManager.resume(ctx._id);
578
+ ctx.footprint('Oracle Pool Connection Done');
579
+ ctx.db_opening = false;
580
+
581
+ if (dbc_step) {
582
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
583
+ }
584
+ }
585
+
586
+ if (connection) {
587
+ connectionAttributes.set(connection, poolInfo);
588
+ self._wrapPoolOrConnection(connection, dbc);
589
+ }
590
+
591
+ return connection;
592
+ })
593
+ .catch(function(err) {
594
+ if (ctx) {
595
+ TraceContextManager.resume(ctx._id);
596
+ ctx.footprint('Oracle Pool Connection Error');
597
+ ctx.db_opening = false;
598
+
599
+ if (dbc_step) {
600
+ dbc_step.error = StatError.addError('oracle-' + (err.errorNum || 'ERROR'),
601
+ err.message || 'Oracle connection error', ctx.service_hash);
602
+ dbc_step.elapsed = ctx.getElapsedTime() - dbc_step.start_time;
603
+ }
604
+ }
605
+
606
+ throw err;
607
+ });
608
+ }
609
+ };
610
+ };
611
+
612
+ var checkedSql = new IntKeyMap(2000).setMax(2000);
613
+ var nonLiteSql = new IntKeyMap(5000).setMax(5000);
614
+ var date = DateUtil.yyyymmdd();
615
+
616
+ function escapeLiteral(sql) {
617
+ if (sql == null) {
618
+ sql = '';
619
+ }
620
+
621
+ if (date !== DateUtil.yyyymmdd()) {
622
+ checkedSql.clear();
623
+ nonLiteSql.clear();
624
+ date = DateUtil.yyyymmdd();
625
+ Logger.print('WHATAP-SQL-CLEAR', 'OracleObserver CLEAR SQL Cache', false);
626
+ }
627
+
628
+ var sqlHash = HashUtil.hashFromString(sql);
629
+ var psql = nonLiteSql.get(sqlHash);
630
+
631
+ if (psql != null) {
632
+ return psql;
633
+ }
634
+ psql = checkedSql.get(sqlHash);
635
+
636
+ if (psql != null) {
637
+ return psql;
638
+ }
639
+
640
+ var els = new EscapeLiteralSQL(sql);
641
+ els.process();
642
+
643
+ var hash = HashUtil.hashFromString(els.getParsedSql());
644
+ DataTextAgent.SQL.add(hash, els.getParsedSql());
645
+
646
+ if (hash === sqlHash) {
647
+ psql = new ParsedSql(els.sqlType, hash, null);
648
+ nonLiteSql.put(sqlHash, psql);
649
+ } else {
650
+ psql = new ParsedSql(els.sqlType, hash, els.getParameter());
651
+ checkedSql.put(sqlHash, psql);
652
+ }
653
+ return psql;
654
+ }
655
+
656
+ exports.OracleObserver = OracleObserver;
@@ -365,8 +365,8 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
365
365
  ctx.footprint(`Prisma ${modelName}.${action} Start`);
366
366
  ctx.sql_count = (ctx.sql_count || 0) + 1;
367
367
 
368
- // 사용된 모델과 액션으로 SQL 문자열 구성
369
- const queryInfo = `Prisma ${action.toUpperCase()} ${modelName}`;
368
+ // Prisma 쿼리 정보 직접 사용 (SQL 변환 없이)
369
+ const queryInfo = `Prisma ${modelName}.${action}`;
370
370
  const queryHash = HashUtil.hashFromString(queryInfo);
371
371
 
372
372
  DataTextAgent.SQL.add(queryHash, queryInfo);
@@ -388,6 +388,7 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
388
388
  try {
389
389
  result = await next(params);
390
390
 
391
+ // Raw 쿼리 처리
391
392
  if (action === "queryRaw" || action === "executeRaw" || action === "queryRawUnsafe" || action === "executeRawUnsafe") {
392
393
  // SQL 문자열 추출
393
394
  let sqlString = "";
@@ -408,10 +409,6 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
408
409
  var psql = escapeLiteral(sqlString);
409
410
  if (psql != null) {
410
411
  sql_step.hash = psql.sql;
411
- // CRUD 타입 설정 (S: Select, U: Update 등)
412
- // if (psql.type) {
413
- // sql_step.crud = psql.type.charCodeAt(0);
414
- // }
415
412
  }
416
413
 
417
414
  // 추가 SQL 정보 처리
@@ -451,9 +448,8 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
451
448
  recordCount = 1;
452
449
  }
453
450
 
454
- if (recordCount > 0) {
455
- // 가장 최근에 처리된 SQL 해시 사용 (Raw 쿼리에서 업데이트된 경우)
456
- const sqlHashToUse = psql ? psql.sql : queryHash;
451
+ if(psql && psql.sql){
452
+ const sqlHashToUse = psql.sql;
457
453
 
458
454
  var result_step = new ResultSetStep();
459
455
  result_step.start_time = ctx.getElapsedTime();
@@ -484,23 +480,21 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
484
480
  recordCount = 1;
485
481
  }
486
482
 
487
- if (recordCount > 0) {
488
- var result_step = new ResultSetStep();
489
- result_step.start_time = ctx.getElapsedTime();
490
- result_step.elapsed = 0;
491
- result_step.fetch = recordCount;
492
- result_step.sqlhash = queryHash;
493
- result_step.dbc = dbc_hash;
494
- ctx.profile.push(result_step);
483
+ var result_step = new ResultSetStep();
484
+ result_step.start_time = ctx.getElapsedTime();
485
+ result_step.elapsed = 0;
486
+ result_step.fetch = recordCount;
487
+ result_step.sqlhash = queryHash;
488
+ result_step.dbc = dbc_hash;
489
+ ctx.profile.push(result_step);
495
490
 
496
- ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
497
- ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
491
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
492
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
498
493
 
499
- MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
500
- StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
494
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
495
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
501
496
 
502
- TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
503
- }
497
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
504
498
  }
505
499
 
506
500
  // 수정된 레코드 수 처리 (create, update, delete 등)
@@ -512,252 +506,9 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
512
506
  }
513
507
  }
514
508
 
515
- // findUnique, create 등의 메서드에 대한 SQL 형식의 정보 구성
516
- if (modelName !== 'unknown' && action !== 'queryRaw' && action !== 'executeRaw' &&
517
- action !== 'queryRawUnsafe' && action !== 'executeRawUnsafe') {
518
-
519
- try {
520
- // 모델 메서드를 SQL 스타일로 변환
521
- let sqlStyleQuery = "";
522
-
523
- // 액션에 따라 SQL 명령 결정
524
- if (action.startsWith('find')) {
525
- sqlStyleQuery = "SELECT ";
526
-
527
- // select 필드가 있는 경우 해당 필드 사용
528
- if (params.args && params.args.select) {
529
- const fields = Object.keys(params.args.select)
530
- .filter(key => params.args.select[key] === true);
531
-
532
- if (fields.length > 0) {
533
- sqlStyleQuery += fields.join(", ");
534
- } else {
535
- sqlStyleQuery += "*";
536
- }
537
- } else {
538
- sqlStyleQuery += "*";
539
- }
540
-
541
- sqlStyleQuery += ` FROM ${modelName}`;
542
-
543
- // where 조건 추가
544
- if (params.args && params.args.where) {
545
- const conditions = [];
546
- for (const [key, value] of Object.entries(params.args.where)) {
547
- if (typeof value === 'object' && value !== null) {
548
- // 복합 조건(contains, startsWith 등)
549
- for (const [op, val] of Object.entries(value)) {
550
- let sqlOp = "";
551
-
552
- // Prisma 연산자를 SQL 연산자로 변환
553
- switch (op) {
554
- case 'equals': sqlOp = '='; break;
555
- case 'not': sqlOp = '!='; break;
556
- case 'in': sqlOp = 'IN'; break;
557
- case 'notIn': sqlOp = 'NOT IN'; break;
558
- case 'lt': sqlOp = '<'; break;
559
- case 'lte': sqlOp = '<='; break;
560
- case 'gt': sqlOp = '>'; break;
561
- case 'gte': sqlOp = '>='; break;
562
- case 'contains': sqlOp = 'LIKE'; break;
563
- case 'startsWith': sqlOp = 'LIKE'; break;
564
- case 'endsWith': sqlOp = 'LIKE'; break;
565
- default: sqlOp = op;
566
- }
567
-
568
- let sqlVal = val;
569
- if (op === 'contains') {
570
- sqlVal = `'%${val}%'`;
571
- } else if (op === 'startsWith') {
572
- sqlVal = `'${val}%'`;
573
- } else if (op === 'endsWith') {
574
- sqlVal = `'%${val}'`;
575
- } else if (typeof val === 'string') {
576
- sqlVal = `'${val}'`;
577
- } else if (Array.isArray(val)) {
578
- sqlVal = `(${val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ')})`;
579
- }
580
-
581
- conditions.push(`${key} ${sqlOp} ${sqlVal}`);
582
- }
583
- } else {
584
- // 단순 조건
585
- let formattedValue = typeof value === 'string' ? `'${value}'` : value;
586
- conditions.push(`${key} = ${formattedValue}`);
587
- }
588
- }
589
-
590
- if (conditions.length > 0) {
591
- sqlStyleQuery += ` WHERE ${conditions.join(' AND ')}`;
592
- }
593
- }
594
-
595
- // 정렬 조건 추가
596
- if (params.args && params.args.orderBy) {
597
- const orderClauses = [];
598
-
599
- if (Array.isArray(params.args.orderBy)) {
600
- for (const item of params.args.orderBy) {
601
- for (const [field, dir] of Object.entries(item)) {
602
- orderClauses.push(`${field} ${dir}`);
603
- }
604
- }
605
- } else {
606
- for (const [field, dir] of Object.entries(params.args.orderBy)) {
607
- orderClauses.push(`${field} ${dir}`);
608
- }
609
- }
610
-
611
- if (orderClauses.length > 0) {
612
- sqlStyleQuery += ` ORDER BY ${orderClauses.join(', ')}`;
613
- }
614
- }
615
-
616
- // 페이징 정보 추가
617
- if (params.args) {
618
- if (params.args.skip !== undefined) {
619
- sqlStyleQuery += ` OFFSET ${params.args.skip}`;
620
- }
621
-
622
- if (params.args.take !== undefined) {
623
- sqlStyleQuery += ` LIMIT ${params.args.take}`;
624
- }
625
- }
626
-
627
- } else if (action.startsWith('create')) {
628
- sqlStyleQuery = `INSERT INTO ${modelName}`;
629
-
630
- if (params.args && params.args.data) {
631
- const columns = [];
632
- const values = [];
633
-
634
- for (const [key, val] of Object.entries(params.args.data)) {
635
- columns.push(key);
636
- if (typeof val === 'string') {
637
- values.push(`'${val}'`);
638
- } else if (val === null) {
639
- values.push('NULL');
640
- } else if (typeof val === 'object') {
641
- values.push(`'${JSON.stringify(val)}'`);
642
- } else {
643
- values.push(val);
644
- }
645
- }
646
-
647
- sqlStyleQuery += ` (${columns.join(', ')}) VALUES (${values.join(', ')})`;
648
- }
649
-
650
- } else if (action.startsWith('update')) {
651
- sqlStyleQuery = `UPDATE ${modelName}`;
652
-
653
- if (params.args && params.args.data) {
654
- const setExpressions = [];
655
-
656
- for (const [key, val] of Object.entries(params.args.data)) {
657
- let formattedValue;
658
- if (typeof val === 'string') {
659
- formattedValue = `'${val}'`;
660
- } else if (val === null) {
661
- formattedValue = 'NULL';
662
- } else if (typeof val === 'object') {
663
- formattedValue = `'${JSON.stringify(val)}'`;
664
- } else {
665
- formattedValue = val;
666
- }
667
-
668
- setExpressions.push(`${key} = ${formattedValue}`);
669
- }
670
-
671
- if (setExpressions.length > 0) {
672
- sqlStyleQuery += ` SET ${setExpressions.join(', ')}`;
673
- }
674
- }
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
-
697
- } else if (action.startsWith('delete')) {
698
- sqlStyleQuery = `DELETE FROM ${modelName}`;
699
-
700
- // where 조건 추가
701
- if (params.args && params.args.where) {
702
- const conditions = [];
703
- for (const [key, value] of Object.entries(params.args.where)) {
704
- if (typeof value === 'object' && value !== null) {
705
- for (const [op, val] of Object.entries(value)) {
706
- let sqlOp = op === 'equals' ? '=' : op;
707
- let sqlVal = typeof val === 'string' ? `'${val}'` : val;
708
- conditions.push(`${key} ${sqlOp} ${sqlVal}`);
709
- }
710
- } else {
711
- let formattedValue = typeof value === 'string' ? `'${value}'` : value;
712
- conditions.push(`${key} = ${formattedValue}`);
713
- }
714
- }
715
-
716
- if (conditions.length > 0) {
717
- sqlStyleQuery += ` WHERE ${conditions.join(' AND ')}`;
718
- }
719
- }
720
- } else {
721
- // 기타 액션은 기본 형식으로
722
- sqlStyleQuery = `${action.toUpperCase()} ${modelName}`;
723
-
724
- if (params.args) {
725
- sqlStyleQuery += ` ${JSON.stringify(params.args)}`;
726
- }
727
- }
728
-
729
- // 생성된 SQL 쿼리에 escapeLiteral 적용하여 처리
730
- try {
731
- var psql = escapeLiteral(sqlStyleQuery);
732
- if (psql != null) {
733
- sql_step.hash = psql.sql;
734
- // CRUD 타입 설정
735
- if (psql.type) {
736
- sql_step.crud = psql.type.charCodeAt(0);
737
- }
738
- }
739
-
740
- // 추가 SQL 정보 처리
741
- var els = new EscapeLiteralSQL(sqlStyleQuery);
742
- els.process();
743
-
744
- // SQL 파라미터 처리
745
- if (conf.profile_sql_param_enabled) {
746
- sql_step.setTrue(1);
747
- var crc = {value: 0};
748
- sql_step.p1 = toParamBytes(psql.param, crc);
749
-
750
- // 원래 인자를 파라미터로 추가
751
- const paramsString = JSON.stringify(params.args || {});
752
- sql_step.p2 = toParamBytes(paramsString, crc);
753
- sql_step.pcrc = crc.value;
754
- }
755
- } catch (e) {
756
- Logger.printError("WHATAP-306", "escapeLiteral error for model method", e, false);
757
- }
758
- } catch (e) {
759
- Logger.printError("WHATAP-307", "Error creating SQL-style query", e, false);
760
- }
509
+ // UPSERT 처리 추가
510
+ if (action === "upsert") {
511
+ sql_step.updated = 1; // upsert는 항상 1개의 레코드에 영향을 미침
761
512
  }
762
513
 
763
514
  sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
@@ -173,10 +173,10 @@ EscapeLiteralSQL.prototype._quotation = function () {
173
173
  break;
174
174
  case STAT.ALPHABET:
175
175
  this.parsedSql += this.chars[this.pos];
176
- status = STAT.QUOTATION;
176
+ this.status = STAT.QUOTATION;
177
177
  break;
178
178
  case STAT.NUMBER:
179
- parsedSql += this.chars[this.pos];
179
+ this.parsedSql += this.chars[this.pos];
180
180
  this.status = STAT.QUOTATION;
181
181
  break;
182
182
  case STAT.QUOTATION:
@@ -194,16 +194,16 @@ EscapeLiteralSQL.prototype._dquotation = function () {
194
194
  }
195
195
  this.param += this.chars[this.pos];
196
196
  this.status = STAT.DQUOTATION;
197
- break;;
197
+ break;
198
198
  case STAT.COMMENT:
199
199
  this.parsedSql += this.chars[this.pos];
200
200
  break;
201
201
  case STAT.ALPHABET:
202
202
  this.parsedSql += this.chars[this.pos];
203
- status = STAT.DQUOTATION;
203
+ this.status = STAT.DQUOTATION;
204
204
  break;
205
205
  case STAT.NUMBER:
206
- parsedSql += this.chars[this.pos];
206
+ this.parsedSql += this.chars[this.pos];
207
207
  this.status = STAT.DQUOTATION;
208
208
  break;
209
209
  case STAT.DQUOTATION:
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.12",
5
- "releaseDate": "20250324",
4
+ "version": "0.5.13",
5
+ "releaseDate": "20250404",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},