diff --git a/server/src/api/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts index af73d786a..1da1d430e 100644 --- a/server/src/api/controllers/monitorController.ts +++ b/server/src/api/controllers/monitorController.ts @@ -21,6 +21,7 @@ import { getDockerDetailsByIdQueryValidation, getDockerContainerNameParamValidation, getDockerContainerByNameQueryValidation, + getDockerContainerLogsQueryValidation, } from "@/api/validation/monitorValidation.js"; import sslChecker from "ssl-checker"; import * as whoiser from "whoiser"; @@ -37,6 +38,7 @@ export interface IMonitorController { getPageSpeedDetailsById: RequestHandler; getDockerDetailsById: RequestHandler; getDockerContainerByName: RequestHandler; + getDockerContainerLogs: RequestHandler; getGeoChecksByMonitorId: RequestHandler; getMonitorById: RequestHandler; createMonitor: RequestHandler; @@ -198,6 +200,29 @@ class MonitorController implements IMonitorController { }); }); + getDockerContainerLogs = catchAsync(async (req: Request, res: Response) => { + const validatedParams = getDockerContainerNameParamValidation.parse(req.params); + const validatedQuery = getDockerContainerLogsQueryValidation.parse(req.query); + + const { monitorId, containerName } = validatedParams; + const { before, limit } = validatedQuery; + const teamId = requireTeamId(req.user?.teamId); + + const data = await this.monitorService.getDockerContainerLogs({ + teamId, + monitorId, + containerName, + before: before ? new Date(before) : undefined, + limit, + }); + + return res.status(200).json({ + success: true, + msg: "Docker container logs retrieved successfully", + data, + }); + }); + getGeoChecksByMonitorId = catchAsync(async (req: Request, res: Response) => { const validatedParams = getMonitorByIdParamValidation.parse(req.params); const validatedQuery = getMonitorByIdQueryValidation.parse(req.query); diff --git a/server/src/api/routes/monitorRoutes.ts b/server/src/api/routes/monitorRoutes.ts index 5192f6619..dab4614cd 100644 --- a/server/src/api/routes/monitorRoutes.ts +++ b/server/src/api/routes/monitorRoutes.ts @@ -21,6 +21,7 @@ export const createMonitorRoutes = (monitorController: IMonitorController): Rout // Docker routes router.get("/docker/details/:monitorId", monitorController.getDockerDetailsById); router.get("/docker/details/:monitorId/containers/:containerName", monitorController.getDockerContainerByName); + router.get("/docker/details/:monitorId/containers/:containerName/logs", monitorController.getDockerContainerLogs); // Geo checks routes router.get("/:monitorId/geo-checks", monitorController.getGeoChecksByMonitorId); diff --git a/server/src/api/validation/monitorValidation.ts b/server/src/api/validation/monitorValidation.ts index 09f85401c..32d2abee2 100644 --- a/server/src/api/validation/monitorValidation.ts +++ b/server/src/api/validation/monitorValidation.ts @@ -13,7 +13,8 @@ import { ProxyModes, } from "@/domain/monitors/monitor.type.js"; import { DateRanges, SortOrders } from "@/types/query.js"; -import { DockerContainerStates, DockerHealthStatuses, DockerPortProtocols } from "@/domain/docker/docker.type.js"; +import { DockerContainerStates, DockerHealthStatuses, DockerLogStreams, DockerPortProtocols } from "@/domain/docker/docker.type.js"; +import { DOCKER_LOG_PAGE_DEFAULT, DOCKER_LOG_PAGE_MAX } from "@/domain/docker/docker-log.type.js"; const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" }); @@ -332,6 +333,11 @@ export const getDockerContainerNameParamValidation = z.object({ }); export const getDockerContainerByNameQueryValidation = z.object({ dateRange: z.enum(DateRanges).optional() }); +export const getDockerContainerLogsQueryValidation = z.object({ + before: z.iso.datetime().optional(), + limit: z.coerce.number().int().min(1).max(DOCKER_LOG_PAGE_MAX).default(DOCKER_LOG_PAGE_DEFAULT), +}); + // Canonical monitor shape returned by /monitors endpoints. Keep aligned with // what the controllers actually serialize. export const monitorResponseSchema = z @@ -529,3 +535,26 @@ export const dockerContainerDetailsResponseSchema = z.object({ .nullable(), }), }); + +export const dockerLogLineResponseSchema = z.object({ + ts: z.string(), + stream: z.enum(DockerLogStreams), + text: z.string(), +}); + +export const dockerLogResponseSchema = z.object({ + id: z.string(), + metadata: z.object({ monitorId: z.string(), teamId: z.string(), containerId: z.string(), containerName: z.string() }), + lines: z.array(dockerLogLineResponseSchema), + gap: z.boolean(), + checkedAt: z.string(), + expiry: z.string(), + createdAt: z.string(), + updatedAt: z.string(), +}); + +// Response of GET /monitors/docker/details/{monitorId}/containers/{containerName}/logs. +export const dockerContainerLogsResponseSchema = z.object({ + logs: z.array(dockerLogResponseSchema), + nextCursor: z.string().nullable(), +}); diff --git a/server/src/config/services.api.ts b/server/src/config/services.api.ts index 5ce76aa3d..65607107c 100644 --- a/server/src/config/services.api.ts +++ b/server/src/config/services.api.ts @@ -35,6 +35,7 @@ export const buildApi = (shared: SharedServices, jobScheduler: IJobScheduler): A monitorsRepository, checksRepository, geoChecksRepository, + dockerLogsRepository, monitorStatsRepository, statusPagesRepository, usersRepository, @@ -75,6 +76,7 @@ export const buildApi = (shared: SharedServices, jobScheduler: IJobScheduler): A monitorsRepository, checksRepository, geoChecksRepository, + dockerLogsRepository, monitorStatsRepository, statusPagesRepository, incidentsRepository, diff --git a/server/src/config/services.shared.ts b/server/src/config/services.shared.ts index 7ea873c4b..76edfbc99 100644 --- a/server/src/config/services.shared.ts +++ b/server/src/config/services.shared.ts @@ -13,6 +13,7 @@ import { IDb } from "@/db/db.interface.js"; import { ISettingsService, EnvConfig, SettingsService } from "@/domain/app-settings/app-settings.service.js"; import { ICheckService, CheckService } from "@/domain/checks/check.service.js"; import { IGeoChecksService, GeoChecksService } from "@/domain/geo-checks/geo-check.service.js"; +import { IDockerLogsService, DockerLogsService } from "@/domain/docker/docker-log.service.js"; import { IIncidentService, IncidentService } from "@/domain/incidents/incident.service.js"; import { INotificationMessageBuilder, NotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js"; import { INotificationsService, NotificationsService } from "@/domain/notifications/notification.service.js"; @@ -58,6 +59,7 @@ import { IProxiesRepository } from "@/domain/proxies/proxy.repository.interface. import MongoSettingsRepository from "@/domain/app-settings/app-settings.repository.mongo.js"; import MongoChecksRepository from "@/domain/checks/check.repository.mongo.js"; import MongoGeoChecksRepository from "@/domain/geo-checks/geo-check.repository.mongo.js"; +import MongoDockerLogsRepository from "@/domain/docker/docker-log.repository.mongo.js"; import MongoIncidentsRepository from "@/domain/incidents/incident.repository.mongo.js"; import MongoInvitesRepository from "@/domain/invites/invite.repository.mongo.js"; import MongoJobsRepository from "@/domain/jobs/job.repository.mongo.js"; @@ -72,6 +74,7 @@ import MongoTagsRepository from "@/domain/tags/tag.repository.mongo.js"; import MongoTeamsRepository from "@/domain/teams/team.repository.model.js"; import MongoUsersRepository from "@/domain/users/user.repository.mongo.js"; import MongoProxiesRepository from "@/domain/proxies/proxy.repository.mongo.js"; +import { IDockerLogsRepository } from "@/domain/docker/docker-log.repository.interface.js"; // Shared infrastructure + business services that both the API and the worker process construct. export interface SharedServices { @@ -83,6 +86,7 @@ export interface SharedServices { incidentService: IIncidentService; checkService: ICheckService; geoChecksService: IGeoChecksService; + dockerLogsService: IDockerLogsService; notificationsService: INotificationsService; // Queue identity (one per process; the worker reuses these) @@ -94,6 +98,7 @@ export interface SharedServices { monitorsRepository: IMonitorsRepository; checksRepository: IChecksRepository; geoChecksRepository: IGeoChecksRepository; + dockerLogsRepository: IDockerLogsRepository; monitorStatsRepository: IMonitorStatsRepository; statusPagesRepository: IStatusPagesRepository; usersRepository: IUsersRepository; @@ -131,6 +136,7 @@ export const buildShared = async ({ const incidentsRepository = new MongoIncidentsRepository(); const usersRepository = new MongoUsersRepository(); const geoChecksRepository = new MongoGeoChecksRepository(); + const dockerLogsRepository = new MongoDockerLogsRepository(); const notificationsRepository = new MongoNotificationsRepository(); const jobsRepository = new MongoJobsRepository(workerId); const queueWorkersRepository = new MongoQueueWorkersRepository(); @@ -161,6 +167,7 @@ export const buildShared = async ({ monitorsRepository, }); + const dockerLogsService = new DockerLogsService({ logger, dockerLogsRepository }); const webhookProvider = new WebhookProvider(logger); const rocketChatProvider = new RocketChatProvider(logger); const slackProvider = new SlackProvider(logger); @@ -207,6 +214,7 @@ export const buildShared = async ({ incidentService, checkService, geoChecksService, + dockerLogsService, notificationsService, workerId, jobsRepository, @@ -214,6 +222,7 @@ export const buildShared = async ({ queueWorkersRepository, checksRepository, geoChecksRepository, + dockerLogsRepository, monitorStatsRepository, statusPagesRepository, usersRepository, diff --git a/server/src/config/services.worker.ts b/server/src/config/services.worker.ts index 9b4ea8609..edea2b045 100644 --- a/server/src/config/services.worker.ts +++ b/server/src/config/services.worker.ts @@ -52,6 +52,7 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig settingsService, checkService, geoChecksService, + dockerLogsService, notificationsService, incidentService, workerId, @@ -60,6 +61,7 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig monitorsRepository, checksRepository, geoChecksRepository, + dockerLogsRepository, monitorStatsRepository, incidentsRepository, teamsRepository, @@ -95,7 +97,7 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig ]); const proxyResolver = new ProxyResolver(proxiesRepository, settingsService, logger); - const bufferService = new BufferService(logger, checkService, geoChecksService, settingsService, jobsRepository); + const bufferService = new BufferService(logger, checkService, geoChecksService, dockerLogsService, settingsService, jobsRepository); const statusService = new StatusService(logger, monitorsRepository, monitorStatsRepository); const monitorStatusPolicy = new MonitorStatusPolicy(); @@ -119,6 +121,7 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig networkService, proxyResolver, bufferService, + dockerLogsService, logger ); const checkEvaluator = new CheckEvaluator(statusService, monitorStatusPolicy); @@ -138,7 +141,8 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig monitorStatsRepository, checksRepository, incidentsRepository, - geoChecksRepository + geoChecksRepository, + dockerLogsRepository ); const worker = await DBQueueWorker.create({ diff --git a/server/src/domain/docker/docker-log.repository.mongo.ts b/server/src/domain/docker/docker-log.repository.mongo.ts index bbe9ec002..f67d87a04 100644 --- a/server/src/domain/docker/docker-log.repository.mongo.ts +++ b/server/src/domain/docker/docker-log.repository.mongo.ts @@ -91,3 +91,5 @@ class MongoDockerLogsRepository implements IDockerLogsRepository { deleteByMonitorIdsNotIn = async (monitorIds: string[]) => (await DockerLogModel.deleteMany({ "metadata.monitorId": { $nin: monitorIds.map((id) => new mongoose.Types.ObjectId(id)) } })).deletedCount ?? 0; } + +export default MongoDockerLogsRepository; diff --git a/server/src/domain/docker/docker-log.service.ts b/server/src/domain/docker/docker-log.service.ts index 0f914dde3..a67888de0 100644 --- a/server/src/domain/docker/docker-log.service.ts +++ b/server/src/domain/docker/docker-log.service.ts @@ -13,7 +13,7 @@ export interface IDockerLogsService { createDockerLogs(logs: DockerLog[]): Promise; } -export class DockerLogsSerivce implements IDockerLogsService { +export class DockerLogsService implements IDockerLogsService { static SERVICE_NAME = SERVICE_NAME; private logger: ILogger; diff --git a/server/src/domain/monitors/monitor.service.ts b/server/src/domain/monitors/monitor.service.ts index b4a7131df..bc4750de6 100644 --- a/server/src/domain/monitors/monitor.service.ts +++ b/server/src/domain/monitors/monitor.service.ts @@ -9,6 +9,7 @@ import type { GroupedGeoCheckResult, DockerDetailsResult, DockerContainerDetailsResult, + DockerContainerLogsResult, } from "@/domain/monitors/monitor.type.js"; import { supportsGeoCheck, supportsUptimeDetails } from "@/domain/monitors/monitor.type.js"; import type { @@ -31,6 +32,7 @@ import type { ImportedMonitor } from "@/api/validation/monitorValidation.js"; import { ILogger } from "@/utils/logger.js"; import { IJobScheduler } from "@/worker/worker.interface.js"; import { DateRange } from "@/types/query.js"; +import { IDockerLogsRepository } from "@/domain/docker/docker-log.repository.interface.js"; const SERVICE_NAME = "MonitorService"; @@ -73,6 +75,13 @@ export interface IMonitorService { containerName: string; dateRange: DateRange; }): Promise; + getDockerContainerLogs(args: { + teamId: string; + monitorId: string; + containerName: string; + before?: Date; + limit: number; + }): Promise; getGeoChecksByMonitorId(args: { teamId: string; monitorId: string; @@ -130,6 +139,7 @@ export class MonitorService implements IMonitorService { private monitorsRepository: IMonitorsRepository; private checksRepository: IChecksRepository; private geoChecksRepository: IGeoChecksRepository; + private dockerLogsRepository: IDockerLogsRepository; private monitorStatsRepository: IMonitorStatsRepository; private statusPagesRepository: IStatusPagesRepository; private incidentsRepository: IIncidentsRepository; @@ -141,6 +151,7 @@ export class MonitorService implements IMonitorService { monitorsRepository, checksRepository, geoChecksRepository, + dockerLogsRepository, monitorStatsRepository, statusPagesRepository, incidentsRepository, @@ -151,6 +162,7 @@ export class MonitorService implements IMonitorService { monitorsRepository: IMonitorsRepository; checksRepository: IChecksRepository; geoChecksRepository: IGeoChecksRepository; + dockerLogsRepository: IDockerLogsRepository; monitorStatsRepository: IMonitorStatsRepository; statusPagesRepository: IStatusPagesRepository; incidentsRepository: IIncidentsRepository; @@ -161,6 +173,7 @@ export class MonitorService implements IMonitorService { this.monitorsRepository = monitorsRepository; this.checksRepository = checksRepository; this.geoChecksRepository = geoChecksRepository; + this.dockerLogsRepository = dockerLogsRepository; this.monitorStatsRepository = monitorStatsRepository; this.statusPagesRepository = statusPagesRepository; this.incidentsRepository = incidentsRepository; @@ -385,6 +398,38 @@ export class MonitorService implements IMonitorService { }; }; + getDockerContainerLogs = async ({ + teamId, + monitorId, + containerName, + before, + limit, + }: { + teamId: string; + monitorId: string; + containerName: string; + before?: Date; + limit: number; + }): Promise => { + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ + message: `Monitor with ID ${monitorId} not found`, + status: 404, + }); + } + + if (monitor.type !== "docker") { + throw new AppError({ + message: `${monitor.type} monitors are not supported for docker container logs`, + status: 400, + }); + } + + const logs = await this.dockerLogsRepository.findByContainerName({ monitorId, containerName, before, limit }); + return { logs, nextCursor: logs.length === limit ? (logs[logs.length - 1]?.checkedAt ?? null) : null }; + }; + getGeoChecksByMonitorId = async ({ teamId, monitorId, @@ -593,6 +638,14 @@ export class MonitorService implements IMonitorService { }); }); + await this.dockerLogsRepository.deleteByMonitorId(monitor.id).catch((err: unknown) => { + this.logger.warn({ + message: `Error deleting docker logs for monitor ${monitor.id} with name ${monitor.name}`, + service: SERVICE_NAME, + stack: err instanceof Error ? err.stack : undefined, + }); + }); + await this.monitorStatsRepository.deleteByMonitorId(monitor.id).catch((err: unknown) => { this.logger.warn({ message: `Error deleting monitor stats for monitor ${monitor.id} with name ${monitor.name}`, diff --git a/server/src/domain/monitors/monitor.type.ts b/server/src/domain/monitors/monitor.type.ts index df9581131..49fc3cf57 100644 --- a/server/src/domain/monitors/monitor.type.ts +++ b/server/src/domain/monitors/monitor.type.ts @@ -5,6 +5,7 @@ export type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js"; import http from "node:http"; import { HardwareStats } from "@/domain/checks/check.type.js"; import { MonitorStats } from "@/domain/monitor-stats/monitor-stats.type.js"; +import { DockerLogPage } from "@/domain/docker/docker-log.type.js"; export const HttpStatusCodes = [ ...Object.keys(http.STATUS_CODES).map(Number), @@ -189,3 +190,5 @@ export interface Game { export type GamesMap = Record; export type MonitorScheduleFields = Pick; + +export type DockerContainerLogsResult = DockerLogPage; diff --git a/server/src/service/bufferService.ts b/server/src/service/bufferService.ts index 2d10aebbb..672ec174c 100755 --- a/server/src/service/bufferService.ts +++ b/server/src/service/bufferService.ts @@ -5,14 +5,18 @@ import type { ILogger } from "@/utils/logger.js"; import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; import type { IJobsRepository } from "@/domain/jobs/job.repository.interface.js"; import { ICheckService } from "../domain/checks/check.service.js"; +import { DockerLog } from "@/domain/docker/docker-log.type.js"; +import { IDockerLogsService } from "@/domain/docker/docker-log.service.js"; const SERVICE_NAME = "BufferService"; export interface IBufferService { addToBuffer(check: Check): void; addGeoCheckToBuffer(geoCheck: GeoCheck): void; + addDockerLogToBuffer(dockerLog: DockerLog): void; scheduleNextFlush(): void; flushBuffer(): Promise; flushGeoBuffer(): Promise; + flushDockerLogsBuffer(): Promise; shutdown(): Promise; } @@ -23,15 +27,18 @@ export class BufferService implements IBufferService { private SERVICE_NAME: string; private buffer: Check[]; private geoBuffer: GeoCheck[]; + private dockerLogBuffer: DockerLog[]; private bufferTimer: NodeJS.Timeout | null = null; private checksService: ICheckService; private geoChecksService: IGeoChecksService; + private dockerLogsService: IDockerLogsService; private jobsRepository: IJobsRepository; constructor( logger: ILogger, checkService: ICheckService, geoChecksService: IGeoChecksService, + dockerLogsService: IDockerLogsService, settingsService: ISettingsService, jobsRepository: IJobsRepository ) { @@ -39,10 +46,12 @@ export class BufferService implements IBufferService { this.logger = logger; this.checksService = checkService; this.geoChecksService = geoChecksService; + this.dockerLogsService = dockerLogsService; this.jobsRepository = jobsRepository; this.SERVICE_NAME = SERVICE_NAME; this.buffer = []; this.geoBuffer = []; + this.dockerLogBuffer = []; this.scheduleNextFlush(); this.logger.info({ message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`, @@ -77,6 +86,19 @@ export class BufferService implements IBufferService { } } + addDockerLogToBuffer(dockerLog: DockerLog): void { + try { + this.dockerLogBuffer.push(dockerLog); + } catch (error: unknown) { + this.logger.error({ + message: error instanceof Error ? error.message : "Unknown error", + service: this.SERVICE_NAME, + method: "addDockerLogToBuffer", + stack: error instanceof Error ? error.stack : undefined, + }); + } + } + scheduleNextFlush() { if (this.bufferTimer) { clearTimeout(this.bufferTimer); @@ -85,6 +107,7 @@ export class BufferService implements IBufferService { try { await this.flushBuffer(); await this.flushGeoBuffer(); + await this.flushDockerLogsBuffer(); } catch (error: unknown) { this.logger.error({ message: error instanceof Error ? error.message : "Unknown error", @@ -150,6 +173,30 @@ export class BufferService implements IBufferService { } } + async flushDockerLogsBuffer() { + if (this.dockerLogBuffer.length === 0) { + return; + } + // Take the batch first so a write that drains it can't be appended to mid-flush + const batch = this.dockerLogBuffer; + this.dockerLogBuffer = []; + try { + this.logger.debug({ + message: `Flushing ${batch.length} docker logs to database`, + service: this.SERVICE_NAME, + method: "flushDockerLogsBuffer", + }); + await this.dockerLogsService.createDockerLogs(batch); + } catch (error: unknown) { + this.logger.error({ + message: error instanceof Error ? error.message : "Unknown error", + service: this.SERVICE_NAME, + method: "flushDockerLogsBuffer", + stack: error instanceof Error ? error.stack : undefined, + }); + } + } + async shutdown() { if (this.bufferTimer) { clearTimeout(this.bufferTimer); @@ -157,5 +204,6 @@ export class BufferService implements IBufferService { } await this.flushBuffer(); await this.flushGeoBuffer(); + await this.flushDockerLogsBuffer(); } } diff --git a/server/src/service/network/DockerProvider.ts b/server/src/service/network/DockerProvider.ts index 4e0d5e921..f2ece3f4e 100644 --- a/server/src/service/network/DockerProvider.ts +++ b/server/src/service/network/DockerProvider.ts @@ -252,7 +252,7 @@ export class DockerProvider implements IStatusProvider { const demuxedBuffer = this.demuxLogBuffer(buffer); for (const byteStream of demuxedBuffer) { const { stream, chunk } = byteStream; - for (const raw of chunk.split("/n")) { + for (const raw of chunk.split("\n")) { if (raw.length === 0) continue; const line = this.toLogLine(stream, raw); if (line) lines.push(line); diff --git a/server/src/worker/worker.check-producer.ts b/server/src/worker/worker.check-producer.ts index f297d01be..404978aef 100644 --- a/server/src/worker/worker.check-producer.ts +++ b/server/src/worker/worker.check-producer.ts @@ -1,6 +1,6 @@ import { Monitor } from "@/domain/monitors/monitor.type.js"; import { Check } from "@/domain/checks/check.type.js"; -import { MonitorStatusResponse } from "@/types/network.js"; +import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js"; import { AppError } from "@/utils/AppError.js"; import { ILogger } from "@/utils/logger.js"; import { IBufferService } from "@/service/bufferService.js"; @@ -10,6 +10,7 @@ import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interf import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js"; import { isWindowActive } from "@/utils/maintenanceWindow.js"; import { IProxyResolver } from "@/service/network/ProxyResolver.js"; +import { IDockerLogsService } from "@/domain/docker/docker-log.service.js"; export interface ICheckProducer { produce(monitor: Monitor): Promise<{ status: MonitorStatusResponse; check: Check } | null>; @@ -25,6 +26,7 @@ export class CheckProducer implements ICheckProducer { private networkService: INetworkService, private proxyResolver: IProxyResolver, private bufferService: IBufferService, + private dockerLogsService: IDockerLogsService, private logger: ILogger ) {} @@ -81,6 +83,14 @@ export class CheckProducer implements ICheckProducer { } // Step 2b: Add to buffer this.bufferService.addToBuffer(check); + + // Step 2c: Handle docker logs + if (status.type === "docker") { + const dockerLogs = await this.dockerLogsService.buildDockerLogs(status as MonitorStatusResponse); + for (const dockerLog of dockerLogs) { + this.bufferService.addDockerLogToBuffer(dockerLog); + } + } return { status, check }; }; } diff --git a/server/src/worker/worker.helper.ts b/server/src/worker/worker.helper.ts index 9f044763d..d8a35e0b2 100644 --- a/server/src/worker/worker.helper.ts +++ b/server/src/worker/worker.helper.ts @@ -10,6 +10,7 @@ import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interf import { IJobsRepository } from "@/domain/jobs/job.repository.interface.js"; import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js"; import { ILogger } from "@/utils/logger.js"; +import { IDockerLogsRepository } from "@/domain/docker/docker-log.repository.interface.js"; export interface IWorkerHelper { getCleanupOrphanedJob(): () => Promise; @@ -43,6 +44,7 @@ export class WorkerHelper implements IWorkerHelper { private checksRepository: IChecksRepository; private incidentsRepository: IIncidentsRepository; private geoChecksRepository: IGeoChecksRepository; + private dockerLogsRepository: IDockerLogsRepository; constructor( logger: ILogger, @@ -54,7 +56,8 @@ export class WorkerHelper implements IWorkerHelper { monitorStatsRepository: IMonitorStatsRepository, checksRepository: IChecksRepository, incidentsRepository: IIncidentsRepository, - geoChecksRepository: IGeoChecksRepository + geoChecksRepository: IGeoChecksRepository, + dockerLogsRepository: IDockerLogsRepository ) { this.logger = logger; this.checkService = checkService; @@ -66,6 +69,7 @@ export class WorkerHelper implements IWorkerHelper { this.checksRepository = checksRepository; this.incidentsRepository = incidentsRepository; this.geoChecksRepository = geoChecksRepository; + this.dockerLogsRepository = dockerLogsRepository; } getCleanupOrphanedJob = () => { @@ -142,6 +146,15 @@ export class WorkerHelper implements IWorkerHelper { }); } + const deletedDockerLogsCount = await this.dockerLogsRepository.deleteByMonitorIdsNotIn(allMonitorIds); + if (deletedDockerLogsCount > 0) { + this.logger.info({ + message: `Deleted ${deletedDockerLogsCount} orphaned docker logs`, + service: SERVICE_NAME, + method: "getCleanupOrphanedJob", + }); + } + // Remove orphaned jobs const deletedJobsCount = await this.jobsRepository.deleteByMonitorIdsNotIn(allMonitorIds); if (deletedJobsCount > 0) {