add docker container alert fields to monitors,checks

This commit is contained in:
Alex Holliday committed 2026-09-23 10:16:49 -07:00
1 parent a5e66318b5
commit a75bb5fae6
8 files changed
+46 -4

No files matched your search

@@ -220,6 +220,8 @@ export const createMonitorBodyValidation = z
geoCheckLocations: z.array(z.enum(GeoContinents)).optional(),
geoCheckInterval: z.number().min(300000).optional(),
dockerLogsEnabled: z.boolean().optional(),
dockerAlertOnStopped: z.boolean().optional(),
dockerAlertOnUnhealthy: z.boolean().optional(),
dockerTlsCa: z.union([z.string(), z.literal("")]).optional(),
dockerTlsCert: z.union([z.string(), z.literal("")]).optional(),
dockerTlsKey: z.union([z.string(), z.literal("")]).optional(),
@@ -350,6 +352,8 @@ const importedMonitorSchema = z
geoCheckLocations: z.array(z.enum(GeoContinents)).default([]),
geoCheckInterval: z.number().min(300000).default(300000),
dockerLogsEnabled: z.boolean().default(false),
dockerAlertOnStopped: z.boolean().default(false),
dockerAlertOnUnhealthy: z.boolean().default(false),
dnsServer: dnsServerValidation.optional(),
dnsRecordType: z.enum(DnsRecordTypes).optional(),
createdAt: z.string().optional(),
+1
View File
@@ -220,6 +220,7 @@ const dockerContainerSchema = new Schema<DockerContainerInfo>(
startedAt: { type: String },
ports: { type: [dockerContainerPortSchema], default: undefined },
mounts: { type: [dockerContainerMountSchema], default: undefined },
exitCode: { type: Number, default: undefined },
},
{ _id: false }
);
+12 -4
View File
@@ -135,6 +135,17 @@ export class CheckService implements ICheckService {
return check;
};
private toPayload = (check: Check): HardwareStatusPayload | DockerStatusPayload | undefined => {
switch (check.metadata.type) {
case "hardware":
return { data: { cpu: check.cpu, memory: check.memory, disk: check.disk, host: check.host, net: check.net } } as HardwareStatusPayload;
case "docker":
return check.containers ? { containers: check.containers, summary: check.containerSummary } : undefined;
default:
return undefined;
}
};
toStatusResponse = (check: Check) => {
const statusResponse: MonitorStatusResponse<MonitorPayloadMap[keyof MonitorPayloadMap]> = {
monitorId: check.metadata.monitorId,
@@ -145,10 +156,7 @@ export class CheckService implements ICheckService {
message: check.message,
responseTime: check.responseTime,
// re-nest hardware metrics so updateMonitorStatus / extractThresholdBreaches see payload.data
payload:
check.metadata.type === "hardware"
? ({ data: { cpu: check.cpu, memory: check.memory, disk: check.disk, host: check.host, net: check.net } } as HardwareStatusPayload)
: undefined,
payload: this.toPayload(check),
};
return statusResponse;
};
+1
View File
@@ -55,6 +55,7 @@ export interface DockerContainerInfo {
startedAt?: string; // ISO date
ports?: DockerContainerPort[];
mounts?: DockerContainerMount[];
exitCode?: number;
}
export interface DockerContainerSummary {
@@ -307,6 +307,14 @@ const MonitorSchema = new Schema<MonitorDocument>(
type: Boolean,
default: false,
},
dockerAlertOnStopped: {
type: Boolean,
default: false,
},
dockerAlertOnUnhealthy: {
type: Boolean,
default: false,
},
dockerTlsCa: {
type: String,
},
@@ -468,6 +468,8 @@ class MongoMonitorsRepository implements IMonitorsRepository {
geoCheckLocations: doc.geoCheckLocations ?? [],
geoCheckInterval: doc.geoCheckInterval ?? 300000,
dockerLogsEnabled: doc.dockerLogsEnabled ?? false,
dockerAlertOnStopped: doc.dockerAlertOnStopped ?? false,
dockerAlertOnUnhealthy: doc.dockerAlertOnUnhealthy ?? false,
dockerTlsCa: doc.dockerTlsCa ?? undefined,
dockerTlsCert: doc.dockerTlsCert ?? undefined,
dockerTlsKeySet: doc.dockerTlsKeySet ?? false,
@@ -134,6 +134,8 @@ export interface Monitor {
geoCheckLocations?: GeoContinent[];
geoCheckInterval?: number;
dockerLogsEnabled?: boolean;
dockerAlertOnStopped?: boolean;
dockerAlertOnUnhealthy?: boolean;
dockerTlsCa?: string;
dockerTlsCert?: string;
// EncryptionService ciphertext. Normally excluded
@@ -42,6 +42,8 @@ const DOCKER_LOG_TRUNCATION_MARKER = " …[truncated]";
const DOCKER_LOG_TS_REGEX = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,9}))?Z /;
const DOCKER_TLS_DEFAULT_PORT = 2376;
const CAPTURE_FATAL_ERROR_METRICS = new Set(["docker.client", "docker.container.list"]);
const EXITED_STATUS = /^Exited \((\d+)\)/;
const HEALTH_STATUS = /\((healthy|unhealthy|health: starting)\)$/;
export interface DockerError extends Error {
statusCode?: number;
@@ -140,6 +142,17 @@ export class DockerProvider implements IStatusProvider<DockerStatusPayload> {
return status && (DockerHealthStatuses as readonly string[]).includes(status) ? (status as DockerHealthStatus) : "none";
}
private exitCodeFromStatus = (status: string): number | undefined => {
const match = EXITED_STATUS.exec(status);
return match ? Number(match[1]) : undefined;
};
private healthFromStatus = (status: string): DockerHealthStatus => {
const match = HEALTH_STATUS.exec(status);
if (!match) return "none";
return match[1] === "health: starting" ? "starting" : match[1];
};
private toContainerSummary(containers: DockerContainerInfo[]): DockerContainerSummary {
return {
total: containers.length,
@@ -273,7 +286,10 @@ export class DockerProvider implements IStatusProvider<DockerStatusPayload> {
info.health = this.toHealthStatus(inspectResult.value.State?.Health?.Status);
info.ports = this.toPorts(inspectResult.value.NetworkSettings?.Ports);
info.mounts = this.toMounts(inspectResult.value.Mounts);
info.exitCode = inspectResult.value.State.ExitCode;
} else {
info.exitCode = this.exitCodeFromStatus(summary.Status);
info.health = this.healthFromStatus(summary.Status);
this.logger.warn({
message: `Failed to inspect container ${info.name}`,
service: SERVICE_NAME,