@oas-tools/oas-telemetry 0.2.0 → 0.2.1

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/dist/ui.cjs CHANGED
@@ -5,608 +5,685 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = ui;
7
7
  function ui() {
8
- /*
9
- Sources:
10
- ui/detail.html
11
- ui/main.html
12
- Parsing:
13
- ` --> \`
14
- $ --> \$
15
- */
16
-
17
8
  return {
18
9
  main: `
19
- <!DOCTYPE html>
20
- <html lang="en">
21
- <head>
22
- <meta charset="UTF-8">
23
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
- <title>OAS - Telemetry</title>
25
- <style>
26
- body {
27
- font-family: Arial, sans-serif;
28
- margin: 20px;
29
- }
30
- table {
31
- width: 100%;
32
- border-collapse: collapse;
33
- }
34
- th, td {
35
- border: 1px solid #dddddd;
36
- padding: 8px;
37
- text-align: left;
38
- cursor: pointer;
39
- }
40
- th {
41
- background-color: #f2f2f2;
42
- }
43
- #telemetryStatusSpan {
44
- color:rgb(0, 123, 6);
45
- font-size: x-small;
10
+ <!DOCTYPE html>
11
+ <html lang="en">
12
+
13
+ <head>
14
+ <meta charset="UTF-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <title>OAS - Telemetry</title>
17
+ <style>
18
+ body {
19
+ font-family: Arial, sans-serif;
20
+ margin: 20px;
21
+ }
22
+
23
+ table {
24
+ width: 100%;
25
+ border-collapse: collapse;
26
+ }
27
+
28
+ th,
29
+ td {
30
+ border: 1px solid #dddddd;
31
+ padding: 8px;
32
+ text-align: left;
33
+ }
34
+
35
+ th {
36
+ background-color: #f2f2f2;
37
+ }
38
+
39
+ #telemetryStatusSpan {
40
+ color: rgb(0, 123, 6);
41
+ font-size: x-small;
42
+ }
43
+ </style>
44
+ </head>
45
+
46
+ <body>
47
+ <h1>Telemetry <span id="telemetryStatusSpan"></span></h1>
48
+
49
+ <label><input type="checkbox" id="toggleTelemetry"> Allow Server Telemetry</label>
50
+ <br><br>
51
+ <button onclick="fetch('/telemetry/reset');showTelemetryStatus();">Reset Telemetry Data</button>
52
+ <br><br>
53
+ <label><input type="checkbox" id="autoUpdate"> Allow Client Auto Update</label>
54
+ <br><br>
55
+ <table id="apiTable">
56
+ <thead>
57
+ <tr>
58
+ <th onclick="sortTable(0)">Path</th>
59
+ <th onclick="sortTable(1)">Method</th>
60
+ <th onclick="sortTable(2)">Status</th>
61
+ <th onclick="sortTable(3)">Description</th>
62
+ <th onclick="sortTable(4)" style="text-align: center;">Request <br> Count</th>
63
+ <th onclick="sortTable(5)" style="text-align: center;">Average response time<br> (sec)</th>
64
+ <th style="text-align: center;">Auto<br>Update</th>
65
+ </tr>
66
+ </thead>
67
+ <tbody>
68
+ </tbody>
69
+ </table>
70
+
71
+ <script>
72
+ let LOG = false;
73
+
74
+
75
+ let intervalTimer = {
76
+ disabled: true,
77
+ period: 2000,
78
+ subscribers: [],
79
+ start: function () {
80
+ this.interval = setInterval(() => this.tick(), this.period);
81
+ },
82
+ tick: function () {
83
+ if (this.disabled) return;
84
+ log("tick");
85
+ this.subscribers.forEach(callback => callback());//execute all the callbacks
86
+ },
87
+ subscribe: function (callback) {
88
+ this.subscribers.push(callback);
89
+ },
90
+ unsubscribe: function (callback) {
91
+ this.subscribers.pop(callback)
46
92
  }
47
- </style>
48
- </head>
49
- <body>
50
- <h1>Telemetry <span id="telemetryStatusSpan"></span></h1>
51
- <table id="apiTable">
52
- <thead>
53
- <tr>
54
- <th onclick="sortTable(0)">Path</th>
55
- <th onclick="sortTable(1)">Method</th>
56
- <th onclick="sortTable(2)">Status</th>
57
- <th onclick="sortTable(3)">Description</th>
58
- <th onclick="sortTable(4)" style="text-align: center;">Request <br>Count</th>
59
- <th onclick="sortTable(5)" style="text-align: center;">Average response time<br> (sec)</th>
60
-
61
- </tr>
62
- </thead>
63
- <tbody>
64
- </tbody>
65
- </table>
66
- <br/>
67
- <button onclick="fetch('/telemetry/start');loadTelemetryStatus();">Start</button>
68
- <button onclick="fetch('/telemetry/stop');loadTelemetryStatus();">Stop</button>
69
- <button onclick="fetch('/telemetry/reset');loadTelemetryStatus();">Reset</button>
70
- <script>
71
-
72
- let LOG=false;
73
-
74
- function log(s){
75
- if(LOG)
76
- console.log(s);
77
93
  }
78
-
94
+
95
+
96
+
97
+
98
+
99
+ function log(s) {
100
+ if (LOG) console.log(s);
101
+ }
102
+
79
103
  async function fetchSpec() {
80
- try {
81
- const response = await fetch("/telemetry/spec");
82
- if (!response.ok) {
83
- throw new Error("ERROR getting the Spec");
84
- }
85
- apiSpec = await response.json();
86
- loadAPISpec(apiSpec);
87
- loadTelemetryStatus();
88
- } catch (error) {
89
- console.error("ERROR getting the Spec :", error);
90
- }
91
- }
92
-
93
- async function loadTelemetryStatus(){
94
- log("TEST");
95
- const tss = document.getElementById("telemetryStatusSpan");
96
- const response = await fetch("/telemetry/status");
97
- if (!response.ok) {
98
- throw new Error("ERROR getting the Status");
99
- return;
100
- }
101
- tStatus = await response.json();
102
-
103
- log(tStatus);
104
- if(tStatus.active){
105
- tss.textContent = "active";
106
- tss.style.color = "#009900";
107
- }
108
- else{
109
- tss.textContent = "stoped";
110
- tss.style.color = "#666666";
104
+ try {
105
+ const response = await fetch("/telemetry/spec");
106
+ if (!response.ok) {
107
+ throw new Error("ERROR getting the Spec");
108
+ }
109
+ apiSpec = await response.json();
110
+ return apiSpec;
111
+
112
+ } catch (error) {
113
+ console.error("ERROR getting the Spec :", error);
114
+ return null;
111
115
  }
112
-
113
116
  }
114
-
115
-
116
-
117
- function getPathRegEx(path){
117
+
118
+
119
+ function getPathRegEx(path) {
118
120
  let pathComponents = path.split("/");
119
121
  let pathRegExpStr = "^"
120
-
121
- pathComponents.forEach(c =>{
122
- if(c != "") {
122
+
123
+ pathComponents.forEach(c => {
124
+ if (c != "") {
123
125
  pathRegExpStr += "/";
124
- if(c.charAt(0) == "{" && c.charAt(c.length-1) == "}"){
126
+ if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
125
127
  pathRegExpStr += "(.*)";
126
- }else{
128
+ } else {
127
129
  pathRegExpStr += c;
128
130
  }
129
131
  }
130
132
  });
131
-
133
+
132
134
  pathRegExpStr += "\$";
133
-
134
- return new RegExp(pathRegExpStr);
135
+
136
+ return pathRegExpStr;
135
137
  }
136
-
137
-
138
- async function fetchTracesByParsing(path,method,status) {
138
+
139
+ async function fetchTracesByFind(path, method, status) {
139
140
  try {
140
- log(\`Fetchig traces for <\${path}> - \${method} - \${status},.. \`);
141
-
142
- const response = await fetch("/telemetry/list");
143
-
141
+ log(\`Fetching traces for <\${path}> - \${method} - \${status} \`);
142
+ const body = {
143
+ "flags": { "containsRegex": true },
144
+ "config": { "regexIds": ["attributes.http.target"] },
145
+ "search": {
146
+ "attributes.http.target": getPathRegEx(path),
147
+ "attributes.http.method": method.toUpperCase(),
148
+ "attributes.http.status_code": parseInt(status)
149
+ }
150
+ };
151
+ log("body: " + JSON.stringify(body, null, 2));
152
+ //response is to the post at /telemetry/find
153
+ const response = await fetch("/telemetry/find", {
154
+ method: "POST",
155
+ headers: {
156
+ "Content-Type": "application/json"
157
+ },
158
+ body: JSON.stringify(body)
159
+ });
160
+
144
161
  if (!response.ok) {
145
- throw new Error("ERROR getting the Traces.");
162
+ throw new Error("ERROR getting the Traces.");
146
163
  }
147
-
164
+
148
165
  const responseJSON = await response.json();
149
166
  const traces = responseJSON.spans;
150
-
151
- log(\`Feched \${traces.length} traces.\`);
152
- //log(\`First trace: \${JSON.stringify(traces[0],null,2)}\`);
153
-
154
- return traces.filter((t)=>{
155
- return (
156
- (getPathRegEx(path).test(t.attributes.http_dot_target)) &&
157
- (t.attributes.http_dot_method.toUpperCase().includes(method.toUpperCase())) &&
158
- (t.attributes.http_dot_status_code == status)
159
- );
160
- });
161
-
167
+
168
+ log(\`Fetched \${traces.length} traces.\`);
169
+ return traces;
170
+
162
171
  } catch (error) {
163
172
  console.error("ERROR getting the Traces :", error);
164
173
  }
165
174
  }
166
- function calculateTiming(startSecInput,startNanoSecInput,endSecInput,endNanoSecInput,precision = 3){
167
- // Default precision 3 = miliseconds
168
-
169
- let startSec= parseFloat(startSecInput)
170
- let startNanoSec= parseFloat(startNanoSecInput)
171
- let endSec= parseFloat(endSecInput)
172
- let endNanoSec= parseFloat(endNanoSecInput)
173
-
174
- let startNanoSecParsed = parseFloat("0."+startNanoSec);
175
- let endNanoSecParsed = parseFloat("0."+endNanoSec);
176
-
175
+
176
+ function calculateTiming(startSecInput, startNanoSecInput, endSecInput, endNanoSecInput, precision = 3) {
177
+ let startSec = parseFloat(startSecInput);
178
+ let startNanoSec = parseFloat(startNanoSecInput);
179
+ let endSec = parseFloat(endSecInput);
180
+ let endNanoSec = parseFloat(endNanoSecInput);
181
+
182
+ let startNanoSecParsed = parseFloat("0." + startNanoSec);
183
+ let endNanoSecParsed = parseFloat("0." + endNanoSec);
184
+
177
185
  let preciseStart = parseFloat(startSec + startNanoSecParsed);
178
186
  let preciseEnd = parseFloat(endSec + endNanoSecParsed);
179
- let preciseDuration = parseFloat(preciseEnd-preciseStart);
180
-
181
- let startDate = new Date(preciseStart.toFixed(precision)*1000);
187
+ let preciseDuration = parseFloat(preciseEnd - preciseStart);
188
+
189
+ let startDate = new Date(preciseStart.toFixed(precision) * 1000);
182
190
  let startTS = startDate.toISOString();
183
-
184
- let endDate = new Date(preciseEnd.toFixed(precision)*1000);
191
+
192
+ let endDate = new Date(preciseEnd.toFixed(precision) * 1000);
185
193
  let endTS = endDate.toISOString();
186
-
194
+
187
195
  return {
188
196
  preciseStart: preciseStart,
189
- preciseEnd : preciseEnd,
190
- preciseDuration : preciseDuration,
191
- start : parseFloat(preciseStart.toFixed(precision)),
197
+ preciseEnd: preciseEnd,
198
+ preciseDuration: preciseDuration,
199
+ start: parseFloat(preciseStart.toFixed(precision)),
192
200
  end: parseFloat(preciseEnd.toFixed(precision)),
193
- duration : parseFloat(preciseDuration.toFixed(precision)),
201
+ duration: parseFloat(preciseDuration.toFixed(precision)),
194
202
  startDate: startDate,
195
203
  endDate: endDate,
196
- startTS :startTS,
204
+ startTS: startTS,
197
205
  endTS: endTS
198
206
  };
199
-
200
- }
201
-
202
- function parseTraceInfo(t){
203
- const ep = t.attributes.http_dot_target;
204
- const method = t.attributes.http_dot_method.toLowerCase();
205
- const status = t.attributes.http_dot_status_code;
206
-
207
- const timing = calculateTiming(t.startTime[0],t.startTime[1],t.endTime[0],t.endTime[1]);
208
-
207
+ }
208
+
209
+ function parseTraceInfo(t) {
210
+ const ep = t.attributes.http.target;
211
+ const method = t.attributes.http.method.toLowerCase();
212
+ const status = t.attributes.http.status_code;
213
+
214
+ const timing = calculateTiming(t.startTime[0], t.startTime[1], t.endTime[0], t.endTime[1]);
215
+
209
216
  log(\`\${timing.startTS} - \${timing.endTS} - \${t._spanContext.traceId} - \${t.name} - \${ep} - \${status} - \${timing.duration}\`);
210
217
  return {
211
- ts : timing.startTS,
218
+ ts: timing.startTS,
212
219
  ep: ep,
213
220
  method: method,
214
221
  status: status,
215
222
  duration: timing.duration
216
223
  };
217
224
  }
218
-
219
- async function loadStats(path,method,status,cellRequestCount,cellAverageResponseTime){
220
- let traces = await fetchTracesByParsing(path,method,status);
225
+
226
+ async function loadStats(path, method, status, cellRequestCount, cellAverageResponseTime) {
227
+ log(\`loadStats(\${path}, \${method}, \${status}, \${cellRequestCount}, \${cellAverageResponseTime})\`);
228
+ let traces = await fetchTracesByFind(path, method, status);
221
229
  let requestCount = traces.length;
222
230
  let averageResponseTime = 0;
223
-
224
- traces.forEach(trace=>{
231
+
232
+ traces.forEach(trace => {
225
233
  t = parseTraceInfo(trace);
226
- log(JSON.stringify(t,null,2));
234
+ log(JSON.stringify(t, null, 2));
227
235
  averageResponseTime += parseFloat(t.duration);
228
- log(\`averageResponseTime += t.duration --> \${averageResponseTime} += \${ t.duration }\`);
236
+ log(\`averageResponseTime += t.duration --> \${averageResponseTime} += \${t.duration}\`);
229
237
  });
230
-
231
-
238
+
232
239
  averageResponseTime = averageResponseTime / requestCount;
233
-
240
+
234
241
  log(\`averageResponseTime = averageResponseTime / requestCount --> \${averageResponseTime} = \${averageResponseTime} / \${requestCount}\`);
235
-
242
+
236
243
  cellRequestCount.textContent = requestCount;
237
- cellAverageResponseTime.textContent = requestCount? averageResponseTime.toFixed(3):"--";
238
-
239
- setTimeout(loadStats,2000,path,method,status,cellRequestCount,cellAverageResponseTime);
240
-
241
- }
242
-
243
- function loadAPISpec(apiSpec) {
244
-
244
+ cellAverageResponseTime.textContent = requestCount ? averageResponseTime.toFixed(3) : "--";
245
+
246
+ // setTimeout(() => loadStats(path, method, status, cellRequestCount, cellAverageResponseTime), 2000);
247
+ }
248
+
249
+ function populateOASTable(apiSpec) {
245
250
  const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
246
251
  Object.keys(apiSpec.paths).forEach(path => {
247
252
  Object.keys(apiSpec.paths[path]).forEach(method => {
248
253
  Object.keys(apiSpec.paths[path][method].responses).forEach(responseType => {
249
- if(!Number.isNaN(parseInt(responseType))){
254
+ if (!Number.isNaN(parseInt(responseType))) {
250
255
  const row = tableBody.insertRow();
251
256
  const cellPath = row.insertCell(0);
252
257
  const cellMethod = row.insertCell(1);
253
258
  const cellStatus = row.insertCell(2);
254
259
  const cellDescription = row.insertCell(3);
255
260
  const cellRequestCount = row.insertCell(4);
256
- cellRequestCount.style="text-align: center;"
261
+ cellRequestCount.style = "text-align: center;";
262
+ cellRequestCount.textContent = "--";
257
263
  const cellAverageResponseTime = row.insertCell(5);
258
264
  cellAverageResponseTime.style.textAlign = "center";
259
-
265
+ cellAverageResponseTime.textContent = "--";
266
+
267
+ const basePath = apiSpec.basePath ? apiSpec.basePath : "";
268
+ const fullPath = basePath + path;
260
269
  cellPath.textContent = path;
270
+ cellPath.style.cursor = 'pointer';
271
+ cellPath.onclick = function () {
272
+ window.location.href = row.detailPath;
273
+ };
261
274
  cellMethod.textContent = method.toUpperCase();
262
275
  cellStatus.textContent = responseType;
263
- cellDescription.textContent = apiSpec.paths[path][method].summary
264
- + " - "
265
- + apiSpec.paths[path][method].responses[responseType].description;
266
-
267
-
268
- loadStats(path,method.toLowerCase(),responseType,cellRequestCount,cellAverageResponseTime);
269
- setTimeout(loadStats,1000,path,method.toLowerCase(),responseType,cellRequestCount,cellAverageResponseTime);
270
-
271
-
272
- row.detailPath = \`/telemetry/detail/\${responseType}/\${method.toLowerCase()}\${path}\`;
273
- row.onclick = function(){
274
- window.location.href = this.detailPath;
275
- };
276
+ cellDescription.textContent = apiSpec.paths[path][method].summary
277
+ + " - "
278
+ + apiSpec.paths[path][method].responses[responseType].description;
279
+
280
+ row.detailPath = \`/telemetry/detail/\${responseType}/\${method.toLowerCase()}\${fullPath}\`;
281
+ const cellCheckbox = row.insertCell(6);
282
+
283
+ // Create and insert checkbox
284
+ const checkbox = document.createElement('input');
285
+ checkbox.type = 'checkbox';
286
+ cellCheckbox.appendChild(checkbox);
287
+ cellCheckbox.style.textAlign = 'center';
288
+ // class to search for the checkboxes
289
+ checkbox.className = 'autoUpdateCheckbox';
290
+ checkbox.disabled = intervalTimer.disabled;
291
+
292
+ // Add event listener to handle checkbox auto-update
293
+ checkbox.addEventListener('change', function () {
294
+ if (this.checked)
295
+ intervalTimer.subscribe(() =>
296
+ loadStats(fullPath, method.toLowerCase(), responseType, cellRequestCount, cellAverageResponseTime)
297
+ );
298
+ else
299
+ intervalTimer.unsubscribe(() =>
300
+ loadStats(fullPath, method.toLowerCase(), responseType, cellRequestCount, cellAverageResponseTime)
301
+ );
302
+ });
303
+
276
304
  }
277
305
  });
278
306
  });
279
307
  });
280
308
  }
281
-
309
+
282
310
  function sortTable(column) {
283
311
  const table = document.getElementById('apiTable');
284
312
  let rows, switching, i, x, y, shouldSwitch;
285
313
  switching = true;
286
- // Loop until no switching has been done:
287
314
  while (switching) {
288
315
  switching = false;
289
316
  rows = table.rows;
290
- // Loop through all table rows (except the first, which contains table headers):
291
317
  for (i = 1; i < (rows.length - 1); i++) {
292
318
  shouldSwitch = false;
293
- // Get the two elements you want to compare, one from current row and one from the next:
294
319
  x = rows[i].getElementsByTagName("TD")[column];
295
320
  y = rows[i + 1].getElementsByTagName("TD")[column];
296
- // Check if the two rows should switch place:
297
321
  if (x.textContent.toLowerCase() > y.textContent.toLowerCase()) {
298
322
  shouldSwitch = true;
299
323
  break;
300
324
  }
301
325
  }
302
326
  if (shouldSwitch) {
303
- // If a switch has been marked, make the switch and mark that a switch has been done:
304
327
  rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
305
328
  switching = true;
306
329
  }
307
330
  }
308
331
  }
309
-
310
- window.onload = fetchSpec();
311
- </script>
312
- </body>
313
- </html>
314
- `,
315
- detail: `
316
332
 
317
- <!DOCTYPE html>
318
- <html lang="en">
319
- <head>
320
- <meta charset="UTF-8">
321
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
322
- <title>OAS - Telemetry</title>
323
- <style>
324
- body {
325
- font-family: Arial, sans-serif;
326
- margin: 20px;
327
- }
328
- table {
329
- width: 100%;
330
- border-collapse: collapse;
331
- }
332
- th, td {
333
- border: 1px solid #dddddd;
334
- padding: 8px;
335
- text-align: left;
336
- cursor: pointer;
337
- }
338
- th {
339
- background-color: #f2f2f2;
333
+ document.getElementById('autoUpdate').addEventListener('change', function () {
334
+ const autoUpdateDisabled = !this.checked;
335
+ intervalTimer.disabled = autoUpdateDisabled;
336
+ // Disable/Enable all the checkboxes
337
+ const checkboxes = document.getElementsByClassName('autoUpdateCheckbox');
338
+ for (let i = 0; i < checkboxes.length; i++) {
339
+ checkboxes[i].disabled = autoUpdateDisabled;
340
340
  }
341
- .box {
342
- width: 100%;
343
- margin: 0 auto;
344
- background: rgba(255,255,255,0.2);
345
- padding: 35px;
346
- border: 2px solid #fff;
347
- border-radius: 20px/50px;
348
- background-clip: padding-box;
349
- text-align: center;
350
- }
351
-
352
- .overlay {
353
- position: fixed;
354
- top: 0;
355
- bottom: 0;
356
- left: 0;
357
- right: 0;
358
- background: rgba(0, 0, 0, 0.7);
359
- transition: opacity 500ms;
360
- visibility: hidden;
361
- opacity: 0;
362
- overflow: scroll;
363
- }
364
-
365
- .overlay:target {
366
- visibility: visible;
367
- opacity: 1;
341
+ });
342
+
343
+ async function showTelemetryStatus() {
344
+ const tss = document.getElementById("telemetryStatusSpan");
345
+ const response = await fetch("/telemetry/status");
346
+ if (!response.ok) {
347
+ throw new Error("ERROR getting the Status");
348
+ return false;
368
349
  }
369
-
370
- .popup {
371
- margin: 70px auto;
372
- padding: 20px;
373
- background: #fff;
374
- border-radius: 5px;
375
- width: 70%;
376
- position: relative;
377
- transition: all 5s ease-in-out;
378
- font-size: small;
379
- overflow: scroll;
350
+ tStatus = await response.json();
351
+
352
+ log(tStatus);
353
+ if (tStatus.active) {
354
+ tss.textContent = "active";
355
+ tss.style.color = "#009900";
356
+ } else {
357
+ tss.textContent = "stopped";
358
+ tss.style.color = "#666666";
380
359
  }
381
-
382
- .popup .close {
383
- position: absolute;
384
- top: 20px;
385
- right: 30px;
386
- transition: all 200ms;
387
- font-size: 30px;
388
- font-weight: bold;
389
- text-decoration: none;
390
- color: #333;
360
+ return tStatus.active;
361
+ }
362
+
363
+ document.getElementById('toggleTelemetry').addEventListener('change', function () {
364
+ if (this.checked) {
365
+ fetch('/telemetry/start').then(showTelemetryStatus());
366
+ } else {
367
+ fetch('/telemetry/stop').then(showTelemetryStatus());
391
368
  }
392
-
393
- </style>
394
- </head>
395
- <body>
396
- <h1><span id="heading">Telemetry for...</span></h1>
397
- <a href="/telemetry/">Back</a><br><br>
398
- <table id="apiTable">
399
- <thead>
400
- <tr>
401
- <th onclick="sortTable(0)">TimeStamp</th>
402
- <th onclick="sortTable(1)">End point</th>
403
- <th onclick="sortTable(2)">Method</th>
404
- <th onclick="sortTable(3)">Status</th>
405
- <th onclick="sortTable(4)" style="text-align: center;">Response time<br> (sec)</th>
406
- </tr>
407
- </thead>
408
- <tbody>
409
- </tbody>
410
- </table>
411
- <script>
412
-
413
- let traceCount=-1;
414
- let LOG=false;
415
-
416
- function log(s){
417
- if(LOG)
369
+ });
370
+
371
+ window.onload = async function () {
372
+ document.getElementById('autoUpdate').checked = !intervalTimer.disabled; // Default to false
373
+ const activeTelemetry = await showTelemetryStatus();
374
+ document.getElementById('toggleTelemetry').checked = activeTelemetry; // Default to not toggled
375
+ const apiSpec = await fetchSpec()
376
+ populateOASTable(apiSpec);
377
+ intervalTimer.start();
378
+ };
379
+ </script>
380
+ </body>
381
+
382
+ </html>`,
383
+ detail: `
384
+ <!DOCTYPE html>
385
+ <html lang="en">
386
+
387
+ <head>
388
+ <meta charset="UTF-8">
389
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
390
+ <title>OAS - Telemetry</title>
391
+ <style>
392
+ body {
393
+ font-family: Arial, sans-serif;
394
+ margin: 20px;
395
+ }
396
+
397
+ table {
398
+ width: 100%;
399
+ border-collapse: collapse;
400
+ }
401
+
402
+ th,
403
+ td {
404
+ border: 1px solid #dddddd;
405
+ padding: 8px;
406
+ text-align: left;
407
+ cursor: pointer;
408
+ }
409
+
410
+ th {
411
+ background-color: #f2f2f2;
412
+ }
413
+
414
+ .box {
415
+ width: 100%;
416
+ margin: 0 auto;
417
+ background: rgba(255, 255, 255, 0.2);
418
+ padding: 35px;
419
+ border: 2px solid #fff;
420
+ border-radius: 20px/50px;
421
+ background-clip: padding-box;
422
+ text-align: center;
423
+ }
424
+
425
+ .overlay {
426
+ position: fixed;
427
+ top: 0;
428
+ bottom: 0;
429
+ left: 0;
430
+ right: 0;
431
+ background: rgba(0, 0, 0, 0.7);
432
+ transition: opacity 500ms;
433
+ visibility: hidden;
434
+ opacity: 0;
435
+ overflow: scroll;
436
+ }
437
+
438
+ .overlay:target {
439
+ visibility: visible;
440
+ opacity: 1;
441
+ }
442
+
443
+ .popup {
444
+ margin: 70px auto;
445
+ padding: 20px;
446
+ background: #fff;
447
+ border-radius: 5px;
448
+ width: 70%;
449
+ position: relative;
450
+ transition: all 5s ease-in-out;
451
+ font-size: small;
452
+ overflow: scroll;
453
+ }
454
+
455
+ .popup .close {
456
+ position: absolute;
457
+ top: 20px;
458
+ right: 30px;
459
+ transition: all 200ms;
460
+ font-size: 30px;
461
+ font-weight: bold;
462
+ text-decoration: none;
463
+ color: #333;
464
+ }
465
+ </style>
466
+ </head>
467
+
468
+ <body>
469
+ <h1><span id="heading">Telemetry for...</span></h1>
470
+ <a href="/telemetry/">Back</a><br><br>
471
+ <table id="apiTable">
472
+ <thead>
473
+ <tr>
474
+ <th onclick="sortTable(0)">TimeStamp</th>
475
+ <th onclick="sortTable(1)">End point</th>
476
+ <th onclick="sortTable(2)">Method</th>
477
+ <th onclick="sortTable(3)">Status</th>
478
+ <th onclick="sortTable(4)" style="text-align: center;">Response time<br> (sec)</th>
479
+ </tr>
480
+ </thead>
481
+ <tbody>
482
+ </tbody>
483
+ </table>
484
+ <script>
485
+
486
+ let traceCount = -1;
487
+ let LOG = true;
488
+
489
+ function log(s) {
490
+ if (LOG)
418
491
  console.log(s);
419
492
  }
420
-
493
+
421
494
  function parsePath() {
422
-
495
+
423
496
  let detailPath = window.location.pathname.split("/");
424
-
425
- if(detailPath.length < 6 || detailPath[5] == ""){
497
+
498
+ if (detailPath.length < 6 || detailPath[5] == "") {
426
499
  alert("Wrong invocation params");
427
500
  return;
428
501
  }
429
-
502
+
430
503
  let status = parseInt(detailPath[3]);
431
-
432
- if(Number.isNaN(status))
504
+
505
+ if (Number.isNaN(status))
433
506
  status = -1;
434
-
507
+
435
508
  const method = detailPath[4];
436
-
437
-
438
- const path = decodeURI("/"+ detailPath
439
- .splice(5,detailPath.length-5)
440
- .filter(c=>(c != ""))
441
- .join("/"));
442
-
509
+
510
+
511
+ const path = decodeURI("/" + detailPath
512
+ .splice(5, detailPath.length - 5)
513
+ .filter(c => (c != ""))
514
+ .join("/"));
515
+
443
516
  headingObj = document.getElementById('heading');
444
517
  headingObj.textContent = \`Telemetry for \${path} - \${method} - \${status} \`;
445
- fetchTracesByParsing(path,method,status);
518
+ fetchTracesByParsing(path, method, status);
446
519
  }
447
-
448
- function getSearchQuery(path,method,status){
520
+
521
+ function getSearchQuery(path, method, status) {
449
522
  let pathComponents = path.split("/");
450
523
  let pathRegex = "^"
451
-
452
- pathComponents.forEach(c =>{
453
- if(c != "") {
524
+
525
+ pathComponents.forEach(c => {
526
+ if (c != "") {
454
527
  pathRegex += "/";
455
- if(c.charAt(0) == "{" && c.charAt(c.length-1) == "}"){
528
+ if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
456
529
  pathRegex += "(.*)";
457
- }else{
530
+ } else {
458
531
  pathRegex += c;
459
532
  }
460
533
  }
461
534
  });
462
-
535
+
463
536
  pathRegex += "\$";
464
-
537
+
465
538
  return {
466
- "attributes.http_dot_target" : { \$regex: new RegExp(pathRegex)},
467
- "name" : method.toUpperCase(),
468
- "attributes.http_dot_status_code" : status
539
+ "attributes.http.target": { \$regex: new RegExp(pathRegex) },
540
+ "name": method.toUpperCase(),
541
+ "attributes.http.status_code": status
469
542
  };
470
543
  }
471
-
472
- async function fetchTracesByFinding(path,method,status) {
544
+
545
+ async function fetchTracesByFinding(path, method, status) {
473
546
  try {
474
- const response = await fetch("/telemetry/find",{
475
- method: 'POST',
476
- headers: {
477
- 'Accept': 'application/json',
478
- 'Content-Type': 'application/json'
479
- },
480
- body: JSON.stringify({a: 1, b: 'Textual content'})
481
- });
482
-
483
- if (!response.ok) {
484
- throw new Error("ERROR getting the Traces.");
547
+ log(\`Fetching traces for <\${path}> - \${method} - \${status} \`);
548
+ const body = {
549
+ "flags": { "containsRegex": true },
550
+ "config": { "regexIds": ["attributes.http.target"] },
551
+ "search": {
552
+ "attributes.http.target": getPathRegEx(path),
553
+ "attributes.http.method": method.toUpperCase(),
554
+ "attributes.http.status_code": parseInt(status)
555
+ }
556
+ };
557
+ log("body: " + JSON.stringify(body, null, 2));
558
+ //response is to the post at /telemetry/find
559
+ const response = await fetch("/telemetry/find", {
560
+ method: "POST",
561
+ headers: {
562
+ "Content-Type": "application/json"
563
+ },
564
+ body: JSON.stringify(body)
565
+ });
566
+
567
+ if (!response.ok) {
568
+ throw new Error("ERROR getting the Traces.");
569
+ }
570
+
571
+ const traces = await response.json();
572
+ loadTraces(traces);
573
+
574
+ } catch (error) {
575
+ console.error("ERROR getting the Traces :", error);
485
576
  }
486
-
487
- const traces = await response.json();
488
- loadTraces(traces);
489
-
490
- } catch (error) {
491
- console.error("ERROR getting the Traces :", error);
492
- }
493
- }
494
-
495
- function getPathRegEx(path){
577
+ }
578
+
579
+ function getPathRegEx(path) {
496
580
  let pathComponents = path.split("/");
497
581
  let pathRegExpStr = "^"
498
-
499
- pathComponents.forEach(c =>{
500
- if(c != "") {
582
+
583
+ pathComponents.forEach(c => {
584
+ if (c != "") {
501
585
  pathRegExpStr += "/";
502
- if(c.charAt(0) == "{" && c.charAt(c.length-1) == "}"){
586
+ if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
503
587
  pathRegExpStr += "(.*)";
504
- }else{
588
+ } else {
505
589
  pathRegExpStr += c;
506
590
  }
507
591
  }
508
592
  });
509
-
593
+
510
594
  pathRegExpStr += "\$";
511
-
512
- return new RegExp(pathRegExpStr);
595
+
596
+ return pathRegExpStr;
513
597
  }
514
-
515
- async function fetchTracesByParsing(path,method,status) {
598
+
599
+ async function fetchTracesByParsing(path, method, status) {
516
600
  try {
517
601
  log(\`Fetchig traces for <\${path}> - \${method} - \${status},.. \`);
518
-
602
+
519
603
  const response = await fetch("/telemetry/list");
520
-
604
+
521
605
  if (!response.ok) {
522
- throw new Error("ERROR getting the Traces.");
606
+ throw new Error("ERROR getting the Traces.");
523
607
  }
524
-
608
+
525
609
  const responseJSON = await response.json();
526
610
  const traces = responseJSON.spans;
527
-
611
+ const filteredTraces = traces; //filter made in server side
528
612
  log(\`Feched \${traces.length} traces.\`);
529
613
  //log(\`First trace: \${JSON.stringify(traces[0],null,2)}\`);
530
- let filteredTraces = traces.filter((t)=>{
531
- return (
532
- (getPathRegEx(path).test(t.attributes.http_dot_target)) &&
533
- (t.attributes.http_dot_method.toUpperCase().includes(method.toUpperCase())) &&
534
- (t.attributes.http_dot_status_code == status)
535
- );
536
- })
537
-
538
- if(filteredTraces.length != traceCount){
614
+
615
+ if (filteredTraces.length != traceCount) {
539
616
  loadTraces(filteredTraces);
540
617
  traceCount = filteredTraces.length;
541
618
  }
542
-
543
- setTimeout(fetchTracesByParsing,1000,path,method,status);
544
-
619
+
620
+ setTimeout(fetchTracesByParsing, 1000, path, method, status);
621
+
545
622
  } catch (error) {
546
623
  console.error("ERROR getting the Traces :", error);
547
624
  }
548
625
  }
549
-
550
- function calculateTiming(startSecInput,startNanoSecInput,endSecInput,endNanoSecInput,precision = 3){
626
+
627
+ function calculateTiming(startSecInput, startNanoSecInput, endSecInput, endNanoSecInput, precision = 3) {
551
628
  // Default precision 3 = miliseconds
552
-
553
- let startSec= parseFloat(startSecInput)
554
- let startNanoSec= parseFloat(startNanoSecInput)
555
- let endSec= parseFloat(endSecInput)
556
- let endNanoSec= parseFloat(endNanoSecInput)
557
-
558
- let startNanoSecParsed = parseFloat("0."+startNanoSec);
559
- let endNanoSecParsed = parseFloat("0."+endNanoSec);
560
-
629
+
630
+ let startSec = parseFloat(startSecInput)
631
+ let startNanoSec = parseFloat(startNanoSecInput)
632
+ let endSec = parseFloat(endSecInput)
633
+ let endNanoSec = parseFloat(endNanoSecInput)
634
+
635
+ let startNanoSecParsed = parseFloat("0." + startNanoSec);
636
+ let endNanoSecParsed = parseFloat("0." + endNanoSec);
637
+
561
638
  let preciseStart = parseFloat(startSec + startNanoSecParsed);
562
639
  let preciseEnd = parseFloat(endSec + endNanoSecParsed);
563
- let preciseDuration = parseFloat(preciseEnd-preciseStart);
564
-
565
- let startDate = new Date(preciseStart.toFixed(precision)*1000);
640
+ let preciseDuration = parseFloat(preciseEnd - preciseStart);
641
+
642
+ let startDate = new Date(preciseStart.toFixed(precision) * 1000);
566
643
  let startTS = startDate.toISOString();
567
-
568
- let endDate = new Date(preciseEnd.toFixed(precision)*1000);
644
+
645
+ let endDate = new Date(preciseEnd.toFixed(precision) * 1000);
569
646
  let endTS = endDate.toISOString();
570
-
647
+
571
648
  return {
572
649
  preciseStart: preciseStart,
573
- preciseEnd : preciseEnd,
574
- preciseDuration : preciseDuration,
575
- start : parseFloat(preciseStart.toFixed(precision)),
650
+ preciseEnd: preciseEnd,
651
+ preciseDuration: preciseDuration,
652
+ start: parseFloat(preciseStart.toFixed(precision)),
576
653
  end: parseFloat(preciseEnd.toFixed(precision)),
577
- duration : parseFloat(preciseDuration.toFixed(precision)),
654
+ duration: parseFloat(preciseDuration.toFixed(precision)),
578
655
  startDate: startDate,
579
656
  endDate: endDate,
580
- startTS :startTS,
657
+ startTS: startTS,
581
658
  endTS: endTS
582
659
  };
583
-
584
- }
585
-
586
- function parseTraceInfo(t){
587
- const ep = t.attributes.http_dot_target;
588
- const method = t.attributes.http_dot_method.toLowerCase();
589
- const status = t.attributes.http_dot_status_code;
590
-
591
- const timing = calculateTiming(t.startTime[0],t.startTime[1],t.endTime[0],t.endTime[1]);
592
-
660
+
661
+ }
662
+
663
+ function parseTraceInfo(t) {
664
+ const ep = t.attributes.http.target;
665
+ const method = t.attributes.http.method.toLowerCase();
666
+ const status = t.attributes.http.status_code;
667
+
668
+ const timing = calculateTiming(t.startTime[0], t.startTime[1], t.endTime[0], t.endTime[1]);
669
+
593
670
  log(\`\${timing.startTS} - \${timing.endTS} - \${t._spanContext.traceId} - \${t.name} - \${ep} - \${status} - \${timing.duration}\`);
594
671
  return {
595
- ts : timing.startTS,
672
+ ts: timing.startTS,
596
673
  ep: ep,
597
674
  method: method,
598
675
  status: status,
599
676
  duration: timing.duration
600
677
  };
601
678
  }
602
-
679
+
603
680
  function loadTraces(traces) {
604
-
681
+
605
682
  const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
606
683
  while (tableBody.hasChildNodes()) {
607
684
  tableBody.removeChild(tableBody.lastChild);
608
685
  }
609
-
686
+
610
687
  traces.forEach(trace => {
611
688
  const row = tableBody.insertRow();
612
689
  const cellTS = row.insertCell(0);
@@ -617,26 +694,26 @@ function ui() {
617
694
  cellStatus.style.textAlign = "center";
618
695
  const cellDuration = row.insertCell(4);
619
696
  cellDuration.style.textAlign = "center";
620
-
697
+
621
698
  let t = parseTraceInfo(trace);
622
-
699
+
623
700
  cellTS.textContent = t.ts;
624
701
  cellEP.textContent = t.ep;
625
702
  cellMethod.textContent = t.method;
626
703
  cellStatus.textContent = t.status;
627
- cellDuration.textContent = t.duration.toFixed(3);
628
-
704
+ cellDuration.textContent = t.duration.toFixed(3);
705
+
629
706
  row.trace = trace;
630
- row.onclick = function() {
707
+ row.onclick = function () {
631
708
  const popup = document.getElementById("tracePopup");
632
- popup.firstChild.nodeValue = JSON.stringify(this.trace,null,2);
709
+ popup.firstChild.nodeValue = JSON.stringify(this.trace, null, 2);
633
710
  const popupOverlay = document.getElementById("popupOverlay");
634
711
  popupOverlay.style.visibility = "visible";
635
712
  popupOverlay.style.opacity = 1;
636
713
  };
637
714
  });
638
715
  }
639
-
716
+
640
717
  function sortTable(column) {
641
718
  const table = document.getElementById('apiTable');
642
719
  let rows, switching, i, x, y, shouldSwitch;
@@ -664,120 +741,152 @@ function ui() {
664
741
  }
665
742
  }
666
743
  }
667
-
668
- function hidePopup(){
744
+
745
+ function hidePopup() {
669
746
  const popupOverlay = document.getElementById("popupOverlay");
670
747
  popupOverlay.style.visibility = "hidden";
671
748
  popupOverlay.style.opacity = 0;
672
749
  }
673
-
750
+
674
751
  window.onload = parsePath();
675
- </script>
676
-
677
-
678
-
679
- <div id="popupOverlay" class="overlay">
680
- <div class="popup">
681
- <pre id="tracePopup">
682
- "attributes": {
683
- "http_dot_url": "http://localhost:3000/api/v1/test/unknown",
684
- "http_dot_host": "localhost:3000",
685
- "net_dot_host_dot_name": "localhost",
686
- "http_dot_method": "GET",
687
- "http_dot_scheme": "http",
688
- "http_dot_target": "/api/v1/test/unknown",
689
- "http_dot_user_agent": "curl/7.68.0",
690
- "http_dot_flavor": "1.1",
691
- "net_dot_transport": "ip_tcp",
692
- "net_dot_host_dot_ip": "::ffff:127.0.0.1",
693
- "net_dot_host_dot_port": 3000,
694
- "net_dot_peer_dot_ip": "::ffff:127.0.0.1",
695
- "net_dot_peer_dot_port": 37718,
696
- "http_dot_status_code": 404,
697
- "http_dot_status_text": "NOT FOUND"
698
- },
699
- "links": [],
700
- "events": [],
701
- "_droppedAttributesCount": 0,
702
- "_droppedEventsCount": 0,
703
- "_droppedLinksCount": 0,
704
- "status": {
705
- "code": 0
706
- },
707
- "endTime": [
708
- 1714196017,
709
- 860657322
710
- ],
711
- "_ended": true,
712
- "_duration": [
713
- 0,
714
- 1657322
715
- ],
716
- "name": "GET",
717
- "_spanContext": {
718
- "traceId": "7963907d7be515617050ece544ab5c9e",
719
- "spanId": "f5cec976b23725c5",
720
- "traceFlags": 1
721
- },
722
- "kind": 1,
723
- "_performanceStartTime": 211662.4864029996,
724
- "_performanceOffset": -0.596435546875,
725
- "_startTimeProvided": false,
726
- "startTime": [
727
- 1714196017,
728
- 859000000
729
- ],
730
- "resource": {
731
- "_attributes": {
732
- "service_dot_name": "unknown_service:/home/pafmon/.nvm/versions/node/v18.0.0/bin/node",
733
- "telemetry_dot_sdk_dot_language": "nodejs",
734
- "telemetry_dot_sdk_dot_name": "opentelemetry",
735
- "telemetry_dot_sdk_dot_version": "1.22.0",
736
- "process_dot_pid": 12568,
737
- "process_dot_executable_dot_name": "/home/pafmon/.nvm/versions/node/v18.0.0/bin/node",
738
- "process_dot_executable_dot_path": "/home/pafmon/.nvm/versions/node/v18.0.0/bin/node",
739
- "process_dot_command_args": [
740
- "/home/pafmon/.nvm/versions/node/v18.0.0/bin/node",
741
- "/home/pafmon/devel/github/ot-ui-poc/index.js"
742
- ],
743
- "process_dot_runtime_dot_version": "18.0.0",
744
- "process_dot_runtime_dot_name": "nodejs",
745
- "process_dot_runtime_dot_description": "Node.js",
746
- "process_dot_command": "/home/pafmon/devel/github/ot-ui-poc/index.js",
747
- "process_dot_owner": "pafmon"
748
- },
749
- "asyncAttributesPending": false,
750
- "_syncAttributes": {
751
- "service_dot_name": "unknown_service:/home/pafmon/.nvm/versions/node/v18.0.0/bin/node",
752
- "telemetry_dot_sdk_dot_language": "nodejs",
753
- "telemetry_dot_sdk_dot_name": "opentelemetry",
754
- "telemetry_dot_sdk_dot_version": "1.22.0"
755
- },
756
- "_asyncAttributesPromise": {}
757
- },
758
- "instrumentationLibrary": {
759
- "name": "@opentelemetry/instrumentation-http",
760
- "version": "0.51.0"
761
- },
762
- "_spanLimits": {
763
- "attributeValueLengthLimit": null,
764
- "attributeCountLimit": 128,
765
- "linkCountLimit": 128,
766
- "eventCountLimit": 128,
767
- "attributePerEventCountLimit": 128,
768
- "attributePerLinkCountLimit": 128
769
- },
770
- "_attributeValueLengthLimit": null,
771
- "_spanProcessor": "oas-telemetry skips this field to avoid circular reference",
772
- "_id": "3qYjJV2KdMa6zJw7"
773
- </pre>
774
- <a class="close" href="#" onclick="hidePopup()">&times;</a>
775
- </div>
752
+ </script>
753
+
754
+
755
+
756
+ <div id="popupOverlay" class="overlay">
757
+ <div class="popup">
758
+ <pre id="tracePopup">
759
+ "attributes": {
760
+ "http": {
761
+ "url": "http://localhost:3000/api/v1/test",
762
+ "host": "localhost:3000",
763
+ "method": "GET",
764
+ "scheme": "http",
765
+ "target": "/api/v1/test",
766
+ "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
767
+ "flavor": "1.1",
768
+ "status_code": 304,
769
+ "status_text": "NOT MODIFIED"
770
+ },
771
+ "net": {
772
+ "host": {
773
+ "name": "localhost",
774
+ "ip": "::1",
775
+ "port": 3000
776
+ },
777
+ "transport": "ip_tcp",
778
+ "peer": {
779
+ "ip": "::1",
780
+ "port": 58101
781
+ }
782
+ }
783
+ },
784
+ "links": {
785
+
786
+ },
787
+ "events": {
788
+
789
+ },
790
+ "_droppedAttributesCount": 0,
791
+ "_droppedEventsCount": 0,
792
+ "_droppedLinksCount": 0,
793
+ "status": {
794
+ "code": 0
795
+ },
796
+ "endTime": {
797
+ "0": 1724019619,
798
+ "1": 414126300
799
+ },
800
+ "_ended": true,
801
+ "_duration": {
802
+ "0": 0,
803
+ "1": 2126300
804
+ },
805
+ "name": "GET",
806
+ "_spanContext": {
807
+ "traceId": "ee70c9a937bbf95940a8971dc96077b3",
808
+ "spanId": "4fe34ee075253ecb",
809
+ "traceFlags": 1
810
+ },
811
+ "kind": 1,
812
+ "_performanceStartTime": 40561.097599983215,
813
+ "_performanceOffset": -8.425537109375,
814
+ "_startTimeProvided": false,
815
+ "startTime": {
816
+ "0": 1724019619,
817
+ "1": 412000000
818
+ },
819
+ "resource": {
820
+ "_attributes": {
821
+ "service": {
822
+ "name": "unknown_service:node"
823
+ },
824
+ "telemetry": {
825
+ "sdk": {
826
+ "language": "nodejs",
827
+ "name": "opentelemetry",
828
+ "version": "1.22.0"
829
+ }
830
+ },
831
+ "process": {
832
+ "pid": 23128,
833
+ "executable": {
834
+ "name": "npm",
835
+ "path": "C:\\Program Files\\nodejs\\node.exe"
836
+ },
837
+ "command_args": {
838
+ "0": "C:\\Program Files\\nodejs\\node.exe",
839
+ "1": "C:\\Personal\\ISA\\telemetry\\ot-ui-poc\\index"
840
+ },
841
+ "runtime": {
842
+ "version": "14.21.3",
843
+ "name": "nodejs",
844
+ "description": "Node.js"
845
+ },
846
+ "command": "C:\\Personal\\ISA\\telemetry\\ot-ui-poc\\index",
847
+ "owner": "manol"
848
+ }
849
+ },
850
+ "asyncAttributesPending": false,
851
+ "_syncAttributes": {
852
+ "service": {
853
+ "name": "unknown_service:node"
854
+ },
855
+ "telemetry": {
856
+ "sdk": {
857
+ "language": "nodejs",
858
+ "name": "opentelemetry",
859
+ "version": "1.22.0"
860
+ }
861
+ }
862
+ },
863
+ "_asyncAttributesPromise": {
864
+
865
+ }
866
+ },
867
+ "instrumentationLibrary": {
868
+ "name": "@opentelemetry/instrumentation-http",
869
+ "version": "0.51.0"
870
+ },
871
+ "_spanLimits": {
872
+ "attributeValueLengthLimit": null,
873
+ "attributeCountLimit": 128,
874
+ "linkCountLimit": 128,
875
+ "eventCountLimit": 128,
876
+ "attributePerEventCountLimit": 128,
877
+ "attributePerLinkCountLimit": 128
878
+ },
879
+ "_attributeValueLengthLimit": null,
880
+ "_spanProcessor": "oas-telemetry skips this field to avoid circular reference",
881
+ "_id": "f2989F2IDm3uSfml"
882
+ </pre>
883
+ <a class="close" href="#" onclick="hidePopup()">&times;</a>
776
884
  </div>
777
-
778
- </body>
779
- </html>
780
- `
885
+ </div>
886
+
887
+ </body>
888
+
889
+ </html>`
781
890
  };
782
891
  }
783
892
  module.exports = exports.default;