add opt in docker logs flag

This commit is contained in:
Alex Holliday committed 2026-09-04 14:06:46 -07:00
1 parent e012910332
commit 431cada08e
7 files changed
+54 -7

No files matched your search

@@ -177,6 +177,7 @@ export const createMonitorBodyValidation = z
geoCheckEnabled: z.boolean().optional(),
geoCheckLocations: z.array(z.enum(GeoContinents)).optional(),
geoCheckInterval: z.number().min(300000).optional(),
dockerLogsEnabled: z.boolean().optional(),
dnsServer: dnsServerValidation.optional(),
dnsRecordType: z.enum(DnsRecordTypes).optional(),
})
@@ -222,6 +223,7 @@ export const editMonitorBodyValidation = z
geoCheckEnabled: z.boolean().optional(),
geoCheckLocations: z.array(z.enum(GeoContinents)).optional(),
geoCheckInterval: z.number().min(300000).optional(),
dockerLogsEnabled: z.boolean().optional(),
dnsServer: dnsServerValidation.optional(),
dnsRecordType: z.enum(DnsRecordTypes).optional(),
})
@@ -298,6 +300,8 @@ const importedMonitorSchema = z
geoCheckEnabled: z.boolean().default(false),
geoCheckLocations: z.array(z.enum(GeoContinents)).default([]),
geoCheckInterval: z.number().min(300000).default(300000),
dockerLogsEnabled: z.boolean().default(false),
dnsServer: dnsServerValidation.optional(),
dnsRecordType: z.enum(DnsRecordTypes).optional(),
createdAt: z.string().optional(),
@@ -387,6 +391,7 @@ export const monitorResponseSchema = z
geoCheckEnabled: z.boolean(),
geoCheckLocations: z.array(z.enum(GeoContinents)),
geoCheckInterval: z.number(),
dockerLogsEnabled: z.boolean(),
dnsServer: z.string().optional(),
dnsRecordType: z.enum(DnsRecordTypes).optional(),
teamId: z.string(),
@@ -306,6 +306,10 @@ const MonitorSchema = new Schema<MonitorDocument>(
type: Number,
default: 300000,
},
dockerLogsEnabled: {
type: Boolean,
default: false,
},
dnsServer: {
type: String,
},
@@ -454,6 +454,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
geoCheckEnabled: doc.geoCheckEnabled ?? false,
geoCheckLocations: doc.geoCheckLocations ?? [],
geoCheckInterval: doc.geoCheckInterval ?? 300000,
dockerLogsEnabled: doc.dockerLogsEnabled ?? false,
dnsServer: doc.dnsServer ?? undefined,
dnsRecordType: doc.dnsRecordType ?? undefined,
createdAt: toDateString(doc.createdAt),
@@ -109,6 +109,7 @@ export interface Monitor {
geoCheckEnabled?: boolean;
geoCheckLocations?: GeoContinent[];
geoCheckInterval?: number;
dockerLogsEnabled?: boolean;
dnsServer?: string;
dnsRecordType?: DnsRecordType;
recentChecks: CheckSnapshot[];
+7 -2
View File
@@ -279,6 +279,11 @@ export class DockerProvider implements IStatusProvider<DockerStatusPayload> {
}
};
private collectLogs = async (docker: Dockerode, summaries: Dockerode.ContainerInfo[]): Promise<DockerContainerLogs[]> => {
const rawLogs = await this.mapWithConcurrency(summaries, STATS_CONCURRENCY, (s) => this.toContainerLogs(docker, s));
return rawLogs.filter((log) => log !== null);
};
handle = async (monitor: Monitor): Promise<MonitorStatusResponse<DockerStatusPayload>> => {
try {
const docker = new this.DockerLib(this.toDockerOptions(monitor));
@@ -307,8 +312,8 @@ export class DockerProvider implements IStatusProvider<DockerStatusPayload> {
const summaries = await docker.listContainers({ all: true });
const containers = await this.mapWithConcurrency(summaries, STATS_CONCURRENCY, (s) => this.toContainerInfo(docker, s));
const rawLogs = await this.mapWithConcurrency(summaries, STATS_CONCURRENCY, (s) => this.toContainerLogs(docker, s));
const logs = rawLogs.filter((log) => log !== null);
const logs = monitor.dockerLogsEnabled ? await this.collectLogs(docker, summaries) : undefined;
const summary: DockerContainerSummary = {
total: containers.length,
running: containers.filter((c) => c.state === "running").length,
@@ -297,7 +297,7 @@ describe("DockerProvider", () => {
);
const { provider } = setup({ logs });
const result = await provider.handle(makeMonitor());
const result = await provider.handle(makeMonitor({ dockerLogsEnabled: true }));
expect(result.payload?.logs?.[0]?.lines).toEqual([
{ ts: "2026-01-01T00:00:37.100000000Z", stream: "stdout", text: "ready" },
@@ -309,7 +309,7 @@ describe("DockerProvider", () => {
const logs = jest.fn().mockResolvedValue(Buffer.from("noise\n2026-01-01T00:00:00Z first\n2026-01-01T00:00:01.12Z second\n"));
const { provider } = setup({ logs });
const result = await provider.handle(makeMonitor());
const result = await provider.handle(makeMonitor({ dockerLogsEnabled: true }));
expect(result.payload?.logs?.[0]?.lines).toEqual([
{ ts: "2026-01-01T00:00:00.000000000Z", stream: "stdout", text: "first" },
@@ -321,7 +321,7 @@ describe("DockerProvider", () => {
const logs = jest.fn().mockResolvedValue(Buffer.from(`2026-01-01T00:00:00Z ${"x".repeat(5000)}\n`));
const { provider } = setup({ logs });
const result = await provider.handle(makeMonitor());
const result = await provider.handle(makeMonitor({ dockerLogsEnabled: true }));
const text = result.payload?.logs?.[0]?.lines[0]?.text;
expect(text).toEqual(expect.stringMatching(/ …\[truncated\]$/));
expect(Buffer.byteLength(text?.replace(" …[truncated]", "") ?? "", "utf8")).toBeLessThanOrEqual(4096);
@@ -330,7 +330,7 @@ describe("DockerProvider", () => {
it("isolates a logs failure and warns", async () => {
const { provider, logger } = setup({ logs: jest.fn().mockRejectedValue(new Error("logs failed")) });
const result = await provider.handle(makeMonitor());
const result = await provider.handle(makeMonitor({ dockerLogsEnabled: true }));
expect(result.payload?.logs).toEqual([]);
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Failed to read logs for container my-container" }));
@@ -340,11 +340,29 @@ describe("DockerProvider", () => {
const containers = [makeContainer({ Id: "a".repeat(64) }), makeContainer({ Id: "b".repeat(64), State: "exited" })];
const { provider, logs } = setup({ listContainers: jest.fn().mockResolvedValue(containers) });
await provider.handle(makeMonitor());
await provider.handle(makeMonitor({ dockerLogsEnabled: true }));
expect(logs).toHaveBeenCalledTimes(2);
expect(logs).toHaveBeenCalledWith({ follow: false, stdout: true, stderr: true, timestamps: true, tail: DOCKER_LOG_TAIL_LINES });
});
it("does not read logs when dockerLogsEnabled is false", async () => {
const { provider, logs } = setup();
const result = await provider.handle(makeMonitor({ dockerLogsEnabled: false }));
expect(logs).not.toHaveBeenCalled();
expect(result.payload?.logs).toBeUndefined();
});
it("does not read logs when dockerLogsEnabled is unset", async () => {
const { provider, logs } = setup();
const result = await provider.handle(makeMonitor());
expect(logs).not.toHaveBeenCalled();
expect(result.payload?.logs).toBeUndefined();
});
});
// ── Enrichment fault isolation ───────────────────────────────────────
@@ -233,6 +233,19 @@ describe("monitorValidation — strategy gating", () => {
expect(parsed.monitors[0].strategy).toBeUndefined();
});
it("defaults dockerLogsEnabled to false on imported docker monitors", () => {
const parsed = importMonitorsBodyValidation.parse({
monitors: [
{
name: "Imported Docker",
type: "docker",
url: "unix:///var/run/docker.sock",
},
],
});
expect(parsed.monitors[0].dockerLogsEnabled).toBe(false);
});
it("rejects strategy on imported non-pagespeed monitors", () => {
expect(() =>
importMonitorsBodyValidation.parse({