add after to docker logs query
This commit is contained in:
1 parent
37e1005479
commit
6e0afe223e
7 files changed
+60
-11
No files matched your search
@@ -205,7 +205,7 @@ class MonitorController implements IMonitorController {
|
||||
const validatedQuery = getDockerContainerLogsQueryValidation.parse(req.query);
|
||||
|
||||
const { monitorId, containerName } = validatedParams;
|
||||
const { before, limit } = validatedQuery;
|
||||
const { before, after, limit } = validatedQuery;
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const data = await this.monitorService.getDockerContainerLogs({
|
||||
@@ -213,6 +213,7 @@ class MonitorController implements IMonitorController {
|
||||
monitorId,
|
||||
containerName,
|
||||
before: before ? new Date(before) : undefined,
|
||||
after: after ? new Date(after) : undefined,
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -333,11 +333,21 @@ 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),
|
||||
});
|
||||
|
||||
export const getDockerContainerLogsQueryValidation = z
|
||||
.object({
|
||||
before: z.iso.datetime().optional(),
|
||||
after: z.iso.datetime().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(DOCKER_LOG_PAGE_MAX).default(DOCKER_LOG_PAGE_DEFAULT),
|
||||
})
|
||||
.superRefine((query, ctx) => {
|
||||
if (query.before && query.after) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "Specify either before or after, not both",
|
||||
path: ["after"],
|
||||
});
|
||||
}
|
||||
});
|
||||
// Canonical monitor shape returned by /monitors endpoints. Keep aligned with
|
||||
// what the controllers actually serialize.
|
||||
export const monitorResponseSchema = z
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DockerLog } from "@/domain/docker/docker-log.type.js";
|
||||
export interface IDockerLogsRepository {
|
||||
createDockerLogs(logs: Omit<DockerLog, "id" | "createdAt" | "updatedAt">[]): Promise<number>;
|
||||
findLastLineTimestamp(monitorId: string, containerId: string): Promise<string | null>;
|
||||
findByContainerName(args: { monitorId: string; containerName: string; before?: Date; limit: number }): Promise<DockerLog[]>;
|
||||
findByContainerName(args: { monitorId: string; containerName: string; before?: Date; after?: Date; limit: number }): Promise<DockerLog[]>;
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
deleteByTeamId(teamId: string): Promise<number>;
|
||||
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
|
||||
|
||||
@@ -64,22 +64,26 @@ class MongoDockerLogsRepository implements IDockerLogsRepository {
|
||||
monitorId,
|
||||
containerName,
|
||||
before,
|
||||
after,
|
||||
limit,
|
||||
}: {
|
||||
monitorId: string;
|
||||
containerName: string;
|
||||
before?: Date;
|
||||
after?: Date;
|
||||
limit: number;
|
||||
}): Promise<DockerLog[]> => {
|
||||
const docs = await DockerLogModel.find({
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
"metadata.containerName": containerName,
|
||||
...(before ? { checkedAt: { $lt: before } } : {}),
|
||||
...(after ? { checkedAt: { $gt: after } } : {}),
|
||||
})
|
||||
.sort({ checkedAt: -1 })
|
||||
.sort({ checkedAt: after ? 1 : -1 })
|
||||
.limit(limit)
|
||||
.lean<DockerLogDocument[]>();
|
||||
return docs.map(this.toEntity);
|
||||
const entities = docs.map(this.toEntity);
|
||||
return after ? entities.reverse() : entities;
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string) =>
|
||||
|
||||
@@ -80,6 +80,7 @@ export interface IMonitorService {
|
||||
monitorId: string;
|
||||
containerName: string;
|
||||
before?: Date;
|
||||
after?: Date;
|
||||
limit: number;
|
||||
}): Promise<DockerContainerLogsResult>;
|
||||
getGeoChecksByMonitorId(args: {
|
||||
@@ -403,12 +404,14 @@ export class MonitorService implements IMonitorService {
|
||||
monitorId,
|
||||
containerName,
|
||||
before,
|
||||
after,
|
||||
limit,
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
containerName: string;
|
||||
before?: Date;
|
||||
after?: Date;
|
||||
limit: number;
|
||||
}): Promise<DockerContainerLogsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
@@ -426,8 +429,9 @@ export class MonitorService implements IMonitorService {
|
||||
});
|
||||
}
|
||||
|
||||
const logs = await this.dockerLogsRepository.findByContainerName({ monitorId, containerName, before, limit });
|
||||
return { logs, nextCursor: logs.length === limit ? (logs[logs.length - 1]?.checkedAt ?? null) : null };
|
||||
const logs = await this.dockerLogsRepository.findByContainerName({ monitorId, containerName, before, after, limit });
|
||||
const nextCursor = !after && logs.length === limit ? (logs[logs.length - 1]?.checkedAt ?? null) : null;
|
||||
return { logs, nextCursor };
|
||||
};
|
||||
|
||||
getGeoChecksByMonitorId = async ({
|
||||
|
||||
@@ -656,6 +656,28 @@ describe("MonitorService", () => {
|
||||
|
||||
expect(result.nextCursor).toBeNull();
|
||||
});
|
||||
|
||||
it("passes after through and returns a null cursor even for a full page", async () => {
|
||||
const monitorsRepository = createMonitorsRepositoryMock();
|
||||
const dockerLogsRepository = createDockerLogsRepositoryMock();
|
||||
const after = new Date("2026-01-01T00:00:00.000Z");
|
||||
const logs = [{ checkedAt: "2026-01-01T02:00:00.000Z" }, { checkedAt: "2026-01-01T01:00:00.000Z" }];
|
||||
(monitorsRepository.findById as jest.Mock).mockResolvedValue(makeMonitor({ type: "docker" }));
|
||||
(dockerLogsRepository.findByContainerName as jest.Mock).mockResolvedValue(logs);
|
||||
const { service } = createService({ monitorsRepository, dockerLogsRepository });
|
||||
|
||||
const result = await service.getDockerContainerLogs({
|
||||
teamId: TEAM_ID,
|
||||
monitorId: MONITOR_ID,
|
||||
containerName: "web",
|
||||
after,
|
||||
limit: 2,
|
||||
});
|
||||
|
||||
expect(dockerLogsRepository.findByContainerName).toHaveBeenCalledWith({ monitorId: MONITOR_ID, containerName: "web", after, limit: 2 });
|
||||
expect(result.logs).toBe(logs);
|
||||
expect(result.nextCursor).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPageSpeedDetailsById", () => {
|
||||
|
||||
@@ -18,6 +18,14 @@ describe("getDockerContainerLogsQueryValidation", () => {
|
||||
it("rejects a non-ISO before cursor", () => {
|
||||
expect(() => getDockerContainerLogsQueryValidation.parse({ before: "yesterday" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects a non-ISO after cursor", () => {
|
||||
expect(() => getDockerContainerLogsQueryValidation.parse({ after: "yesterday" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects before and after together", () => {
|
||||
expect(() => getDockerContainerLogsQueryValidation.parse({ before: "2026-01-02T00:00:00.000Z", after: "2026-01-01T00:00:00.000Z" })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
const baseDnsBody = {
|
||||
|
||||
Reference in new issue
Block a user