@oneuptime/common 10.0.54 → 10.0.56
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/DatabaseModels/DockerHost.ts +662 -0
- package/Models/DatabaseModels/GlobalConfig.ts +112 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Server/API/TelemetryAPI.ts +352 -16
- package/Server/Infrastructure/ClickhouseConfig.ts +9 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.ts +76 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.ts +133 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/DockerHostService.ts +173 -0
- package/Server/Services/ExceptionAggregationService.ts +335 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LogAggregationService.ts +17 -0
- package/Server/Services/MonitorProbeService.ts +42 -21
- package/Server/Services/MonitorService.ts +21 -21
- package/Server/Services/TraceAggregationService.ts +514 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +73 -1
- package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
- package/Tests/__mocks__/mermaid.js +18 -0
- package/Tests/__mocks__/react-markdown.js +17 -0
- package/Tests/__mocks__/react-syntax-highlighter.js +19 -0
- package/Tests/__mocks__/remark-gfm.js +8 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Monitor/DockerAlertTemplates.ts +507 -0
- package/Types/Monitor/DockerMetricCatalog.ts +226 -0
- package/Types/Monitor/MonitorStep.ts +33 -0
- package/Types/Monitor/MonitorStepDockerMonitor.ts +38 -0
- package/Types/Monitor/MonitorType.ts +15 -1
- package/Types/Permission.ts +38 -0
- package/UI/Components/Icon/Icon.tsx +87 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +7 -132
- package/UI/Components/ModelDetail/CardModelDetail.tsx +11 -1
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +285 -0
- package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +85 -0
- package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +156 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +160 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +85 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +102 -0
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +280 -0
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +125 -0
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +114 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +378 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.tsx +78 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +64 -0
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +193 -0
- package/UI/Components/TelemetryViewer/types.ts +67 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js +686 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +117 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +237 -16
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js +52 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +162 -0
- package/build/dist/Server/Services/DockerHostService.js.map +1 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js +224 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -0
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +11 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +28 -14
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +19 -17
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +364 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +46 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js +2 -2
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/DockerAlertTemplates.js +410 -0
- package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/DockerMetricCatalog.js +192 -0
- package/build/dist/Types/Monitor/DockerMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStep.js +23 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js +21 -0
- package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +14 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Permission.js +36 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +13 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +7 -75
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +8 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +71 -0
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +39 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +61 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +66 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +41 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +35 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +132 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +65 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +52 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +224 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js +35 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +27 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +97 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/types.js +6 -0
- package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -0
- package/jest.config.json +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement";
|
|
2
|
+
import ExceptionInstanceService from "./ExceptionInstanceService";
|
|
3
|
+
import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
|
|
4
|
+
import { JSONObject } from "../../Types/JSON";
|
|
5
|
+
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
7
|
+
import Includes from "../../Types/BaseDatabase/Includes";
|
|
8
|
+
import AnalyticsTableName from "../../Types/AnalyticsDatabase/AnalyticsTableName";
|
|
9
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
10
|
+
import { DbJSONResponse, Results } from "./AnalyticsDatabaseService";
|
|
11
|
+
|
|
12
|
+
export interface HistogramBucket {
|
|
13
|
+
time: string;
|
|
14
|
+
series: string;
|
|
15
|
+
count: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ExceptionFilters {
|
|
19
|
+
serviceIds?: Array<ObjectID> | undefined;
|
|
20
|
+
exceptionTypes?: Array<string> | undefined;
|
|
21
|
+
environments?: Array<string> | undefined;
|
|
22
|
+
fingerprints?: Array<string> | undefined;
|
|
23
|
+
traceIds?: Array<string> | undefined;
|
|
24
|
+
escaped?: boolean | undefined;
|
|
25
|
+
messageSearchText?: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HistogramRequest extends ExceptionFilters {
|
|
29
|
+
projectId: ObjectID;
|
|
30
|
+
startTime: Date;
|
|
31
|
+
endTime: Date;
|
|
32
|
+
bucketSizeInMinutes: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface FacetValue {
|
|
36
|
+
value: string;
|
|
37
|
+
count: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface FacetRequest extends ExceptionFilters {
|
|
41
|
+
projectId: ObjectID;
|
|
42
|
+
startTime: Date;
|
|
43
|
+
endTime: Date;
|
|
44
|
+
facetKey: string;
|
|
45
|
+
limit?: number | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class ExceptionAggregationService {
|
|
49
|
+
private static readonly DEFAULT_FACET_LIMIT: number = 500;
|
|
50
|
+
private static readonly TABLE_NAME: string =
|
|
51
|
+
AnalyticsTableName.ExceptionInstance;
|
|
52
|
+
private static readonly TOP_LEVEL_COLUMNS: Set<string> = new Set([
|
|
53
|
+
"serviceId",
|
|
54
|
+
"exceptionType",
|
|
55
|
+
"environment",
|
|
56
|
+
"fingerprint",
|
|
57
|
+
"traceId",
|
|
58
|
+
"spanId",
|
|
59
|
+
"escaped",
|
|
60
|
+
"release",
|
|
61
|
+
]);
|
|
62
|
+
private static readonly ATTRIBUTE_KEY_PATTERN: RegExp = /^[a-zA-Z0-9._:/-]+$/;
|
|
63
|
+
private static readonly MAX_FACET_KEY_LENGTH: number = 256;
|
|
64
|
+
|
|
65
|
+
@CaptureSpan()
|
|
66
|
+
public static async getHistogram(
|
|
67
|
+
request: HistogramRequest,
|
|
68
|
+
): Promise<Array<HistogramBucket>> {
|
|
69
|
+
const statement: Statement =
|
|
70
|
+
ExceptionAggregationService.buildHistogramStatement(request);
|
|
71
|
+
|
|
72
|
+
const dbResult: Results =
|
|
73
|
+
await ExceptionInstanceService.executeQuery(statement);
|
|
74
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
75
|
+
data?: Array<JSONObject>;
|
|
76
|
+
}>();
|
|
77
|
+
|
|
78
|
+
const rows: Array<JSONObject> = response.data || [];
|
|
79
|
+
|
|
80
|
+
return rows.map((row: JSONObject): HistogramBucket => {
|
|
81
|
+
return {
|
|
82
|
+
time: String(row["bucket"] || ""),
|
|
83
|
+
series: ExceptionAggregationService.mapEscapedToSeries(row["escaped"]),
|
|
84
|
+
count: Number(row["cnt"] || 0),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@CaptureSpan()
|
|
90
|
+
public static async getFacetValues(
|
|
91
|
+
request: FacetRequest,
|
|
92
|
+
): Promise<Array<FacetValue>> {
|
|
93
|
+
const statement: Statement =
|
|
94
|
+
ExceptionAggregationService.buildFacetStatement(request);
|
|
95
|
+
|
|
96
|
+
const dbResult: Results =
|
|
97
|
+
await ExceptionInstanceService.executeQuery(statement);
|
|
98
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
99
|
+
data?: Array<JSONObject>;
|
|
100
|
+
}>();
|
|
101
|
+
|
|
102
|
+
const rows: Array<JSONObject> = response.data || [];
|
|
103
|
+
|
|
104
|
+
return rows
|
|
105
|
+
.map((row: JSONObject): FacetValue => {
|
|
106
|
+
return {
|
|
107
|
+
value: String(row["val"] || ""),
|
|
108
|
+
count: Number(row["cnt"] || 0),
|
|
109
|
+
};
|
|
110
|
+
})
|
|
111
|
+
.filter((facet: FacetValue): boolean => {
|
|
112
|
+
return facet.value.length > 0;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private static mapEscapedToSeries(raw: unknown): string {
|
|
117
|
+
// ClickHouse returns booleans as 0/1
|
|
118
|
+
if (raw === true || raw === 1 || raw === "1" || raw === "true") {
|
|
119
|
+
return "unhandled";
|
|
120
|
+
}
|
|
121
|
+
return "handled";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private static buildHistogramStatement(request: HistogramRequest): Statement {
|
|
125
|
+
const intervalSeconds: number = request.bucketSizeInMinutes * 60;
|
|
126
|
+
|
|
127
|
+
const statement: Statement = SQL`
|
|
128
|
+
SELECT
|
|
129
|
+
toStartOfInterval(time, INTERVAL ${{
|
|
130
|
+
type: TableColumnType.Number,
|
|
131
|
+
value: intervalSeconds,
|
|
132
|
+
}} SECOND) AS bucket,
|
|
133
|
+
escaped,
|
|
134
|
+
count() AS cnt
|
|
135
|
+
FROM ${ExceptionAggregationService.TABLE_NAME}
|
|
136
|
+
WHERE projectId = ${{
|
|
137
|
+
type: TableColumnType.ObjectID,
|
|
138
|
+
value: request.projectId,
|
|
139
|
+
}}
|
|
140
|
+
AND time >= ${{
|
|
141
|
+
type: TableColumnType.Date,
|
|
142
|
+
value: request.startTime,
|
|
143
|
+
}}
|
|
144
|
+
AND time <= ${{
|
|
145
|
+
type: TableColumnType.Date,
|
|
146
|
+
value: request.endTime,
|
|
147
|
+
}}
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
ExceptionAggregationService.appendCommonFilters(statement, request);
|
|
151
|
+
|
|
152
|
+
statement.append(" GROUP BY bucket, escaped ORDER BY bucket ASC");
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* Defense in depth: cap histogram runtime below nginx's 60s
|
|
156
|
+
* proxy_read_timeout. 'break' returns partial aggregated results
|
|
157
|
+
* rather than throwing, which is acceptable for a density viz.
|
|
158
|
+
*/
|
|
159
|
+
statement.append(
|
|
160
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return statement;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private static buildFacetStatement(request: FacetRequest): Statement {
|
|
167
|
+
const limit: number =
|
|
168
|
+
request.limit ?? ExceptionAggregationService.DEFAULT_FACET_LIMIT;
|
|
169
|
+
|
|
170
|
+
ExceptionAggregationService.validateFacetKey(request.facetKey);
|
|
171
|
+
|
|
172
|
+
const isTopLevelColumn: boolean =
|
|
173
|
+
ExceptionAggregationService.isTopLevelColumn(request.facetKey);
|
|
174
|
+
|
|
175
|
+
const statement: Statement = new Statement();
|
|
176
|
+
|
|
177
|
+
if (isTopLevelColumn) {
|
|
178
|
+
statement.append(
|
|
179
|
+
SQL`SELECT toString(${request.facetKey}) AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`,
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
statement.append(
|
|
183
|
+
SQL`SELECT JSONExtractRaw(attributes, ${{
|
|
184
|
+
type: TableColumnType.Text,
|
|
185
|
+
value: request.facetKey,
|
|
186
|
+
}}) AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
statement.append(
|
|
191
|
+
SQL` WHERE projectId = ${{
|
|
192
|
+
type: TableColumnType.ObjectID,
|
|
193
|
+
value: request.projectId,
|
|
194
|
+
}} AND time >= ${{
|
|
195
|
+
type: TableColumnType.Date,
|
|
196
|
+
value: request.startTime,
|
|
197
|
+
}} AND time <= ${{
|
|
198
|
+
type: TableColumnType.Date,
|
|
199
|
+
value: request.endTime,
|
|
200
|
+
}}`,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (!isTopLevelColumn) {
|
|
204
|
+
statement.append(
|
|
205
|
+
SQL` AND JSONHas(attributes, ${{
|
|
206
|
+
type: TableColumnType.Text,
|
|
207
|
+
value: request.facetKey,
|
|
208
|
+
}}) = 1`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
ExceptionAggregationService.appendCommonFilters(statement, request);
|
|
213
|
+
|
|
214
|
+
statement.append(
|
|
215
|
+
SQL` GROUP BY val ORDER BY cnt DESC LIMIT ${{
|
|
216
|
+
type: TableColumnType.Number,
|
|
217
|
+
value: limit,
|
|
218
|
+
}}`,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
/*
|
|
222
|
+
* Defense in depth: cap individual facet query runtime below nginx's
|
|
223
|
+
* 60s proxy_read_timeout so a slow facet never starves the endpoint.
|
|
224
|
+
*/
|
|
225
|
+
statement.append(
|
|
226
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
return statement;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private static appendCommonFilters(
|
|
233
|
+
statement: Statement,
|
|
234
|
+
request: ExceptionFilters,
|
|
235
|
+
): void {
|
|
236
|
+
if (request.serviceIds && request.serviceIds.length > 0) {
|
|
237
|
+
statement.append(
|
|
238
|
+
SQL` AND serviceId IN (${{
|
|
239
|
+
type: TableColumnType.ObjectID,
|
|
240
|
+
value: new Includes(
|
|
241
|
+
request.serviceIds.map((id: ObjectID) => {
|
|
242
|
+
return id.toString();
|
|
243
|
+
}),
|
|
244
|
+
),
|
|
245
|
+
}})`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (request.exceptionTypes && request.exceptionTypes.length > 0) {
|
|
250
|
+
statement.append(
|
|
251
|
+
SQL` AND exceptionType IN (${{
|
|
252
|
+
type: TableColumnType.Text,
|
|
253
|
+
value: new Includes(request.exceptionTypes),
|
|
254
|
+
}})`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (request.environments && request.environments.length > 0) {
|
|
259
|
+
statement.append(
|
|
260
|
+
SQL` AND environment IN (${{
|
|
261
|
+
type: TableColumnType.Text,
|
|
262
|
+
value: new Includes(request.environments),
|
|
263
|
+
}})`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (request.fingerprints && request.fingerprints.length > 0) {
|
|
268
|
+
statement.append(
|
|
269
|
+
SQL` AND fingerprint IN (${{
|
|
270
|
+
type: TableColumnType.Text,
|
|
271
|
+
value: new Includes(request.fingerprints),
|
|
272
|
+
}})`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (request.traceIds && request.traceIds.length > 0) {
|
|
277
|
+
statement.append(
|
|
278
|
+
SQL` AND traceId IN (${{
|
|
279
|
+
type: TableColumnType.Text,
|
|
280
|
+
value: new Includes(request.traceIds),
|
|
281
|
+
}})`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (request.escaped !== undefined) {
|
|
286
|
+
statement.append(
|
|
287
|
+
SQL` AND escaped = ${{
|
|
288
|
+
type: TableColumnType.Boolean,
|
|
289
|
+
value: request.escaped,
|
|
290
|
+
}}`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
request.messageSearchText &&
|
|
296
|
+
request.messageSearchText.trim().length > 0
|
|
297
|
+
) {
|
|
298
|
+
statement.append(
|
|
299
|
+
SQL` AND message ILIKE ${{
|
|
300
|
+
type: TableColumnType.Text,
|
|
301
|
+
value: `%${request.messageSearchText.trim()}%`,
|
|
302
|
+
}}`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private static isTopLevelColumn(key: string): boolean {
|
|
308
|
+
return ExceptionAggregationService.TOP_LEVEL_COLUMNS.has(key);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private static validateFacetKey(
|
|
312
|
+
facetKey: unknown,
|
|
313
|
+
): asserts facetKey is string {
|
|
314
|
+
if (typeof facetKey !== "string") {
|
|
315
|
+
throw new BadDataException("Invalid facetKey");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (
|
|
319
|
+
facetKey.length === 0 ||
|
|
320
|
+
facetKey.length > ExceptionAggregationService.MAX_FACET_KEY_LENGTH
|
|
321
|
+
) {
|
|
322
|
+
throw new BadDataException("Invalid facetKey");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (ExceptionAggregationService.isTopLevelColumn(facetKey)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!ExceptionAggregationService.ATTRIBUTE_KEY_PATTERN.test(facetKey)) {
|
|
330
|
+
throw new BadDataException("Invalid facetKey");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export default ExceptionAggregationService;
|
package/Server/Services/Index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import IncidentStateTimelineService from "./IncidentStateTimelineService";
|
|
|
34
34
|
//Labels.
|
|
35
35
|
import LabelService from "./LabelService";
|
|
36
36
|
import KubernetesClusterService from "./KubernetesClusterService";
|
|
37
|
+
import DockerHostService from "./DockerHostService";
|
|
37
38
|
import LlmProviderService from "./LlmProviderService";
|
|
38
39
|
import LogService from "./LogService";
|
|
39
40
|
import MailService from "./MailService";
|
|
@@ -258,6 +259,7 @@ const services: Array<BaseService> = [
|
|
|
258
259
|
|
|
259
260
|
LabelService,
|
|
260
261
|
KubernetesClusterService,
|
|
262
|
+
DockerHostService,
|
|
261
263
|
LlmProviderService,
|
|
262
264
|
|
|
263
265
|
MailService,
|
|
@@ -174,6 +174,15 @@ export class LogAggregationService {
|
|
|
174
174
|
|
|
175
175
|
statement.append(" GROUP BY bucket, severityText ORDER BY bucket ASC");
|
|
176
176
|
|
|
177
|
+
/*
|
|
178
|
+
* Defense in depth: cap histogram runtime below nginx's 60s
|
|
179
|
+
* proxy_read_timeout. 'break' returns partial aggregated results
|
|
180
|
+
* rather than throwing, which is acceptable for a density viz.
|
|
181
|
+
*/
|
|
182
|
+
statement.append(
|
|
183
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
184
|
+
);
|
|
185
|
+
|
|
177
186
|
return statement;
|
|
178
187
|
}
|
|
179
188
|
|
|
@@ -233,6 +242,14 @@ export class LogAggregationService {
|
|
|
233
242
|
}}`,
|
|
234
243
|
);
|
|
235
244
|
|
|
245
|
+
/*
|
|
246
|
+
* Defense in depth: cap individual facet query runtime below nginx's
|
|
247
|
+
* 60s proxy_read_timeout so a slow facet never starves the endpoint.
|
|
248
|
+
*/
|
|
249
|
+
statement.append(
|
|
250
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
251
|
+
);
|
|
252
|
+
|
|
236
253
|
return statement;
|
|
237
254
|
}
|
|
238
255
|
|
|
@@ -94,13 +94,15 @@ export class Service extends DatabaseService<MonitorProbe> {
|
|
|
94
94
|
const claimedIds: Array<ObjectID> = await this.executeTransaction(
|
|
95
95
|
async (transactionalEntityManager: EntityManager) => {
|
|
96
96
|
/*
|
|
97
|
-
*
|
|
97
|
+
* Select and lock the monitor probes that need to be processed,
|
|
98
|
+
* including the monitoringInterval so we can compute the real nextPingAt
|
|
99
|
+
* in a single UPDATE (avoiding a second round-trip).
|
|
98
100
|
* FOR UPDATE SKIP LOCKED ensures that:
|
|
99
101
|
* 1. Rows are locked for this transaction
|
|
100
102
|
* 2. Rows already locked by other transactions are skipped
|
|
101
103
|
*/
|
|
102
104
|
const selectQuery: string = `
|
|
103
|
-
SELECT mp."_id"
|
|
105
|
+
SELECT mp."_id", m."monitoringInterval"
|
|
104
106
|
FROM "MonitorProbe" mp
|
|
105
107
|
INNER JOIN "Monitor" m ON mp."monitorId" = m."_id"
|
|
106
108
|
INNER JOIN "Project" p ON mp."projectId" = p."_id"
|
|
@@ -122,41 +124,60 @@ export class Service extends DatabaseService<MonitorProbe> {
|
|
|
122
124
|
FOR UPDATE OF mp SKIP LOCKED
|
|
123
125
|
`;
|
|
124
126
|
|
|
125
|
-
const selectedRows: Array<{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
const selectedRows: Array<{
|
|
128
|
+
_id: string;
|
|
129
|
+
monitoringInterval: string | null;
|
|
130
|
+
}> = await transactionalEntityManager.query(selectQuery, [
|
|
131
|
+
data.probeId.toString(),
|
|
132
|
+
currentDate,
|
|
133
|
+
data.limit,
|
|
134
|
+
]);
|
|
131
135
|
|
|
132
136
|
if (selectedRows.length === 0) {
|
|
133
137
|
return [];
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
/*
|
|
141
|
-
* Update the claimed monitors to set nextPingAt to 1 minute from now
|
|
142
|
-
* This is a temporary value; the actual nextPingAt will be calculated
|
|
143
|
-
* based on the monitor's interval after the probe fetches the full details
|
|
144
|
-
*/
|
|
145
|
-
const tempNextPingAt: Date = OneUptimeDate.addRemoveMinutes(
|
|
140
|
+
// Compute the real nextPingAt per monitor and batch-update in one query
|
|
141
|
+
const defaultNextPing: Date = OneUptimeDate.addRemoveMinutes(
|
|
146
142
|
currentDate,
|
|
147
143
|
1,
|
|
148
144
|
);
|
|
149
145
|
|
|
146
|
+
const ids: Array<string> = [];
|
|
147
|
+
const nextPingDates: Array<Date> = [];
|
|
148
|
+
const caseFragments: Array<string> = [];
|
|
149
|
+
|
|
150
|
+
for (let i: number = 0; i < selectedRows.length; i++) {
|
|
151
|
+
const row: { _id: string; monitoringInterval: string | null } =
|
|
152
|
+
selectedRows[i]!;
|
|
153
|
+
ids.push(row._id);
|
|
154
|
+
|
|
155
|
+
let nextPing: Date = defaultNextPing;
|
|
156
|
+
if (row.monitoringInterval) {
|
|
157
|
+
try {
|
|
158
|
+
nextPing = CronTab.getNextExecutionTime(row.monitoringInterval);
|
|
159
|
+
} catch {
|
|
160
|
+
// fall back to default 1 minute
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
nextPingDates.push(nextPing);
|
|
165
|
+
caseFragments.push(
|
|
166
|
+
`WHEN '${row._id}'::uuid THEN $${i + 3}::timestamptz`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
const updateQuery: string = `
|
|
151
171
|
UPDATE "MonitorProbe"
|
|
152
|
-
SET "lastPingAt" = $1,
|
|
153
|
-
|
|
172
|
+
SET "lastPingAt" = $1,
|
|
173
|
+
"nextPingAt" = CASE "_id" ${caseFragments.join(" ")} END
|
|
174
|
+
WHERE "_id" = ANY($2::uuid[])
|
|
154
175
|
`;
|
|
155
176
|
|
|
156
177
|
await transactionalEntityManager.query(updateQuery, [
|
|
157
178
|
currentDate,
|
|
158
|
-
tempNextPingAt,
|
|
159
179
|
ids,
|
|
180
|
+
...nextPingDates,
|
|
160
181
|
]);
|
|
161
182
|
|
|
162
183
|
return ids.map((id: string) => {
|
|
@@ -71,7 +71,6 @@ import ExceptionMessages from "../../Types/Exception/ExceptionMessages";
|
|
|
71
71
|
import Project from "../../Models/DatabaseModels/Project";
|
|
72
72
|
import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
|
|
73
73
|
import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
|
|
74
|
-
import MetricService from "./MetricService";
|
|
75
74
|
|
|
76
75
|
export interface MonitorDestinationInfo {
|
|
77
76
|
monitorDestination: string;
|
|
@@ -284,27 +283,28 @@ export class Service extends DatabaseService<Model> {
|
|
|
284
283
|
onDelete: OnDelete<Model>,
|
|
285
284
|
_itemIdsBeforeDelete: ObjectID[],
|
|
286
285
|
): Promise<OnDelete<Model>> {
|
|
286
|
+
/*
|
|
287
|
+
* The monitor has already been deleted from the database at this point.
|
|
288
|
+
* Any failure in the post-delete side effects below (e.g. billing
|
|
289
|
+
* reporting) must NOT propagate up to the caller as a 500 — otherwise the
|
|
290
|
+
* client sees "500 Internal Server Error" even though the delete actually
|
|
291
|
+
* succeeded. Log and swallow instead.
|
|
292
|
+
*
|
|
293
|
+
* Note: we intentionally do NOT delete Metric rows for this monitor here.
|
|
294
|
+
* The Metric table has a ClickHouse TTL on retentionDate (set at ingest
|
|
295
|
+
* from GlobalConfig.monitorMetricRetentionInDays) that auto-drops rows.
|
|
296
|
+
* A synchronous ALTER TABLE … DELETE on every monitor deletion is both
|
|
297
|
+
* redundant and expensive.
|
|
298
|
+
*/
|
|
287
299
|
if (onDelete.deleteBy.props.tenantId && IsBillingEnabled) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
await MetricService.deleteBy({
|
|
300
|
-
query: {
|
|
301
|
-
projectId: monitor.projectId,
|
|
302
|
-
serviceId: monitor.id,
|
|
303
|
-
},
|
|
304
|
-
props: {
|
|
305
|
-
isRoot: true,
|
|
306
|
-
},
|
|
307
|
-
});
|
|
300
|
+
try {
|
|
301
|
+
await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider(
|
|
302
|
+
onDelete.deleteBy.props.tenantId,
|
|
303
|
+
);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logger.error(
|
|
306
|
+
`Error while reporting active monitor quantity to billing provider for project ${onDelete.deleteBy.props.tenantId?.toString()}: ${error}`,
|
|
307
|
+
);
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|