feat(backend): add customUpCodes support for HTTP monitors
Adds a customUpCodes field to the monitor schema to allow users to specify which HTTP status codes should be treated as UP instead of DOWN. Includes core logic in HttpProvider and unit tests. Refs #3657
This commit is contained in:
1 parent
b9e8a6dbc8
commit
4e31499f78
8 files changed
+86
-8
No files matched your search
@@ -291,6 +291,10 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
ref: "Tag",
|
||||
},
|
||||
],
|
||||
customUpCodes: {
|
||||
type: [Number],
|
||||
default: [],
|
||||
},
|
||||
secret: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
@@ -456,6 +456,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
uptimePercentage: doc.uptimePercentage ?? undefined,
|
||||
notifications: notificationIds,
|
||||
tags: tagIds,
|
||||
customUpCodes: doc.customUpCodes ?? [],
|
||||
secret: doc.secret ?? undefined,
|
||||
cpuAlertThreshold: doc.cpuAlertThreshold,
|
||||
cpuAlertCounter: doc.cpuAlertCounter,
|
||||
@@ -520,6 +521,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
uptimePercentage: doc.uptimePercentage ?? undefined,
|
||||
notifications: notificationIds,
|
||||
tags: tagIds,
|
||||
customUpCodes: doc.customUpCodes ?? [],
|
||||
secret: doc.secret ?? undefined,
|
||||
cpuAlertThreshold: doc.cpuAlertThreshold,
|
||||
cpuAlertCounter: doc.cpuAlertCounter,
|
||||
|
||||
@@ -89,7 +89,7 @@ export class GeoChecksService implements IGeoChecksService {
|
||||
}
|
||||
|
||||
// Step 2: Poll for results
|
||||
const results = await this.globalPingService.pollForResults(measurementId);
|
||||
const results = await this.globalPingService.pollForResults(measurementId, undefined, monitor.customUpCodes ?? []);
|
||||
|
||||
if (results.length === 0) {
|
||||
// No successful results (all locations timed out or failed)
|
||||
|
||||
@@ -62,7 +62,7 @@ interface GlobalPingProbeResult {
|
||||
export interface IGlobalPingService {
|
||||
readonly serviceName: string;
|
||||
createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise<string | null>;
|
||||
pollForResults(measurementId: string, timeoutMs?: number): Promise<GeoCheckResult[]>;
|
||||
pollForResults(measurementId: string, timeoutMs?: number, customUpCodes?: number[]): Promise<GeoCheckResult[]>;
|
||||
}
|
||||
|
||||
export class GlobalPingService implements IGlobalPingService {
|
||||
@@ -119,7 +119,7 @@ export class GlobalPingService implements IGlobalPingService {
|
||||
}
|
||||
}
|
||||
|
||||
async pollForResults(measurementId: string, timeoutMs: number = MAX_POLL_TIMEOUT_MS): Promise<GeoCheckResult[]> {
|
||||
async pollForResults(measurementId: string, timeoutMs: number = MAX_POLL_TIMEOUT_MS, customUpCodes: number[] = []): Promise<GeoCheckResult[]> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
@@ -132,7 +132,7 @@ export class GlobalPingService implements IGlobalPingService {
|
||||
const measurement = response.body;
|
||||
|
||||
if (measurement.status === "finished") {
|
||||
const results = this.transformResults(measurement.results || []);
|
||||
const results = this.transformResults(measurement.results || [], customUpCodes);
|
||||
this.logger.debug({
|
||||
message: `GlobalPing measurement completed: ${measurementId}`,
|
||||
service: SERVICE_NAME,
|
||||
@@ -174,7 +174,7 @@ export class GlobalPingService implements IGlobalPingService {
|
||||
return [];
|
||||
}
|
||||
|
||||
private transformResults(probeResults: GlobalPingProbeResult[]): GeoCheckResult[] {
|
||||
private transformResults(probeResults: GlobalPingProbeResult[], customUpCodes: number[] = []): GeoCheckResult[] {
|
||||
const successfulResults: GeoCheckResult[] = [];
|
||||
|
||||
for (const probeResult of probeResults) {
|
||||
@@ -205,7 +205,8 @@ export class GlobalPingService implements IGlobalPingService {
|
||||
|
||||
successfulResults.push({
|
||||
location,
|
||||
status: probeResult.result.statusCode >= 200 && probeResult.result.statusCode < 300,
|
||||
status:
|
||||
customUpCodes.includes(probeResult.result.statusCode) || (probeResult.result.statusCode >= 200 && probeResult.result.statusCode < 300),
|
||||
statusCode: probeResult.result.statusCode,
|
||||
timings,
|
||||
});
|
||||
|
||||
@@ -31,11 +31,13 @@ export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
|
||||
|
||||
private handleHttpError<T>(error: unknown, monitor: Monitor): MonitorStatusResponse<T> {
|
||||
if (error instanceof HTTPError || error instanceof RequestError) {
|
||||
const isCustomUp = error instanceof HTTPError && (monitor.customUpCodes ?? []).includes(error.response?.statusCode);
|
||||
|
||||
return {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: false,
|
||||
status: isCustomUp,
|
||||
code: error.response?.statusCode ?? NETWORK_ERROR,
|
||||
message: error.message,
|
||||
responseTime: error.timings?.phases?.total ?? 0,
|
||||
@@ -106,7 +108,7 @@ export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: response.ok && matchResult.ok,
|
||||
status: ((monitor.customUpCodes ?? []).includes(response.statusCode) || response.ok) && matchResult.ok,
|
||||
code: response.statusCode,
|
||||
message: matchResult.ok ? (response.statusMessage ?? "OK") : matchResult.message,
|
||||
responseTime: response.timings.phases.total ?? 0,
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface Monitor {
|
||||
uptimePercentage?: number;
|
||||
notifications: string[];
|
||||
tags: string[];
|
||||
customUpCodes: number[];
|
||||
secret?: string;
|
||||
cpuAlertThreshold: number;
|
||||
cpuAlertCounter: number;
|
||||
|
||||
@@ -81,6 +81,7 @@ export const createMonitorBodyValidation = z
|
||||
tempAlertThreshold: z.number().optional(),
|
||||
notifications: z.array(z.string()).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
customUpCodes: z.array(z.number()).default([]),
|
||||
secret: z.string().optional(),
|
||||
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
||||
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
||||
@@ -110,6 +111,7 @@ export const editMonitorBodyValidation = z
|
||||
interval: z.number().optional(),
|
||||
notifications: z.array(z.string()).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
customUpCodes: z.array(z.number()).optional(),
|
||||
secret: z.string().optional(),
|
||||
ignoreTlsErrors: z.boolean().optional(),
|
||||
useAdvancedMatching: z.boolean().optional(),
|
||||
@@ -180,6 +182,7 @@ const importedMonitorSchema = z
|
||||
uptimePercentage: z.number().optional(),
|
||||
notifications: z.array(z.string()).default([]),
|
||||
tags: z.array(z.string()).default([]),
|
||||
customUpCodes: z.array(z.number()).default([]),
|
||||
secret: z.string().optional(),
|
||||
cpuAlertThreshold: z.number().default(100),
|
||||
cpuAlertCounter: z.number().default(5),
|
||||
@@ -241,6 +244,7 @@ export const monitorResponseSchema = z
|
||||
matchMethod: z.enum(MonitorMatchMethods).optional(),
|
||||
notifications: z.array(z.string()),
|
||||
tags: z.array(z.string()),
|
||||
customUpCodes: z.array(z.number()).optional(),
|
||||
secret: z.string().optional(),
|
||||
cpuAlertThreshold: z.number(),
|
||||
memoryAlertThreshold: z.number(),
|
||||
|
||||
Reference in new issue
Block a user