whatap 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +32 -78
  2. package/lib/conf/conf-sys-mon.js +101 -0
  3. package/lib/conf/config-default.js +10 -3
  4. package/lib/conf/configure.js +369 -349
  5. package/lib/conf/license.js +1 -1
  6. package/lib/control/cmd-config.js +24 -0
  7. package/lib/control/control-handler.js +367 -0
  8. package/lib/control/packagectr-helper.js +34 -3
  9. package/lib/core/agent.js +176 -882
  10. package/lib/core/interceptor.js +6 -6
  11. package/lib/core/request-agent.js +27 -0
  12. package/lib/core/shimmer.js +82 -36
  13. package/lib/counter/counter-manager.js +79 -8
  14. package/lib/counter/meter/meter-activex.js +67 -0
  15. package/lib/counter/meter/meter-httpc.js +57 -0
  16. package/lib/counter/meter/meter-resource.js +9 -0
  17. package/lib/counter/meter/meter-service.js +168 -0
  18. package/lib/counter/meter/meter-socket.io.js +51 -0
  19. package/lib/counter/meter/meter-sql.js +71 -0
  20. package/lib/counter/meter/meter-users.js +58 -0
  21. package/lib/counter/meter.js +183 -0
  22. package/lib/counter/task/activetransaction.js +68 -17
  23. package/lib/counter/task/agentinfo.js +107 -0
  24. package/lib/{system → counter/task}/gc-action.js +27 -74
  25. package/lib/counter/task/gcstat.js +34 -0
  26. package/lib/counter/task/heapmem.js +25 -0
  27. package/lib/counter/task/httpc.js +76 -0
  28. package/lib/counter/task/metering-info.js +125 -0
  29. package/lib/counter/task/proc-cpu.js +29 -0
  30. package/lib/counter/task/realtimeuser.js +31 -0
  31. package/lib/counter/task/res/systemECSTask.js +39 -0
  32. package/lib/counter/task/res/systemKubeTask.js +53 -0
  33. package/lib/counter/task/res/util/awsEcsClientThread.js +218 -0
  34. package/lib/counter/task/res/util/linuxProcStatUtil.js +14 -0
  35. package/lib/counter/task/res-sys-cpu.js +62 -0
  36. package/lib/counter/task/service.js +202 -0
  37. package/lib/counter/task/socketio.js +30 -0
  38. package/lib/counter/task/sql.js +105 -0
  39. package/lib/counter/task/systemperf.js +43 -0
  40. package/lib/data/datapack-sender.js +289 -0
  41. package/lib/data/dataprofile-agent.js +162 -0
  42. package/lib/data/datatext-agent.js +135 -0
  43. package/lib/data/event-level.js +15 -0
  44. package/lib/data/test.js +49 -0
  45. package/lib/data/zipprofile.js +197 -0
  46. package/lib/env/constants.js +21 -0
  47. package/lib/error/error-handler.js +437 -0
  48. package/lib/io/data-inputx.js +13 -3
  49. package/lib/io/data-outputx.js +268 -206
  50. package/lib/kube/kube-client.js +144 -0
  51. package/lib/lang/text-types.js +58 -0
  52. package/lib/logger.js +6 -6
  53. package/lib/logsink/line-log-util.js +87 -0
  54. package/lib/logsink/line-log.js +12 -0
  55. package/lib/logsink/log-sender.js +78 -0
  56. package/lib/logsink/log-tracer.js +40 -0
  57. package/lib/logsink/sender-util.js +56 -0
  58. package/lib/logsink/zip/zip-send.js +177 -0
  59. package/lib/net/netflag.js +55 -0
  60. package/lib/net/receiver.js +66 -0
  61. package/lib/net/security-master.js +139 -20
  62. package/lib/net/sender.js +141 -0
  63. package/lib/net/tcp-return.js +18 -0
  64. package/lib/net/tcp-session.js +286 -0
  65. package/lib/net/tcpreq-client-proxy.js +70 -0
  66. package/lib/net/tcprequest-mgr.js +58 -0
  67. package/lib/observers/apollo-server-observer.js +33 -27
  68. package/lib/observers/cluster-observer.js +22 -0
  69. package/lib/observers/express-observer.js +215 -0
  70. package/lib/observers/file-observer.js +184 -0
  71. package/lib/observers/global-observer.js +155 -80
  72. package/lib/observers/grpc-observer.js +336 -0
  73. package/lib/observers/http-observer.js +666 -236
  74. package/lib/observers/maria-observer.js +204 -362
  75. package/lib/observers/memcached-observer.js +56 -0
  76. package/lib/observers/mongo-observer.js +317 -0
  77. package/lib/observers/mongodb-observer.js +169 -226
  78. package/lib/observers/mongoose-observer.js +518 -323
  79. package/lib/observers/mssql-observer.js +177 -418
  80. package/lib/observers/mysql-observer.js +342 -449
  81. package/lib/observers/mysql2-observer.js +396 -358
  82. package/lib/observers/net-observer.js +77 -0
  83. package/lib/observers/oracle-observer.js +559 -384
  84. package/lib/observers/pgsql-observer.js +231 -489
  85. package/lib/observers/prisma-observer.js +303 -92
  86. package/lib/observers/process-observer.js +79 -35
  87. package/lib/observers/promise-observer.js +31 -0
  88. package/lib/observers/redis-observer.js +166 -331
  89. package/lib/observers/schedule-observer.js +67 -0
  90. package/lib/observers/socket.io-observer.js +226 -187
  91. package/lib/observers/stream-observer.js +19 -0
  92. package/lib/observers/thrift-observer.js +197 -0
  93. package/lib/observers/websocket-observer.js +175 -301
  94. package/lib/pack/activestack-pack.js +55 -0
  95. package/lib/pack/apenum.js +8 -0
  96. package/lib/pack/counter-pack.js +3 -0
  97. package/lib/pack/errorsnap-pack.js +69 -0
  98. package/lib/pack/event-pack.js +54 -0
  99. package/lib/pack/hitmap-pack.js +63 -0
  100. package/lib/pack/hitmap-pack1.js +152 -0
  101. package/lib/pack/log-sink-pack.js +14 -52
  102. package/lib/pack/netstat.js +15 -0
  103. package/lib/pack/otype.js +7 -0
  104. package/lib/pack/profile-pack.js +49 -0
  105. package/lib/pack/realtimeuser-pack.js +41 -0
  106. package/lib/pack/stat-general-pack.js +96 -0
  107. package/lib/pack/staterror-pack.js +120 -0
  108. package/lib/pack/stathttpc-pack.js +66 -0
  109. package/lib/pack/stathttpc-rec.js +78 -0
  110. package/lib/pack/statremote-pack.js +46 -0
  111. package/lib/pack/statservice-pack.js +63 -0
  112. package/lib/pack/statservice-pack1.js +88 -0
  113. package/lib/pack/statservice-rec.js +292 -0
  114. package/lib/pack/statservice-rec_dep.js +151 -0
  115. package/lib/pack/statsql-pack.js +69 -0
  116. package/lib/pack/statsql-rec.js +100 -0
  117. package/lib/pack/statuseragent-pack.js +44 -0
  118. package/lib/pack/tagcount-pack.js +4 -4
  119. package/lib/pack/tagctr.js +15 -0
  120. package/lib/pack/text-pack.js +50 -0
  121. package/lib/pack/time-count.js +25 -0
  122. package/lib/pack/websocket.js +15 -0
  123. package/lib/pack/zip-pack.js +70 -0
  124. package/lib/pii/pii-item.js +31 -0
  125. package/lib/pii/pii-mask.js +174 -0
  126. package/lib/plugin/plugin-loadermanager.js +57 -0
  127. package/lib/plugin/plugin.js +75 -0
  128. package/lib/service/tx-record.js +332 -0
  129. package/lib/stat/stat-error.js +116 -0
  130. package/lib/stat/stat-httpc.js +98 -0
  131. package/lib/stat/stat-remote-ip.js +46 -0
  132. package/lib/stat/stat-remote-ipurl.js +88 -0
  133. package/lib/stat/stat-sql.js +113 -0
  134. package/lib/stat/stat-tranx.js +58 -0
  135. package/lib/stat/stat-tx-caller.js +160 -0
  136. package/lib/stat/stat-tx-domain.js +111 -0
  137. package/lib/stat/stat-tx-referer.js +112 -0
  138. package/lib/stat/stat-useragent.js +48 -0
  139. package/lib/stat/timingsender.js +76 -0
  140. package/lib/step/activestack-step.js +38 -0
  141. package/lib/step/dbc-step.js +36 -0
  142. package/lib/step/http-stepx.js +67 -0
  143. package/lib/step/message-step.js +40 -0
  144. package/lib/step/method-stepx.js +45 -0
  145. package/lib/step/resultset-step.js +40 -0
  146. package/lib/step/securemsg-step.js +44 -0
  147. package/lib/step/socket-step.js +46 -0
  148. package/lib/step/sql-stepx.js +68 -0
  149. package/lib/step/sqlxtype.js +16 -0
  150. package/lib/step/step.js +66 -0
  151. package/lib/step/stepenum.js +54 -0
  152. package/lib/topology/link.js +63 -0
  153. package/lib/topology/nodeinfo.js +123 -0
  154. package/lib/topology/status-detector.js +111 -0
  155. package/lib/trace/trace-context-manager.js +113 -25
  156. package/lib/trace/trace-context.js +21 -7
  157. package/lib/trace/trace-httpc.js +17 -11
  158. package/lib/trace/trace-sql.js +29 -21
  159. package/lib/util/anylist.js +103 -0
  160. package/lib/util/cardinality/hyperloglog.js +106 -0
  161. package/lib/util/cardinality/murmurhash.js +31 -0
  162. package/lib/util/cardinality/registerset.js +75 -0
  163. package/lib/util/errordata.js +21 -0
  164. package/lib/util/escape-literal-sql.js +5 -5
  165. package/lib/util/hashutil.js +18 -18
  166. package/lib/util/iputil_x.js +527 -0
  167. package/lib/util/keygen.js +0 -3
  168. package/lib/util/kube-util.js +73 -0
  169. package/lib/util/linkedset.js +1 -2
  170. package/lib/util/nodeutil.js +2 -1
  171. package/lib/util/paramsecurity.js +80 -0
  172. package/lib/util/pre-process.js +13 -0
  173. package/lib/util/process-seq.js +166 -0
  174. package/lib/util/property-util.js +36 -0
  175. package/lib/util/request-queue.js +70 -0
  176. package/lib/util/requestdouble-queue.js +72 -0
  177. package/lib/util/resourceprofile.js +157 -0
  178. package/lib/util/stop-watch.js +30 -0
  179. package/lib/util/system-util.js +10 -0
  180. package/lib/util/userid-util.js +57 -0
  181. package/lib/value/map-value.js +3 -2
  182. package/package.json +9 -4
  183. package/whatap.conf +1 -4
  184. package/agent/darwin/arm64/whatap_nodejs +0 -0
  185. package/agent/linux/amd64/whatap_nodejs +0 -0
  186. package/agent/linux/arm64/whatap_nodejs +0 -0
  187. package/build.txt +0 -4
  188. package/lib/observers/ioredis-observer.js +0 -294
  189. package/lib/udp/async_sender.js +0 -119
  190. package/lib/udp/index.js +0 -17
  191. package/lib/udp/packet_enum.js +0 -52
  192. package/lib/udp/packet_queue.js +0 -69
  193. package/lib/udp/packet_type_enum.js +0 -33
  194. package/lib/udp/param_def.js +0 -72
  195. package/lib/udp/udp_session.js +0 -336
  196. package/lib/util/sql-util.js +0 -178
  197. package/lib/util/trace-helper.js +0 -91
  198. package/lib/util/transfer.js +0 -58
@@ -5,12 +5,31 @@
5
5
  */
6
6
 
7
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");
8
15
  const conf = require("../conf/configure");
16
+ const IntKeyMap = require("../util/intkey-map");
17
+ const EscapeLiteralSQL = require("../util/escape-literal-sql");
9
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");
10
22
  const Logger = require("../logger");
23
+ const Buffer = require("buffer").Buffer;
24
+ const DateUtil = require("../util/dateutil");
25
+ const TraceSQL = require("../trace/trace-sql");
11
26
  const shimmer = require("../core/shimmer");
12
- const AsyncSender = require('../udp/async_sender');
13
- const PacketTypeEnum = require('../udp/packet_type_enum');
27
+
28
+ var prisma_read_func_name = conf.getProperty('prisma_read_func_name', 'read');
29
+ var prisma_database_url_name = conf.getProperty('prisma_database_url_name', 'DATABASE_URL')
30
+ conf.on('prisma_database_url_name', function (newProps) {
31
+ prisma_database_url_name = newProps;
32
+ })
14
33
 
15
34
  var PrismaObserver = function(agent) {
16
35
  this.agent = agent;
@@ -36,20 +55,40 @@ PrismaObserver.prototype.inject = function(mod, moduleName) {
36
55
 
37
56
  // Prisma Client 초기화 메서드 후킹
38
57
  if (mod.PrismaClient) {
58
+ // 직접 PrismaClient 생성자 후킹
39
59
  shimmer.wrap(mod, 'PrismaClient', function(originalConstructor) {
40
60
  return function(...args) {
41
- // 원래 생성자 호출
42
- const instance = originalConstructor.apply(this, args);
61
+ const originalInstance = new originalConstructor(...args);
62
+ const instance = Object.create(originalInstance);
63
+
64
+ const prismaServicePrototype = Object.getPrototypeOf(this);
65
+ Object.getOwnPropertyNames(prismaServicePrototype).forEach((method) => {
66
+ if (typeof prismaServicePrototype[method] === "function" && !instance[method]) {
67
+ instance[method] = prismaServicePrototype[method].bind(instance);
68
+ }
69
+ });
70
+
71
+ Object.getOwnPropertyNames(originalInstance).forEach((prop) => {
72
+ if (!instance.hasOwnProperty(prop)) {
73
+ instance[prop] = originalInstance[prop];
74
+ }
75
+ });
43
76
 
44
- // 패치 적용
45
77
  self.patchPrismaInstance(instance);
46
78
 
79
+ if (!instance[prisma_read_func_name]) {
80
+ instance[prisma_read_func_name] = function () {
81
+ return this;
82
+ };
83
+ }
84
+
47
85
  return instance;
48
86
  };
49
87
  });
50
88
  }
51
89
  };
52
90
 
91
+ // 각 Prisma 인스턴스에 패치 적용
53
92
  PrismaObserver.prototype.patchPrismaInstance = function(prismaInstance) {
54
93
  if (prismaInstance.__whatap_observe__) {
55
94
  return;
@@ -57,56 +96,71 @@ PrismaObserver.prototype.patchPrismaInstance = function(prismaInstance) {
57
96
  prismaInstance.__whatap_observe__ = true;
58
97
 
59
98
  this.setupConnectionInfo(prismaInstance);
99
+
60
100
  this.hookUseMiddleware(prismaInstance);
61
101
  };
62
102
 
103
+ // 연결 정보 설정
63
104
  PrismaObserver.prototype.setupConnectionInfo = function(prismaInstance) {
64
105
  try {
65
106
  // 연결 정보 가져오기 시도
66
107
  const url = prismaInstance._engineConfig?.overrideDatasources?.db?.url ||
67
- prismaInstance._engineConfig?.datasources?.db?.url ||
68
- prismaInstance._baseDmmf?.datamodel?.datasources?.[0]?.url?.value ||
69
- process.env.DATABASE_URL ||
108
+ prismaInstance.env?.DATABASE_URL ||
109
+ process.env[prisma_database_url_name] ||
70
110
  'prisma:unknown';
71
111
 
72
112
  if (url && url !== "prisma:unknown" && !dbc_hash) {
73
113
  const dbUrl = new URL(url);
74
114
  const protocol = dbUrl.protocol.replace(':', '');
75
115
 
116
+ // 프로토콜에 따라 접두사 설정 (postgresql -> pgsql로 변환할 수도 있음)
117
+ const dbProtocol = protocol === 'pgsql' ? 'postgresql' : protocol;
118
+
76
119
  // MySQL 관찰자와 동일한 형식으로 구성
77
- dbc = `${protocol}://`;
120
+ dbc = `${dbProtocol}://`;
78
121
  dbc += dbUrl.username || '';
79
122
  dbc += "@";
80
123
  dbc += dbUrl.hostname || '';
81
124
  dbc += '/';
82
125
  dbc += dbUrl.pathname.replace('/', '') || '';
83
126
  dbc_hash = HashUtil.hashFromString(dbc);
127
+
128
+ DataTextAgent.DBC.add(dbc_hash, dbc);
129
+ DataTextAgent.METHOD.add(dbc_hash, dbc);
130
+ DataTextAgent.ERROR.add(dbc_hash, dbc);
84
131
  }
85
132
 
86
133
  } catch (e) {
87
- Logger.printError("WHATAP-223", "Failed to extract connection info", e, false);
134
+ Logger.printError("WHATAP-301", "Failed to extract connection info", e, false);
88
135
  dbc = "prisma:unknown";
89
136
  dbc_hash = HashUtil.hashFromString(dbc);
90
137
  }
91
138
  };
92
139
 
93
140
  PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
94
- const self = this;
95
-
96
- if (typeof prismaInstance.$use === 'function') {
141
+ const originalUse = prismaInstance.$use;
142
+ if (typeof originalUse === 'function') {
97
143
  prismaInstance.$use(async (params, next) => {
144
+ var result;
98
145
  const ctx = TraceContextManager.getCurrentContext();
99
146
  if (!ctx) {
100
147
  return next(params);
101
148
  }
102
149
 
103
- // DB 연결 패킷 전송 (mysql2-observer와 동일한 패턴)
104
- ctx.start_time = Date.now();
105
- ctx.elapsed = 0;
106
- AsyncSender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, [dbc]);
150
+ var dbc_step = new DBCStep();
151
+ dbc_hash = HashUtil.hashFromString(dbc);
152
+ DataTextAgent.DBC.add(dbc_hash, dbc);
153
+ DataTextAgent.METHOD.add(dbc_hash, dbc);
154
+ DataTextAgent.ERROR.add(dbc_hash, dbc)
155
+
156
+ dbc_step.hash = dbc_hash;
157
+ dbc_step.start_time = ctx.getElapsedTime();
158
+ ctx.profile.push(dbc_step);
107
159
 
108
- // SQL 시작 시간 기록
109
- var sql_start_time = Date.now();
160
+ const sql_step = new SqlStepX();
161
+ sql_step.start_time = ctx.getElapsedTime();
162
+ sql_step.elapsed = 0;
163
+ ctx.profile.push(sql_step);
110
164
 
111
165
  const modelName = params.model || 'unknown';
112
166
  const action = params.action || 'unknown';
@@ -114,21 +168,161 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
114
168
  ctx.footprint(`Prisma ${modelName}.${action} Start`);
115
169
  ctx.sql_count = (ctx.sql_count || 0) + 1;
116
170
 
117
- // 쿼리 정보 생성
171
+ // Prisma 쿼리 정보 직접 사용 (SQL 변환 없이)
118
172
  const queryInfo = `Prisma ${modelName}.${action}`;
173
+ const queryHash = HashUtil.hashFromString(queryInfo);
174
+
175
+ DataTextAgent.SQL.add(queryHash, queryInfo);
176
+ sql_step.hash = queryHash;
177
+ sql_step.dbc = dbc_hash;
178
+
179
+ ctx.active_sqlhash = sql_step.hash;
180
+ ctx.active_dbc = sql_step.dbc;
181
+
182
+ // 쿼리 파라미터 정보 추출
183
+ if (conf.profile_sql_param_enabled) {
184
+ const paramsString = JSON.stringify(params.args || {});
185
+ sql_step.setTrue(1);
186
+ var crc = { value: 0 };
187
+ sql_step.p1 = toParamBytes(paramsString, crc);
188
+ sql_step.pcrc = crc.value;
189
+ }
119
190
 
120
- ctx.active_sqlhash = HashUtil.hashFromString(queryInfo);
121
- ctx.active_dbc = dbc_hash;
122
-
191
+ if (action === "queryRaw" || action === "executeRaw" || action === "queryRawUnsafe" || action === "executeRawUnsafe") {
192
+ // SQL 문자열 추출
193
+ let sqlString = "";
194
+ if (params.args && params.args.length > 0) {
195
+ if (params.args[0] && params.args[0].strings && Array.isArray(params.args[0].strings)) {
196
+ sqlString = params.args[0].strings.join('').trim();
197
+ } else if (params.args[0] && params.args[0] && Array.isArray(params.args[0])){
198
+ sqlString = params.args[0].join('').trim();
199
+ } else if (typeof params.args[0] === 'string') {
200
+ sqlString = params.args[0];
201
+ }
202
+ }
203
+
204
+ // SQL 문자열이 있으면 처리
205
+ if (sqlString && sqlString.length > 0) {
206
+ try {
207
+ var psql = escapeLiteral(sqlString);
208
+ if (psql != null) {
209
+ sql_step.hash = psql.sql;
210
+ }
211
+
212
+ // 추가 SQL 정보 처리
213
+ var els = new EscapeLiteralSQL(sqlString);
214
+ els.process();
215
+
216
+ // SQL 파라미터 처리
217
+ if (conf.profile_sql_param_enabled) {
218
+ var params = params.args.slice(1);
219
+ sql_step.setTrue(1);
220
+ var crc = { value: 0 };
221
+ sql_step.p1 = toParamBytes(psql.param, crc);
222
+
223
+ if (params && params.length > 0) {
224
+ const result = params.map((param) => {
225
+ if (typeof param === 'string') {
226
+ return `'${param}'`;
227
+ }
228
+ return param;
229
+ }).toString();
230
+ sql_step.p2 = toParamBytes(result, crc);
231
+ }
232
+ sql_step.pcrc = crc.value;
233
+ }
234
+ } catch (e) {
235
+ Logger.printError("WHATAP-305", "escapeLiteral error", e, false);
236
+ }
237
+ }
238
+ }
123
239
  try {
124
- const result = await next(params);
240
+ result = await next(params);
241
+
242
+ // Raw 쿼리 처리
243
+ if (action === "queryRaw" || action === "executeRaw" || action === "queryRawUnsafe" || action === "executeRawUnsafe") {
244
+ // Raw 쿼리 결과셋 처리
245
+ if ((action === "queryRaw" || action === "queryRawUnsafe") && result) {
246
+ let recordCount = 0;
247
+
248
+ if (Array.isArray(result)) {
249
+ recordCount = result.length;
250
+ } else if (result && typeof result === "object") {
251
+ recordCount = 1;
252
+ }
253
+
254
+ if(psql && psql.sql){
255
+ const sqlHashToUse = psql.sql;
256
+
257
+ var result_step = new ResultSetStep();
258
+ result_step.start_time = ctx.getElapsedTime();
259
+ result_step.elapsed = 0;
260
+ result_step.fetch = recordCount;
261
+ result_step.sqlhash = sqlHashToUse;
262
+ result_step.dbc = dbc_hash;
263
+ ctx.profile.push(result_step);
264
+
265
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
266
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
267
+
268
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
269
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
270
+
271
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
272
+ }
273
+ }
274
+ }
275
+
276
+ // 결과셋 처리 (findMany, findFirst, findUnique 등)
277
+ if (["findMany", "findFirst", "findUnique"].includes(action)) {
278
+ let recordCount = 0;
279
+
280
+ if (Array.isArray(result)) {
281
+ recordCount = result.length;
282
+ } else if (result && typeof result === "object") {
283
+ recordCount = 1;
284
+ }
285
+
286
+ var result_step = new ResultSetStep();
287
+ result_step.start_time = ctx.getElapsedTime();
288
+ result_step.elapsed = 0;
289
+ result_step.fetch = recordCount;
290
+ result_step.sqlhash = queryHash;
291
+ result_step.dbc = dbc_hash;
292
+ ctx.profile.push(result_step);
293
+
294
+ ctx.rs_count = ctx.rs_count ? ctx.rs_count + recordCount : recordCount;
295
+ ctx.rs_time = ctx.rs_time ? ctx.rs_time + sql_step.elapsed : sql_step.elapsed;
296
+
297
+ MeterSql.addFetch(result_step.dbc, result_step.fetch, 0);
298
+ StatSql.addFetch(result_step.dbc, result_step.sqlhash, result_step.fetch, 0);
299
+
300
+ TraceSQL.isTooManyRecords(sql_step, result_step.fetch, ctx);
301
+ }
302
+
303
+ // 수정된 레코드 수 처리 (create, update, delete 등)
304
+ if (["create", "createMany", "update", "updateMany", "delete", "deleteMany"].includes(action)) {
305
+ if (result && result.count !== undefined) {
306
+ sql_step.updated = result.count;
307
+ } else if (result && typeof result === "object") {
308
+ sql_step.updated = 1;
309
+ }
310
+ }
311
+
312
+ // UPSERT 처리 추가
313
+ if (action === "upsert") {
314
+ sql_step.updated = 1; // upsert는 항상 1개의 레코드에 영향을 미침
315
+ }
316
+
317
+ this._finishQuery(ctx, sql_step);
125
318
 
126
- self._finishQuery(ctx, sql_start_time, result, queryInfo);
127
319
  ctx.footprint(`Prisma ${modelName}.${action} Done`);
128
320
 
129
321
  return result;
130
322
  } catch (err) {
131
- self._handleError(ctx, sql_start_time, err, queryInfo);
323
+ Logger.printError("WHATAP-308", `Middleware error in ${modelName}.${action}: ${err.message}`, err, false);
324
+
325
+ this._handleError(ctx, sql_step, err);
132
326
  ctx.footprint(`Prisma ${modelName}.${action} Error`);
133
327
  throw err;
134
328
  }
@@ -136,89 +330,106 @@ PrismaObserver.prototype.hookUseMiddleware = function(prismaInstance) {
136
330
  }
137
331
  };
138
332
 
139
- PrismaObserver.prototype._finishQuery = function(ctx, sql_start_time, result, queryInfo) {
140
- var sql_elapsed = Date.now() - sql_start_time;
141
- ctx.sql_time = (ctx.sql_time || 0) + sql_elapsed;
142
-
143
- // 결과 개수 계산
144
- let resultCount = 0;
145
- if (result) {
146
- if (Array.isArray(result)) {
147
- resultCount = result.length;
148
- } else if (result && typeof result === "object" && result.count !== undefined) {
149
- resultCount = result.count;
150
- } else if (result && typeof result === "object") {
151
- resultCount = 1;
152
- }
153
- }
333
+ PrismaObserver.prototype._finishQuery = function(ctx, sql_step) {
334
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
335
+ ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
336
+
337
+ TraceSQL.isSlowSQL(ctx);
154
338
 
155
- // SQL 패킷 전송 (mysql2-observer와 동일한 패턴)
156
- ctx.elapsed = sql_elapsed;
157
- ctx.active_sqlhash = false;
158
- AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, queryInfo, String(resultCount)]);
339
+ MeterSql.add(dbc_hash, sql_step.elapsed, false);
340
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, false, 0);
341
+
342
+ ctx.footprint("Prisma Query Done");
159
343
  };
160
344
 
161
- PrismaObserver.prototype._handleError = function(ctx, sql_start_time, err, queryInfo) {
162
- var sql_elapsed = Date.now() - sql_start_time;
163
- ctx.sql_time = (ctx.sql_time || 0) + sql_elapsed;
345
+ PrismaObserver.prototype._handleError = function(ctx, sql_step, err) {
346
+ sql_step.elapsed = ctx.getElapsedTime() - sql_step.start_time;
347
+ ctx.sql_time = (ctx.sql_time || 0) + sql_step.elapsed;
348
+
349
+ TraceSQL.isSlowSQL(ctx);
350
+
351
+ MeterSql.add(dbc_hash, sql_step.elapsed, true);
352
+ StatSql.addSqlTime(ctx.service_hash, sql_step.dbc, sql_step.hash, sql_step.elapsed, true, 0);
164
353
 
165
354
  try {
166
355
  const errorClassName = err.name || err.constructor?.name || "UnknownError";
167
- const errorMessage = err.message || 'Prisma error';
168
- let errorStack = '';
169
-
170
356
  ctx.error_class = errorClassName;
171
- ctx.error_message = errorMessage;
172
-
173
- // 스택 트레이스 처리
174
- if (conf.trace_sql_error_stack && conf.trace_sql_error_depth && err.stack) {
175
- var traceDepth = conf.trace_sql_error_depth;
176
- var stackLines = err.stack.split("\n");
357
+ ctx.error_message = err.message;
358
+ sql_step.error = StatError.addError(errorClassName, err.message, ctx.service_hash, TextTypes.SQL, sql_step.hash);
177
359
 
178
- if (stackLines.length > traceDepth) {
179
- stackLines = stackLines.slice(0, traceDepth + 1);
180
- }
181
- errorStack = stackLines.join("\n");
182
- ctx.error_message = errorStack;
360
+ if (ctx.error.isZero()) {
361
+ ctx.error = sql_step.error;
183
362
  }
184
363
 
185
- // 에러 무시 조건 확인 (mysql2-observer와 동일한 로직)
186
- var shouldAddError = false;
187
- if (conf._is_trace_ignore_err_cls_contains === true && errorClassName &&
188
- errorClassName.indexOf(conf.trace_ignore_err_cls_contains) < 0) {
189
- shouldAddError = true;
190
- } else if (conf._is_trace_ignore_err_msg_contains === true && errorMessage &&
191
- errorMessage.indexOf(conf.trace_ignore_err_msg_contains) < 0) {
192
- shouldAddError = true;
193
- } else if (conf._is_trace_ignore_err_cls_contains === false &&
194
- conf._is_trace_ignore_err_msg_contains === false) {
195
- shouldAddError = true;
196
- }
197
-
198
- if (shouldAddError) {
199
- if(!ctx.error) ctx.error = 1;
200
- ctx.status = 500;
201
- ctx.errClass = errorClassName;
202
- ctx.errMessage = errorMessage;
364
+ if (conf.trace_sql_error_stack && conf.trace_sql_error_depth) {
365
+ var traceDepth = conf.trace_sql_error_depth;
366
+ var errorStack = err.stack.split("\n");
203
367
 
204
- var errors = [errorClassName];
205
- if (errorMessage) {
206
- errors.push(errorMessage);
207
- }
208
- if (errorStack || err.stack) {
209
- errors.push(errorStack || err.stack);
368
+ if (errorStack.length > traceDepth) {
369
+ errorStack = errorStack.slice(0, traceDepth + 1);
210
370
  }
211
- AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
371
+ ctx.error_message = errorStack.join("\n");
212
372
  }
373
+ } catch (e) {
374
+ Logger.printError("WHATAP-309", "Error handling failed", e, false);
375
+ }
213
376
 
214
- // SQL 패킷 전송 (에러 발생시에도)
215
- ctx.elapsed = sql_elapsed;
216
- ctx.active_sqlhash = false;
217
- AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, queryInfo, "0"]);
377
+ ctx.footprint("Prisma Query Error");
378
+ };
218
379
 
380
+ var toParamBytes = function(p, crc) {
381
+ if (p == null || p.length === 0) {
382
+ return null;
383
+ }
384
+ try {
385
+ return ParamSecurity.encrypt(Buffer.from(p, "utf8"), crc);
219
386
  } catch (e) {
220
- Logger.printError("WHATAP-224", "Error handling failed", e, false);
387
+ return null;
221
388
  }
222
389
  };
223
390
 
391
+ var checkedSql = new IntKeyMap(2000).setMax(2000);
392
+ var nonLiteSql = new IntKeyMap(5000).setMax(5000);
393
+ var date = DateUtil.yyyymmdd();
394
+
395
+ function escapeLiteral(sql) {
396
+ if (sql == null) {
397
+ sql = "";
398
+ }
399
+
400
+ if (date !== DateUtil.yyyymmdd()) {
401
+ checkedSql.clear();
402
+ nonLiteSql.clear();
403
+ date = DateUtil.yyyymmdd();
404
+ Logger.print("WHATAP-SQL-CLEAR", "PrismaObserver CLEAR OK!!!!!!!!!!!!!!!!", false);
405
+ }
406
+
407
+ var sqlHash = HashUtil.hashFromString(sql);
408
+ var psql = nonLiteSql.get(sqlHash);
409
+
410
+ if (psql != null) {
411
+ return psql;
412
+ }
413
+ psql = checkedSql.get(sqlHash);
414
+
415
+ if (psql != null) {
416
+ return psql;
417
+ }
418
+
419
+ var els = new EscapeLiteralSQL(sql);
420
+ els.process();
421
+
422
+ var hash = HashUtil.hashFromString(els.getParsedSql());
423
+ DataTextAgent.SQL.add(hash, els.getParsedSql());
424
+
425
+ if (hash === sqlHash) {
426
+ psql = new ParsedSql(els.sqlType, hash, null);
427
+ nonLiteSql.put(sqlHash, psql);
428
+ } else {
429
+ psql = new ParsedSql(els.sqlType, hash, els.getParameter());
430
+ checkedSql.put(sqlHash, psql);
431
+ }
432
+ return psql;
433
+ }
434
+
224
435
  exports.PrismaObserver = PrismaObserver;
@@ -6,26 +6,28 @@
6
6
 
7
7
  const TraceContextManager = require('../trace/trace-context-manager');
8
8
  const conf = require('../conf/configure');
9
- const AsyncSender = require('../udp/async_sender');
10
- const DataOuputX = require('../io/data-outputx');
11
- const SecurityMaster = require('../net/security-master');
12
- const LogSinkPack = require('../pack/log-sink-pack');
9
+ const LogTracer = require('../logsink/log-tracer');
10
+ const Logger = require('../logger');
13
11
 
14
12
  let logsink_enabled = conf.getProperty('logsink_enabled', false);
13
+ let logTracer = logsink_enabled ? new LogTracer() : null;
15
14
 
16
- conf.on('logsink_enabled', function(newProperty) {
15
+ conf.on('logsink_enabled', function (newProperty) {
17
16
  logsink_enabled = newProperty;
17
+ logTracer = logsink_enabled ? new LogTracer() : null;
18
18
  });
19
19
 
20
20
  const ProcessObserver = function (agent) {
21
21
  this.agent = agent;
22
22
  this.packages = ['process'];
23
+ this.exitHandlersRegistered = false;
23
24
  };
24
25
 
25
26
  ProcessObserver.prototype.inject = function (mod, moduleName) {
26
27
  this._hookNextTick(mod);
27
28
  this._hookStdOutWrite();
28
29
  this._hookStdErrWrite();
30
+ this._hookProcessExit(mod);
29
31
  };
30
32
 
31
33
  ProcessObserver.prototype._hookNextTick = function (mod) {
@@ -39,26 +41,6 @@ ProcessObserver.prototype._hookNextTick = function (mod) {
39
41
  });
40
42
  };
41
43
 
42
- ProcessObserver.prototype._sendLogPack = function (content, category) {
43
- const ctx = TraceContextManager.getCurrentContext();
44
- const tags = ctx && ctx.id ? {'@txid': ctx.id.toString()} : {};
45
-
46
- const fields = {"filename": null};
47
-
48
- const p = new LogSinkPack();
49
- p.Category = category;
50
- p.time = Date.now();
51
- p.line = Date.now();
52
- p.content = content;
53
-
54
- p.pcode = SecurityMaster.PCODE;
55
- const bout = new DataOuputX();
56
- bout.writePack(p, null);
57
- const packbytes = bout.toByteArray();
58
-
59
- AsyncSender.send_relaypack(packbytes);
60
- };
61
-
62
44
  ProcessObserver.prototype._hookStdOutWrite = function () {
63
45
  this.agent.aop.after(process.stdout, 'write', (obj, args) => {
64
46
  if (conf.getProperty('logsink_enabled', false) && args[0]) {
@@ -67,12 +49,9 @@ ProcessObserver.prototype._hookStdOutWrite = function () {
67
49
  const parsedContent = JSON.parse(content);
68
50
  content = parsedContent.message ? parsedContent.message : JSON.stringify(parsedContent);
69
51
  } catch (e) {
70
- // JSON 파싱 실패 시 원본 content 사용
71
52
  }
72
-
73
- if (content && content.trim()) {
74
- const category = conf.getProperty('logsink_category_stdout', 'AppStdOut');
75
- this._sendLogPack(content, category);
53
+ if (logTracer && content) {
54
+ logTracer.addStdWrite(content, conf.logsink_category_stdout);
76
55
  }
77
56
  }
78
57
  });
@@ -86,15 +65,80 @@ ProcessObserver.prototype._hookStdErrWrite = function () {
86
65
  const parsedContent = JSON.parse(content);
87
66
  content = parsedContent.message ? parsedContent.message : JSON.stringify(parsedContent);
88
67
  } catch (e) {
89
- // JSON 파싱 실패 시 원본 content 사용
90
68
  }
91
-
92
- if (content && content.trim()) {
93
- const category = conf.getProperty('logsink_category_stderr', 'AppStdErr');
94
- this._sendLogPack(content, category);
69
+ if (logTracer && content) {
70
+ logTracer.addStdWrite(content, conf.logsink_category_stderr);
95
71
  }
96
72
  }
97
73
  });
98
74
  };
99
75
 
76
+ ProcessObserver.prototype._hookProcessExit = function (mod) {
77
+ if (this.exitHandlersRegistered) {
78
+ Logger.print("WHATAP-901", "[ProcessObserver] Exit handlers already registered, skipping...", false)
79
+ return;
80
+ }
81
+ const self = this;
82
+
83
+ // SIGTERM (graceful shutdown)
84
+ process.on('SIGTERM', function () {
85
+ const message = 'Process termination requested by system or process manager (SIGTERM) - Usually from service stop or container shutdown';
86
+ Logger.print('WHATAP-902', message, false);
87
+ setTimeout(() => process.exit(0), 100);
88
+ });
89
+
90
+ // SIGINT (Ctrl+C)
91
+ process.on('SIGINT', function () {
92
+ const message = 'Process interrupted by user (SIGINT) - Typically Ctrl+C or kill command';
93
+ Logger.print('WHATAP-903', message, false);
94
+ setTimeout(() => process.exit(0), 100);
95
+ });
96
+
97
+ // SIGHUP (hang up)
98
+ process.on('SIGHUP', function () {
99
+ const message = 'Process hangup signal received (SIGHUP) - Terminal disconnection or parent process terminated';
100
+ Logger.print('WHATAP-904', message, false);
101
+ setTimeout(() => process.exit(0), 100);
102
+ });
103
+
104
+ // SIGUSR1 (user-defined signal 1)
105
+ process.on('SIGUSR1', function () {
106
+ const message = 'User-defined signal 1 received (SIGUSR1) - Custom application signal';
107
+ Logger.print('WHATAP-905', message, false);
108
+ setTimeout(() => process.exit(0), 100);
109
+ });
110
+
111
+ // SIGUSR2 (user-defined signal 2)
112
+ process.on('SIGUSR2', function () {
113
+ const message = 'User-defined signal 2 received (SIGUSR2) - Custom application signal';
114
+ Logger.print('WHATAP-906', message, false);
115
+ setTimeout(() => process.exit(0), 100);
116
+ });
117
+
118
+ // 처리되지 않은 예외
119
+ // process.on('uncaughtException', function (err) {
120
+ // const message = `Unhandled exception caused process crash - Error: ${err.message}, File: ${err.stack ? err.stack.split('\n')[1] || 'unknown' : 'unknown'}`;
121
+ // Logger.print('WHATAP-907', message, err, false);
122
+ //
123
+ // // 로그 남긴 후 프로세스 종료
124
+ // setTimeout(() => {
125
+ // process.exit(1);
126
+ // }, 100);
127
+ // });
128
+ //
129
+ // // 처리되지 않은 Promise rejection
130
+ // process.on('unhandledRejection', function (reason, promise) {
131
+ // const message = `Unhandled Promise rejection may cause process termination - Reason: ${reason instanceof Error ? reason.message : String(reason)}`;
132
+ // const err = reason instanceof Error ? reason : new Error(String(reason));
133
+ // Logger.print('WHATAP-908', message, err, false);
134
+ //
135
+ // // 로그 남긴 후 프로세스 종료
136
+ // setTimeout(() => {
137
+ // process.exit(1);
138
+ // }, 100);
139
+ // });
140
+
141
+ this.exitHandlersRegistered = true;
142
+ };
143
+
100
144
  module.exports.ProcessObserver = ProcessObserver;