Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/infrastructure/logging.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ The control plane exposes logs at `https://logs.<ROOT_DOMAIN>` with basic auth.
## Accessing Logs

Logs are accessible from the web UI for each service, deployment, build, and server. The control plane queries Victoria Logs using LogSQL with filters for `service_id`, `deployment_id`, `server_id`, and time ranges.

Searches run against Victoria Logs rather than only the entries currently loaded in the browser. Continuous service, request, and server log views default to the last 24 hours and support 1-hour, 6-hour, 24-hour, and 7-day ranges. These query ranges do not change the separate `VL_RETENTION` storage setting.
16 changes: 13 additions & 3 deletions web/app/api/builds/[buildId]/logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";
import { invalidLogQueryResponse, normalizeLogSearch } from "@/lib/log-query";
import { isLoggingEnabled, queryLogsByBuild } from "@/lib/victoria-logs";

export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ buildId: string }> },
) {
const { buildId } = await params;
let search: string | undefined;
try {
search = normalizeLogSearch(request.nextUrl.searchParams.get("q"));
} catch (error) {
return invalidLogQueryResponse(error);
}

if (!isLoggingEnabled()) {
return NextResponse.json({ logs: [] });
}

try {
const { logs: rawLogs } = await queryLogsByBuild(buildId);
const { logs: rawLogs } = await queryLogsByBuild(buildId, { search });

const logs = rawLogs.map((log) => ({
timestamp: log._time,
Expand All @@ -22,6 +29,9 @@ export async function GET(
return NextResponse.json({ logs });
} catch (error) {
console.error("Failed to fetch build logs:", error);
return NextResponse.json({ logs: [] });
return NextResponse.json(
{ message: "Failed to query build logs" },
{ status: 502 },
);
}
}
25 changes: 18 additions & 7 deletions web/app/api/deployments/[id]/logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import {
invalidLogQueryResponse,
normalizeLogCursor,
parseLogLimit,
} from "@/lib/log-query";
import { isLoggingEnabled, queryLogsByDeployment } from "@/lib/victoria-logs";

export async function GET(
Expand All @@ -20,11 +25,14 @@ export async function GET(

const { id: deploymentId } = await params;
const url = new URL(request.url);
const limit = Math.min(
Number.parseInt(url.searchParams.get("limit") || "100", 10),
1000,
);
const after = url.searchParams.get("after") || undefined;
let limit: number;
let after: string | undefined;
try {
limit = parseLogLimit(url.searchParams.get("limit"), 100);
after = normalizeLogCursor(url.searchParams.get("after"));
} catch (error) {
return invalidLogQueryResponse(error);
}

try {
const result = await queryLogsByDeployment(deploymentId, limit, after);
Expand All @@ -43,6 +51,9 @@ export async function GET(
});
} catch (error) {
console.error("[logs:deployment] failed to query logs:", error);
return Response.json({ logs: [], hasMore: false });
return Response.json(
{ message: "Failed to query deployment logs" },
{ status: 502 },
);
}
}
16 changes: 13 additions & 3 deletions web/app/api/rollouts/[rolloutId]/logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";
import { invalidLogQueryResponse, normalizeLogSearch } from "@/lib/log-query";
import { isLoggingEnabled, queryLogsByRollout } from "@/lib/victoria-logs";

export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ rolloutId: string }> },
) {
const { rolloutId } = await params;
let search: string | undefined;
try {
search = normalizeLogSearch(request.nextUrl.searchParams.get("q"));
} catch (error) {
return invalidLogQueryResponse(error);
}

if (!isLoggingEnabled()) {
return NextResponse.json({ logs: [] });
}

try {
const { logs: rawLogs } = await queryLogsByRollout(rolloutId);
const { logs: rawLogs } = await queryLogsByRollout(rolloutId, { search });

const logs = rawLogs.map((log) => ({
timestamp: log._time,
Expand All @@ -23,6 +30,9 @@ export async function GET(
return NextResponse.json({ logs });
} catch (error) {
console.error("Failed to fetch rollout logs:", error);
return NextResponse.json({ logs: [] });
return NextResponse.json(
{ message: "Failed to query rollout logs" },
{ status: 502 },
);
}
}
24 changes: 16 additions & 8 deletions web/app/api/servers/[id]/logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { invalidLogQueryResponse, parseLogListParams } from "@/lib/log-query";
import { isLoggingEnabled, queryLogsByServer } from "@/lib/victoria-logs";

export async function GET(
Expand All @@ -20,14 +21,18 @@ export async function GET(

const { id: serverId } = await params;
const url = new URL(request.url);
const limit = Math.min(
Number.parseInt(url.searchParams.get("limit") || "500", 10),
1000,
);
const before = url.searchParams.get("before") || undefined;
let queryParams: ReturnType<typeof parseLogListParams>;
try {
queryParams = parseLogListParams(url.searchParams, 500);
} catch (error) {
return invalidLogQueryResponse(error);
}

try {
const result = await queryLogsByServer(serverId, limit, before);
const result = await queryLogsByServer({
serverId,
...queryParams,
});

const logs = result.logs.map((log, index) => ({
id: `${log.server_id}-${log._time}-${index}`,
Expand All @@ -42,6 +47,9 @@ export async function GET(
});
} catch (error) {
console.error("[logs:server] failed to query logs:", error);
return Response.json({ logs: [], hasMore: false });
return Response.json(
{ message: "Failed to query server logs" },
{ status: 502 },
);
}
}
24 changes: 14 additions & 10 deletions web/app/api/services/[id]/logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { invalidLogQueryResponse, parseLogListParams } from "@/lib/log-query";
import {
isLoggingEnabled,
queryLogsByService,
type LogType,
queryLogsByService,
} from "@/lib/victoria-logs";

export async function GET(
Expand All @@ -24,11 +25,12 @@ export async function GET(

const { id: serviceId } = await params;
const url = new URL(request.url);
const limit = Math.min(
Number.parseInt(url.searchParams.get("limit") || "100", 10),
1000,
);
const before = url.searchParams.get("before") || undefined;
let queryParams: ReturnType<typeof parseLogListParams>;
try {
queryParams = parseLogListParams(url.searchParams, 100);
} catch (error) {
return invalidLogQueryResponse(error);
}
const serverId = url.searchParams.get("serverId") || undefined;
const logTypeParam = url.searchParams.get("type");
const logType =
Expand All @@ -39,8 +41,7 @@ export async function GET(
try {
const result = await queryLogsByService({
serviceId,
limit,
before,
...queryParams,
logType,
serverId,
});
Expand All @@ -65,6 +66,9 @@ export async function GET(
});
} catch (error) {
console.error("[logs:service] failed to query logs:", error);
return Response.json({ logs: [], hasMore: false });
return Response.json(
{ message: "Failed to query service logs" },
{ status: 502 },
);
}
}
22 changes: 13 additions & 9 deletions web/app/api/services/[id]/requests/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { invalidLogQueryResponse, parseLogListParams } from "@/lib/log-query";
import { isLoggingEnabled, queryLogsByService } from "@/lib/victoria-logs";

export async function GET(
Expand All @@ -20,17 +21,17 @@ export async function GET(

const { id: serviceId } = await params;
const url = new URL(request.url);
const limit = Math.min(
Number.parseInt(url.searchParams.get("limit") || "100", 10),
1000,
);
const before = url.searchParams.get("before") || undefined;
let queryParams: ReturnType<typeof parseLogListParams>;
try {
queryParams = parseLogListParams(url.searchParams, 100);
} catch (error) {
return invalidLogQueryResponse(error);
}

try {
const result = await queryLogsByService({
serviceId,
limit,
before,
...queryParams,
logType: "http",
});

Expand All @@ -50,6 +51,9 @@ export async function GET(
});
} catch (error) {
console.error("[logs:requests] failed to query HTTP logs:", error);
return Response.json({ logs: [], hasMore: false });
return Response.json(
{ message: "Failed to query request logs" },
{ status: 502 },
);
}
}
8 changes: 7 additions & 1 deletion web/app/api/v1/manifest/logs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ export const dynamic = "force-dynamic";
import { z } from "zod";
import { requireRequestRole } from "@/lib/api-auth";
import { getManifestStatus } from "@/lib/cli-service";
import { isLogCursor } from "@/lib/log-query";
import { slugify } from "@/lib/utils";
import { isLoggingEnabled, queryLogsByService } from "@/lib/victoria-logs";

const querySchema = z.object({
project: z.string().trim().min(1),
environment: z.string().trim().min(1),
service: z.string().trim().min(1),
after: z.string().trim().min(1).optional(),
after: z
.string()
.trim()
.min(1)
.refine(isLogCursor, { message: "Invalid log cursor" })
.optional(),
tail: z.coerce.number().int().min(1).max(1000).default(100),
});

Expand Down
Loading
Loading