Logs
This commit is contained in:
+19
-127
@@ -1,143 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { DashboardLayout } from "@/components/dashboard/DashboardLayout";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Pause, Play, Download, Filter, RefreshCw } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const logEntries = [
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:45.123",
|
||||
level: "info",
|
||||
source: "api-gateway-1",
|
||||
message: "Request received: GET /api/v1/users",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:45.145",
|
||||
level: "info",
|
||||
source: "api-gateway-1",
|
||||
message: "Response sent: 200 OK (22ms)",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:46.012",
|
||||
level: "info",
|
||||
source: "auth-service-1",
|
||||
message: "Token validation successful for user_id=12345",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:46.234",
|
||||
level: "warn",
|
||||
source: "worker-queue-2",
|
||||
message: "Job queue depth exceeded threshold: 150 pending jobs",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:47.001",
|
||||
level: "info",
|
||||
source: "postgres-db-1",
|
||||
message: "Query executed: SELECT * FROM users WHERE id = $1 (2ms)",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:47.456",
|
||||
level: "info",
|
||||
source: "api-gateway-2",
|
||||
message: "Request received: POST /api/v1/orders",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:47.789",
|
||||
level: "error",
|
||||
source: "metrics-collector-1",
|
||||
message: "Failed to connect to metrics endpoint: connection refused",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:48.012",
|
||||
level: "error",
|
||||
source: "metrics-collector-1",
|
||||
message: "Retry attempt 1/3 failed",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:48.234",
|
||||
level: "info",
|
||||
source: "redis-cache-1",
|
||||
message: "Cache hit: session:user:12345",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:49.001",
|
||||
level: "info",
|
||||
source: "worker-queue-1",
|
||||
message: "Job completed: send_email_notification (job_id=abc123)",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:49.345",
|
||||
level: "warn",
|
||||
source: "api-gateway-3",
|
||||
message: "High latency detected: upstream response took 850ms",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:50.012",
|
||||
level: "info",
|
||||
source: "auth-service-2",
|
||||
message: "New session created for user_id=67890",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:50.456",
|
||||
level: "error",
|
||||
source: "metrics-collector-1",
|
||||
message: "Retry attempt 2/3 failed",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:51.001",
|
||||
level: "info",
|
||||
source: "api-gateway-1",
|
||||
message: "Request received: GET /api/v1/health",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:51.023",
|
||||
level: "info",
|
||||
source: "api-gateway-1",
|
||||
message: "Response sent: 200 OK (2ms)",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:52.012",
|
||||
level: "info",
|
||||
source: "worker-queue-3",
|
||||
message: "Processing job: generate_report (job_id=def456)",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:52.234",
|
||||
level: "error",
|
||||
source: "metrics-collector-1",
|
||||
message: "Retry attempt 3/3 failed. Giving up.",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:52.456",
|
||||
level: "error",
|
||||
source: "metrics-collector-1",
|
||||
message: "Container entering unhealthy state",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:53.012",
|
||||
level: "info",
|
||||
source: "postgres-db-1",
|
||||
message: "Connection pool: 12/50 active connections",
|
||||
},
|
||||
{
|
||||
timestamp: "2024-01-15 14:32:54.001",
|
||||
level: "info",
|
||||
source: "api-gateway-2",
|
||||
message: "Request received: PUT /api/v1/users/12345",
|
||||
},
|
||||
];
|
||||
import { Log } from "@/lib/types";
|
||||
import { getAllContainerLogs } from "@/core/docker";
|
||||
|
||||
export default function LogsPage() {
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [filter, setFilter] = useState<"all" | "info" | "warn" | "error">(
|
||||
"all"
|
||||
);
|
||||
const [logs, setLogs] = useState<Log[]>([]);
|
||||
|
||||
const filteredLogs = logEntries.filter((log) =>
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
setLogs(await getAllContainerLogs());
|
||||
};
|
||||
|
||||
const iid = setInterval(fetch, 1000);
|
||||
|
||||
fetch();
|
||||
|
||||
return () => clearInterval(iid);
|
||||
}, []);
|
||||
|
||||
const filteredLogs = logs.filter((log) =>
|
||||
filter === "all" ? true : log.level === filter
|
||||
);
|
||||
|
||||
@@ -182,7 +74,7 @@ export default function LogsPage() {
|
||||
onClick={() => setFilter("warn")}
|
||||
className="gap-1"
|
||||
>
|
||||
<span className="w-2 h-2 rounded-full bg-warning" />
|
||||
<span className="w-2 h-2 rounded-full bg-yellow-300" />
|
||||
Warn
|
||||
</Button>
|
||||
<Button
|
||||
@@ -256,7 +148,7 @@ export default function LogsPage() {
|
||||
>
|
||||
{/* Timestamp */}
|
||||
<span className="text-gray-400 whitespace-nowrap md:mr-3">
|
||||
{log.timestamp}
|
||||
{log.timestamp.replace("T", " ").slice(0, 19)}
|
||||
</span>
|
||||
|
||||
{/* Level */}
|
||||
|
||||
+23
-1
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import axios from "axios";
|
||||
import { Docker, Node, SimpleStats } from "@/lib/types";
|
||||
import { Docker, Log, Node, SimpleStats } from "@/lib/types";
|
||||
import { verifySession } from "./auth/session";
|
||||
import { getNodes } from "./node";
|
||||
|
||||
@@ -86,3 +86,25 @@ export async function removeContainer(containerId: string) {
|
||||
).data
|
||||
);
|
||||
}
|
||||
|
||||
export async function getAllContainerLogs(): Promise<Log[]> {
|
||||
const nodes = await getNodes();
|
||||
|
||||
return (
|
||||
await Promise.all(
|
||||
nodes.map(
|
||||
async (n) =>
|
||||
await (
|
||||
await axios.get(`http://${n.ip}:3000/api/logs`, {
|
||||
headers: { Authorization: `ApiKey ${n.key}` },
|
||||
})
|
||||
).data
|
||||
)
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { CPUusage } from "@/lib/server/calc";
|
||||
import { SimpleStats } from "@/lib/types";
|
||||
import { Log, SimpleStats } from "@/lib/types";
|
||||
import Docker from "dockerode";
|
||||
|
||||
const docker = new Docker();
|
||||
@@ -34,3 +34,71 @@ export async function stopContainer(containerId: string) {
|
||||
export async function removeContainer(containerId: string) {
|
||||
await docker.getContainer(containerId).remove();
|
||||
}
|
||||
|
||||
function inferLevel(message: string): "info" | "warn" | "error" {
|
||||
const msg = message.toLowerCase();
|
||||
|
||||
if (msg.includes("error") || msg.includes("fatal")) return "error";
|
||||
if (msg.includes("warn")) return "warn";
|
||||
|
||||
return "info";
|
||||
}
|
||||
|
||||
function demuxLogs(buffer: Buffer): string {
|
||||
let offset = 0;
|
||||
let output = "";
|
||||
|
||||
while (offset < buffer.length) {
|
||||
const streamType = buffer[offset];
|
||||
const length = buffer.readUInt32BE(offset + 4);
|
||||
offset += 8;
|
||||
|
||||
output += buffer.slice(offset, offset + length).toString("utf8");
|
||||
offset += length;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
async function getContainerLogs(
|
||||
containerId: string,
|
||||
name: string
|
||||
): Promise<Log[]> {
|
||||
const container = docker.getContainer(containerId);
|
||||
|
||||
const raw = await container.logs({
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
const text = demuxLogs(raw);
|
||||
|
||||
return text
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((line) => {
|
||||
const firstSpace = line.indexOf(" ");
|
||||
const timestamp = line.slice(0, firstSpace);
|
||||
const message = line.slice(firstSpace + 1);
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
level: inferLevel(message),
|
||||
source: name,
|
||||
message,
|
||||
} satisfies Log;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllContainerLogs(): Promise<Log[]> {
|
||||
const containers = await docker.listContainers({ all: true });
|
||||
|
||||
const logs = await Promise.all(
|
||||
containers.map((c) => getContainerLogs(c.Id, c.Names[0].replace("/", "")))
|
||||
);
|
||||
|
||||
return logs
|
||||
.flat()
|
||||
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
||||
}
|
||||
|
||||
@@ -74,3 +74,10 @@ export interface SimpleStats {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
timestamp: string;
|
||||
level: "info" | "warn" | "error";
|
||||
source: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user