@oneuptime/common 10.0.10 → 10.0.14
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/Models/AnalyticsModels/ExceptionInstance.ts +98 -0
- package/Models/DatabaseModels/TelemetryException.ts +105 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1772111896988-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Utils/Browser.ts +40 -2
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +3 -15
- package/Server/Utils/Telemetry.ts +7 -2
- package/Server/Utils/VM/VMAPI.ts +204 -0
- package/Tests/Server/Utils/VM/VMAPI.test.ts +310 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +87 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +108 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1772111896988-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1772111896988-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Utils/Browser.js +40 -2
- package/build/dist/Server/Utils/Browser.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +3 -9
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +4 -2
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMAPI.js +153 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Tests/Server/Utils/VM/VMAPI.test.js +214 -0
- package/build/dist/Tests/Server/Utils/VM/VMAPI.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -311,6 +311,77 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
|
|
|
311
311
|
},
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
+
const releaseColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
315
|
+
key: "release",
|
|
316
|
+
title: "Release",
|
|
317
|
+
description:
|
|
318
|
+
"Service version / release from service.version resource attribute",
|
|
319
|
+
required: false,
|
|
320
|
+
type: TableColumnType.Text,
|
|
321
|
+
accessControl: {
|
|
322
|
+
read: [
|
|
323
|
+
Permission.ProjectOwner,
|
|
324
|
+
Permission.ProjectAdmin,
|
|
325
|
+
Permission.ProjectMember,
|
|
326
|
+
Permission.ReadTelemetryException,
|
|
327
|
+
],
|
|
328
|
+
create: [
|
|
329
|
+
Permission.ProjectOwner,
|
|
330
|
+
Permission.ProjectAdmin,
|
|
331
|
+
Permission.ProjectMember,
|
|
332
|
+
Permission.CreateTelemetryException,
|
|
333
|
+
],
|
|
334
|
+
update: [],
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const environmentColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
339
|
+
key: "environment",
|
|
340
|
+
title: "Environment",
|
|
341
|
+
description:
|
|
342
|
+
"Deployment environment from deployment.environment resource attribute",
|
|
343
|
+
required: false,
|
|
344
|
+
type: TableColumnType.Text,
|
|
345
|
+
accessControl: {
|
|
346
|
+
read: [
|
|
347
|
+
Permission.ProjectOwner,
|
|
348
|
+
Permission.ProjectAdmin,
|
|
349
|
+
Permission.ProjectMember,
|
|
350
|
+
Permission.ReadTelemetryException,
|
|
351
|
+
],
|
|
352
|
+
create: [
|
|
353
|
+
Permission.ProjectOwner,
|
|
354
|
+
Permission.ProjectAdmin,
|
|
355
|
+
Permission.ProjectMember,
|
|
356
|
+
Permission.CreateTelemetryException,
|
|
357
|
+
],
|
|
358
|
+
update: [],
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const parsedFramesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
363
|
+
key: "parsedFrames",
|
|
364
|
+
title: "Parsed Stack Frames",
|
|
365
|
+
description: "Stack trace parsed into structured frames (JSON array)",
|
|
366
|
+
required: false,
|
|
367
|
+
type: TableColumnType.Text,
|
|
368
|
+
accessControl: {
|
|
369
|
+
read: [
|
|
370
|
+
Permission.ProjectOwner,
|
|
371
|
+
Permission.ProjectAdmin,
|
|
372
|
+
Permission.ProjectMember,
|
|
373
|
+
Permission.ReadTelemetryException,
|
|
374
|
+
],
|
|
375
|
+
create: [
|
|
376
|
+
Permission.ProjectOwner,
|
|
377
|
+
Permission.ProjectAdmin,
|
|
378
|
+
Permission.ProjectMember,
|
|
379
|
+
Permission.CreateTelemetryException,
|
|
380
|
+
],
|
|
381
|
+
update: [],
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
314
385
|
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
315
386
|
key: "attributes",
|
|
316
387
|
title: "Attributes",
|
|
@@ -384,6 +455,9 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
|
|
|
384
455
|
spanIdColumn,
|
|
385
456
|
fingerprintColumn,
|
|
386
457
|
spanNameColumn,
|
|
458
|
+
releaseColumn,
|
|
459
|
+
environmentColumn,
|
|
460
|
+
parsedFramesColumn,
|
|
387
461
|
attributesColumn,
|
|
388
462
|
],
|
|
389
463
|
projections: [],
|
|
@@ -504,4 +578,28 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
|
|
|
504
578
|
public set spanName(v: string | undefined) {
|
|
505
579
|
this.setColumnValue("spanName", v);
|
|
506
580
|
}
|
|
581
|
+
|
|
582
|
+
public get release(): string | undefined {
|
|
583
|
+
return this.getColumnValue("release") as string | undefined;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
public set release(v: string | undefined) {
|
|
587
|
+
this.setColumnValue("release", v);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
public get environment(): string | undefined {
|
|
591
|
+
return this.getColumnValue("environment") as string | undefined;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
public set environment(v: string | undefined) {
|
|
595
|
+
this.setColumnValue("environment", v);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
public get parsedFrames(): string | undefined {
|
|
599
|
+
return this.getColumnValue("parsedFrames") as string | undefined;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
public set parsedFrames(v: string | undefined) {
|
|
603
|
+
this.setColumnValue("parsedFrames", v);
|
|
604
|
+
}
|
|
507
605
|
}
|
|
@@ -1026,4 +1026,109 @@ export default class TelemetryException extends DatabaseBaseModel {
|
|
|
1026
1026
|
default: 1,
|
|
1027
1027
|
})
|
|
1028
1028
|
public occuranceCount?: number = undefined;
|
|
1029
|
+
|
|
1030
|
+
@ColumnAccessControl({
|
|
1031
|
+
create: [
|
|
1032
|
+
Permission.ProjectOwner,
|
|
1033
|
+
Permission.ProjectAdmin,
|
|
1034
|
+
Permission.CreateTelemetryException,
|
|
1035
|
+
],
|
|
1036
|
+
read: [
|
|
1037
|
+
Permission.ProjectOwner,
|
|
1038
|
+
Permission.ProjectAdmin,
|
|
1039
|
+
Permission.ProjectMember,
|
|
1040
|
+
Permission.ReadTelemetryException,
|
|
1041
|
+
Permission.ReadAllProjectResources,
|
|
1042
|
+
],
|
|
1043
|
+
update: [
|
|
1044
|
+
Permission.ProjectOwner,
|
|
1045
|
+
Permission.ProjectAdmin,
|
|
1046
|
+
Permission.EditTelemetryException,
|
|
1047
|
+
],
|
|
1048
|
+
})
|
|
1049
|
+
@TableColumn({
|
|
1050
|
+
required: false,
|
|
1051
|
+
type: TableColumnType.LongText,
|
|
1052
|
+
canReadOnRelationQuery: true,
|
|
1053
|
+
title: "First Seen In Release",
|
|
1054
|
+
description:
|
|
1055
|
+
"The service version / release in which this exception was first observed",
|
|
1056
|
+
example: "v1.2.3",
|
|
1057
|
+
})
|
|
1058
|
+
@Column({
|
|
1059
|
+
nullable: true,
|
|
1060
|
+
type: ColumnType.LongText,
|
|
1061
|
+
length: ColumnLength.LongText,
|
|
1062
|
+
})
|
|
1063
|
+
public firstSeenInRelease?: string = undefined;
|
|
1064
|
+
|
|
1065
|
+
@ColumnAccessControl({
|
|
1066
|
+
create: [
|
|
1067
|
+
Permission.ProjectOwner,
|
|
1068
|
+
Permission.ProjectAdmin,
|
|
1069
|
+
Permission.CreateTelemetryException,
|
|
1070
|
+
],
|
|
1071
|
+
read: [
|
|
1072
|
+
Permission.ProjectOwner,
|
|
1073
|
+
Permission.ProjectAdmin,
|
|
1074
|
+
Permission.ProjectMember,
|
|
1075
|
+
Permission.ReadTelemetryException,
|
|
1076
|
+
Permission.ReadAllProjectResources,
|
|
1077
|
+
],
|
|
1078
|
+
update: [
|
|
1079
|
+
Permission.ProjectOwner,
|
|
1080
|
+
Permission.ProjectAdmin,
|
|
1081
|
+
Permission.EditTelemetryException,
|
|
1082
|
+
],
|
|
1083
|
+
})
|
|
1084
|
+
@TableColumn({
|
|
1085
|
+
required: false,
|
|
1086
|
+
type: TableColumnType.LongText,
|
|
1087
|
+
canReadOnRelationQuery: true,
|
|
1088
|
+
title: "Last Seen In Release",
|
|
1089
|
+
description:
|
|
1090
|
+
"The most recent service version / release in which this exception was observed",
|
|
1091
|
+
example: "v1.4.0",
|
|
1092
|
+
})
|
|
1093
|
+
@Column({
|
|
1094
|
+
nullable: true,
|
|
1095
|
+
type: ColumnType.LongText,
|
|
1096
|
+
length: ColumnLength.LongText,
|
|
1097
|
+
})
|
|
1098
|
+
public lastSeenInRelease?: string = undefined;
|
|
1099
|
+
|
|
1100
|
+
@ColumnAccessControl({
|
|
1101
|
+
create: [
|
|
1102
|
+
Permission.ProjectOwner,
|
|
1103
|
+
Permission.ProjectAdmin,
|
|
1104
|
+
Permission.CreateTelemetryException,
|
|
1105
|
+
],
|
|
1106
|
+
read: [
|
|
1107
|
+
Permission.ProjectOwner,
|
|
1108
|
+
Permission.ProjectAdmin,
|
|
1109
|
+
Permission.ProjectMember,
|
|
1110
|
+
Permission.ReadTelemetryException,
|
|
1111
|
+
Permission.ReadAllProjectResources,
|
|
1112
|
+
],
|
|
1113
|
+
update: [
|
|
1114
|
+
Permission.ProjectOwner,
|
|
1115
|
+
Permission.ProjectAdmin,
|
|
1116
|
+
Permission.EditTelemetryException,
|
|
1117
|
+
],
|
|
1118
|
+
})
|
|
1119
|
+
@TableColumn({
|
|
1120
|
+
required: false,
|
|
1121
|
+
type: TableColumnType.LongText,
|
|
1122
|
+
canReadOnRelationQuery: true,
|
|
1123
|
+
title: "Environment",
|
|
1124
|
+
description:
|
|
1125
|
+
"Deployment environment from deployment.environment resource attribute",
|
|
1126
|
+
example: "production",
|
|
1127
|
+
})
|
|
1128
|
+
@Column({
|
|
1129
|
+
nullable: true,
|
|
1130
|
+
type: ColumnType.LongText,
|
|
1131
|
+
length: ColumnLength.LongText,
|
|
1132
|
+
})
|
|
1133
|
+
public environment?: string = undefined;
|
|
1029
1134
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1772111896988 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1772111896988";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "TelemetryException" ADD "firstSeenInRelease" character varying(500)`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "TelemetryException" ADD "lastSeenInRelease" character varying(500)`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
|
15
|
+
);
|
|
16
|
+
await queryRunner.query(
|
|
17
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
|
18
|
+
);
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`ALTER TABLE "TelemetryException" ADD "environment" character varying(500)`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "TelemetryException" DROP COLUMN "environment"`,
|
|
27
|
+
);
|
|
28
|
+
await queryRunner.query(
|
|
29
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
|
30
|
+
);
|
|
31
|
+
await queryRunner.query(
|
|
32
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
|
33
|
+
);
|
|
34
|
+
await queryRunner.query(
|
|
35
|
+
`ALTER TABLE "TelemetryException" DROP COLUMN "lastSeenInRelease"`,
|
|
36
|
+
);
|
|
37
|
+
await queryRunner.query(
|
|
38
|
+
`ALTER TABLE "TelemetryException" DROP COLUMN "firstSeenInRelease"`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -259,6 +259,7 @@ import { MigrationName1770732721195 } from "./1770732721195-MigrationName";
|
|
|
259
259
|
import { MigrationName1770833704656 } from "./1770833704656-MigrationName";
|
|
260
260
|
import { MigrationName1770834237090 } from "./1770834237090-MigrationName";
|
|
261
261
|
import { MigrationName1770834237091 } from "./1770834237091-MigrationName";
|
|
262
|
+
import { MigrationName1772111896988 } from "./1772111896988-MigrationName";
|
|
262
263
|
|
|
263
264
|
export default [
|
|
264
265
|
InitialMigration,
|
|
@@ -522,4 +523,5 @@ export default [
|
|
|
522
523
|
MigrationName1770833704656,
|
|
523
524
|
MigrationName1770834237090,
|
|
524
525
|
MigrationName1770834237091,
|
|
526
|
+
MigrationName1772111896988,
|
|
525
527
|
];
|
package/Server/Utils/Browser.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type Page = PlaywrightPage;
|
|
|
16
16
|
export type Browser = PlaywrightBrowser;
|
|
17
17
|
|
|
18
18
|
export default class BrowserUtil {
|
|
19
|
-
// Chromium arguments for stability in containerized environments
|
|
19
|
+
// Chromium arguments for stability and memory optimization in containerized environments
|
|
20
20
|
public static chromiumStabilityArgs: string[] = [
|
|
21
21
|
"--no-sandbox",
|
|
22
22
|
"--disable-setuid-sandbox",
|
|
@@ -26,9 +26,32 @@ export default class BrowserUtil {
|
|
|
26
26
|
"--disable-dbus", // no D-Bus daemon in containers
|
|
27
27
|
"--disable-features=dbus", // additional D-Bus feature gate
|
|
28
28
|
"--no-zygote", // skip zygote process that fails OOM score adjustments in containers
|
|
29
|
+
// Memory optimization flags
|
|
30
|
+
"--single-process", // run browser in single process to reduce memory overhead
|
|
31
|
+
"--disable-extensions", // no extensions needed for monitoring
|
|
32
|
+
"--disable-background-networking", // disable background network requests
|
|
33
|
+
"--disable-default-apps", // don't load default apps
|
|
34
|
+
"--disable-sync", // no sync needed
|
|
35
|
+
"--disable-translate", // no translation needed
|
|
36
|
+
"--disable-backgrounding-occluded-windows", // don't throttle hidden windows
|
|
37
|
+
"--disable-renderer-backgrounding", // don't background renderers
|
|
38
|
+
"--disable-background-timer-throttling", // don't throttle timers
|
|
39
|
+
"--disable-ipc-flooding-protection", // allow high IPC throughput
|
|
40
|
+
"--memory-pressure-off", // disable memory pressure signals that cause GC
|
|
41
|
+
"--js-flags=--max-old-space-size=256", // limit V8 heap to 256MB
|
|
42
|
+
"--disable-features=TranslateUI,BlinkGenPropertyTrees", // disable unused features
|
|
43
|
+
"--disable-component-update", // don't update components
|
|
44
|
+
"--disable-domain-reliability", // no domain reliability monitoring
|
|
45
|
+
"--disable-client-side-phishing-detection", // no phishing detection
|
|
46
|
+
"--no-first-run", // skip first run experience
|
|
47
|
+
"--disable-hang-monitor", // no hang monitor
|
|
48
|
+
"--disable-popup-blocking", // allow popups for testing
|
|
49
|
+
"--disable-prompt-on-repost", // no repost prompts
|
|
50
|
+
"--metrics-recording-only", // disable metrics uploading
|
|
51
|
+
"--safebrowsing-disable-auto-update", // no safe browsing updates
|
|
29
52
|
];
|
|
30
53
|
|
|
31
|
-
// Firefox preferences for stability in containerized environments
|
|
54
|
+
// Firefox preferences for stability and memory optimization in containerized environments
|
|
32
55
|
public static firefoxStabilityPrefs: Record<
|
|
33
56
|
string,
|
|
34
57
|
string | number | boolean
|
|
@@ -37,6 +60,21 @@ export default class BrowserUtil {
|
|
|
37
60
|
"media.hardware-video-decoding.enabled": false, // disable hardware video decoding
|
|
38
61
|
"layers.acceleration.disabled": true, // disable GPU-accelerated layers
|
|
39
62
|
"network.http.spdy.enabled.http2": true, // keep HTTP/2 enabled
|
|
63
|
+
// Memory optimization preferences
|
|
64
|
+
"javascript.options.mem.max": 256 * 1024, // limit JS memory to 256MB in KB
|
|
65
|
+
"javascript.options.mem.high_water_mark": 128, // GC high water mark in MB
|
|
66
|
+
"browser.cache.memory.capacity": 16384, // limit memory cache to 16MB
|
|
67
|
+
"browser.cache.disk.enable": false, // disable disk cache
|
|
68
|
+
"browser.sessionhistory.max_entries": 3, // limit session history
|
|
69
|
+
"browser.sessionhistory.max_total_viewers": 0, // don't keep pages in bfcache
|
|
70
|
+
"dom.ipc.processCount": 1, // single content process
|
|
71
|
+
"extensions.update.enabled": false, // no extension updates
|
|
72
|
+
"network.prefetch-next": false, // no prefetching
|
|
73
|
+
"network.dns.disablePrefetch": true, // no DNS prefetch
|
|
74
|
+
"network.http.speculative-parallel-limit": 0, // no speculative connections
|
|
75
|
+
"browser.tabs.remote.autostart": false, // disable multi-process
|
|
76
|
+
"media.peerconnection.enabled": false, // disable WebRTC
|
|
77
|
+
"media.navigator.enabled": false, // disable getUserMedia
|
|
40
78
|
};
|
|
41
79
|
|
|
42
80
|
@CaptureSpan()
|
|
@@ -159,11 +159,7 @@ export default class IncomingRequestCriteria {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
if (
|
|
163
|
-
input.criteriaFilter.checkOn === CheckOn.RequestBody &&
|
|
164
|
-
!(input.dataToProcess as IncomingMonitorRequest)
|
|
165
|
-
.onlyCheckForIncomingRequestReceivedAt
|
|
166
|
-
) {
|
|
162
|
+
if (input.criteriaFilter.checkOn === CheckOn.RequestBody) {
|
|
167
163
|
let responseBody: string | JSONObject | undefined = (
|
|
168
164
|
input.dataToProcess as IncomingMonitorRequest
|
|
169
165
|
).requestBody;
|
|
@@ -200,11 +196,7 @@ export default class IncomingRequestCriteria {
|
|
|
200
196
|
}
|
|
201
197
|
}
|
|
202
198
|
|
|
203
|
-
if (
|
|
204
|
-
input.criteriaFilter.checkOn === CheckOn.RequestHeader &&
|
|
205
|
-
!(input.dataToProcess as IncomingMonitorRequest)
|
|
206
|
-
.onlyCheckForIncomingRequestReceivedAt
|
|
207
|
-
) {
|
|
199
|
+
if (input.criteriaFilter.checkOn === CheckOn.RequestHeader) {
|
|
208
200
|
const headerKeys: Array<string> = Object.keys(
|
|
209
201
|
(input.dataToProcess as IncomingMonitorRequest).requestHeaders || {},
|
|
210
202
|
).map((key: string) => {
|
|
@@ -227,11 +219,7 @@ export default class IncomingRequestCriteria {
|
|
|
227
219
|
}
|
|
228
220
|
}
|
|
229
221
|
|
|
230
|
-
if (
|
|
231
|
-
input.criteriaFilter.checkOn === CheckOn.RequestHeaderValue &&
|
|
232
|
-
!(input.dataToProcess as IncomingMonitorRequest)
|
|
233
|
-
.onlyCheckForIncomingRequestReceivedAt
|
|
234
|
-
) {
|
|
222
|
+
if (input.criteriaFilter.checkOn === CheckOn.RequestHeaderValue) {
|
|
235
223
|
const headerValues: Array<string> = Object.values(
|
|
236
224
|
(input.dataToProcess as IncomingMonitorRequest).requestHeaders || {},
|
|
237
225
|
).map((key: string) => {
|
|
@@ -34,10 +34,13 @@ import {
|
|
|
34
34
|
import type { PushMetricExporter } from "@opentelemetry/sdk-metrics/build/src/export/MetricExporter";
|
|
35
35
|
import * as opentelemetry from "@opentelemetry/sdk-node";
|
|
36
36
|
import { SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
ATTR_SERVICE_NAME,
|
|
39
|
+
ATTR_SERVICE_VERSION,
|
|
40
|
+
} from "@opentelemetry/semantic-conventions";
|
|
38
41
|
import URL from "../../Types/API/URL";
|
|
39
42
|
import Dictionary from "../../Types/Dictionary";
|
|
40
|
-
import { DisableTelemetry } from "../EnvironmentConfig";
|
|
43
|
+
import { AppVersion, Env, DisableTelemetry } from "../EnvironmentConfig";
|
|
41
44
|
import logger from "./Logger";
|
|
42
45
|
|
|
43
46
|
type ResourceWithRawAttributes = LogsResource & {
|
|
@@ -141,6 +144,8 @@ export default class Telemetry {
|
|
|
141
144
|
public static getResource(data: { serviceName: string }): Resource {
|
|
142
145
|
return new Resource({
|
|
143
146
|
[ATTR_SERVICE_NAME]: data.serviceName,
|
|
147
|
+
[ATTR_SERVICE_VERSION]: AppVersion,
|
|
148
|
+
["deployment.environment"]: Env,
|
|
144
149
|
});
|
|
145
150
|
}
|
|
146
151
|
|
package/Server/Utils/VM/VMAPI.ts
CHANGED
|
@@ -69,6 +69,14 @@ export default class VMUtil {
|
|
|
69
69
|
valueToReplaceInPlace.toString().includes("}}")
|
|
70
70
|
) {
|
|
71
71
|
let valueToReplaceInPlaceCopy: string = valueToReplaceInPlace.toString();
|
|
72
|
+
|
|
73
|
+
// First, expand {{#each path}}...{{/each}} loops before variable substitution
|
|
74
|
+
valueToReplaceInPlaceCopy = VMUtil.expandEachLoops(
|
|
75
|
+
storageMap,
|
|
76
|
+
valueToReplaceInPlaceCopy,
|
|
77
|
+
isJSON,
|
|
78
|
+
);
|
|
79
|
+
|
|
72
80
|
const variablesInArgument: Array<string> = [];
|
|
73
81
|
|
|
74
82
|
const regex: RegExp = /{{(.*?)}}/g; // Find all matches of the regular expression and capture the word between the braces {{x}} => x
|
|
@@ -128,6 +136,202 @@ export default class VMUtil {
|
|
|
128
136
|
return valueToReplaceInPlace;
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Expand {{#each path}}...{{/each}} loop blocks by iterating over arrays.
|
|
141
|
+
*
|
|
142
|
+
* Supports:
|
|
143
|
+
* - {{variableName}} inside the loop body resolves relative to the current array element
|
|
144
|
+
* - {{@index}} resolves to the 0-based index of the current iteration
|
|
145
|
+
* - {{this}} resolves to the current element value (useful for primitive arrays)
|
|
146
|
+
* - Nested {{#each}} blocks for multi-level array traversal
|
|
147
|
+
* - If the resolved path is not an array, the block is removed (replaced with empty string)
|
|
148
|
+
*
|
|
149
|
+
* Example:
|
|
150
|
+
* {{#each requestBody.alerts}}
|
|
151
|
+
* Alert {{@index}}: {{labels.label}} - {{status}}
|
|
152
|
+
* {{/each}}
|
|
153
|
+
*/
|
|
154
|
+
@CaptureSpan()
|
|
155
|
+
public static expandEachLoops(
|
|
156
|
+
storageMap: JSONObject,
|
|
157
|
+
template: string,
|
|
158
|
+
isJSON: boolean | undefined,
|
|
159
|
+
): string {
|
|
160
|
+
let result: string = template;
|
|
161
|
+
const maxIterations: number = 100; // safety limit to prevent infinite loops
|
|
162
|
+
let iterations: number = 0;
|
|
163
|
+
|
|
164
|
+
while (iterations < maxIterations) {
|
|
165
|
+
iterations++;
|
|
166
|
+
|
|
167
|
+
// Find the first (outermost) {{#each ...}} tag
|
|
168
|
+
const openTag: RegExp = /\{\{#each\s+(.*?)\}\}/;
|
|
169
|
+
const openMatch: RegExpExecArray | null = openTag.exec(result);
|
|
170
|
+
|
|
171
|
+
if (!openMatch) {
|
|
172
|
+
break; // no more {{#each}} blocks
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const blockStart: number = openMatch.index!;
|
|
176
|
+
const arrayPath: string = openMatch[1]!.trim();
|
|
177
|
+
const bodyStart: number = blockStart + openMatch[0]!.length;
|
|
178
|
+
|
|
179
|
+
// Find the matching {{/each}} by counting nesting depth
|
|
180
|
+
let depth: number = 1;
|
|
181
|
+
let searchPos: number = bodyStart;
|
|
182
|
+
let matchEnd: number = -1;
|
|
183
|
+
let bodyEnd: number = -1;
|
|
184
|
+
|
|
185
|
+
while (depth > 0 && searchPos < result.length) {
|
|
186
|
+
const nextOpen: number = result.indexOf("{{#each ", searchPos);
|
|
187
|
+
const nextClose: number = result.indexOf("{{/each}}", searchPos);
|
|
188
|
+
|
|
189
|
+
if (nextClose === -1) {
|
|
190
|
+
// Unmatched {{#each}} — break out to avoid infinite loop
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
195
|
+
// Found a nested {{#each}} before the next {{/each}}
|
|
196
|
+
depth++;
|
|
197
|
+
searchPos = nextOpen + 8; // skip past "{{#each "
|
|
198
|
+
} else {
|
|
199
|
+
// Found {{/each}}
|
|
200
|
+
depth--;
|
|
201
|
+
if (depth === 0) {
|
|
202
|
+
bodyEnd = nextClose;
|
|
203
|
+
matchEnd = nextClose + "{{/each}}".length;
|
|
204
|
+
}
|
|
205
|
+
searchPos = nextClose + "{{/each}}".length;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (matchEnd === -1 || bodyEnd === -1) {
|
|
210
|
+
// Unmatched {{#each}} — remove it to prevent infinite loop
|
|
211
|
+
result =
|
|
212
|
+
result.slice(0, blockStart) +
|
|
213
|
+
result.slice(blockStart + openMatch[0]!.length);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const loopBody: string = result.slice(bodyStart, bodyEnd);
|
|
218
|
+
|
|
219
|
+
// Resolve the array from the storage map
|
|
220
|
+
const arrayValue: JSONValue = VMUtil.deepFind(storageMap, arrayPath);
|
|
221
|
+
|
|
222
|
+
if (!Array.isArray(arrayValue)) {
|
|
223
|
+
// Not an array — remove the block entirely
|
|
224
|
+
result = result.slice(0, blockStart) + result.slice(matchEnd);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Expand the loop body for each element in the array
|
|
229
|
+
const expandedParts: Array<string> = [];
|
|
230
|
+
|
|
231
|
+
for (let i: number = 0; i < arrayValue.length; i++) {
|
|
232
|
+
const element: JSONValue = arrayValue[i]!;
|
|
233
|
+
let iterationBody: string = loopBody;
|
|
234
|
+
|
|
235
|
+
// Replace {{@index}} with the current index
|
|
236
|
+
iterationBody = iterationBody.replace(/\{\{@index\}\}/g, i.toString());
|
|
237
|
+
|
|
238
|
+
if (typeof element === "object" && element !== null) {
|
|
239
|
+
/*
|
|
240
|
+
* Merge element properties into a scoped storageMap so that:
|
|
241
|
+
* 1. Element properties can be accessed directly (e.g., {{status}})
|
|
242
|
+
* 2. Parent storageMap properties are still accessible (e.g., {{requestBody.receiver}})
|
|
243
|
+
*/
|
|
244
|
+
const scopedStorageMap: JSONObject = {
|
|
245
|
+
...storageMap,
|
|
246
|
+
...(element as JSONObject),
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Recursively expand any nested {{#each}} blocks within the iteration body
|
|
250
|
+
iterationBody = VMUtil.expandEachLoops(
|
|
251
|
+
scopedStorageMap,
|
|
252
|
+
iterationBody,
|
|
253
|
+
isJSON,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Replace remaining {{variable}} placeholders
|
|
257
|
+
iterationBody = VMUtil.replaceLoopVariables(
|
|
258
|
+
element as JSONObject,
|
|
259
|
+
storageMap,
|
|
260
|
+
iterationBody,
|
|
261
|
+
isJSON,
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
// For primitive array elements, replace {{this}} with the value
|
|
265
|
+
iterationBody = iterationBody.replace(
|
|
266
|
+
/\{\{this\}\}/g,
|
|
267
|
+
isJSON ? VMUtil.serializeValueForJSON(`${element}`) : `${element}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
expandedParts.push(iterationBody);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
result =
|
|
275
|
+
result.slice(0, blockStart) +
|
|
276
|
+
expandedParts.join("") +
|
|
277
|
+
result.slice(matchEnd);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Replace {{variable}} placeholders inside a loop body.
|
|
285
|
+
* Variables are resolved first against the current element (scoped),
|
|
286
|
+
* then fall back to the parent storageMap.
|
|
287
|
+
*/
|
|
288
|
+
@CaptureSpan()
|
|
289
|
+
private static replaceLoopVariables(
|
|
290
|
+
element: JSONObject,
|
|
291
|
+
parentStorageMap: JSONObject,
|
|
292
|
+
body: string,
|
|
293
|
+
isJSON: boolean | undefined,
|
|
294
|
+
): string {
|
|
295
|
+
const variableRegex: RegExp = /\{\{((?!#each\b|\/each\b|@index\b).*?)\}\}/g;
|
|
296
|
+
let match: RegExpExecArray | null = null;
|
|
297
|
+
const variables: Array<string> = [];
|
|
298
|
+
|
|
299
|
+
while ((match = variableRegex.exec(body)) !== null) {
|
|
300
|
+
if (match[1]) {
|
|
301
|
+
variables.push(match[1]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const variable of variables) {
|
|
306
|
+
// First try resolving relative to the current element
|
|
307
|
+
let foundValue: JSONValue = VMUtil.deepFind(element, variable.trim());
|
|
308
|
+
|
|
309
|
+
// Fall back to the parent storage map (for absolute paths)
|
|
310
|
+
if (foundValue === undefined) {
|
|
311
|
+
foundValue = VMUtil.deepFind(parentStorageMap, variable.trim());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (foundValue === undefined) {
|
|
315
|
+
continue; // leave unresolved
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let replacement: string;
|
|
319
|
+
|
|
320
|
+
if (typeof foundValue === "object" && foundValue !== null) {
|
|
321
|
+
replacement = JSON.stringify(foundValue, null, 2);
|
|
322
|
+
} else {
|
|
323
|
+
replacement = `${foundValue}`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
body = body.replace(
|
|
327
|
+
"{{" + variable + "}}",
|
|
328
|
+
isJSON ? VMUtil.serializeValueForJSON(replacement) : replacement,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return body;
|
|
333
|
+
}
|
|
334
|
+
|
|
131
335
|
@CaptureSpan()
|
|
132
336
|
public static serializeValueForJSON(value: string): string {
|
|
133
337
|
if (!value) {
|