mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-26 01:25:22 +02:00
refactor: rename builders to server
This commit is contained in:
117
packages/server/src/utils/access-log/handler.ts
Normal file
117
packages/server/src/utils/access-log/handler.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { IS_CLOUD, paths } from "@/server/constants";
|
||||
import { findAdmin, updateAdmin } from "@/server/services/admin";
|
||||
import { type RotatingFileStream, createStream } from "rotating-file-stream";
|
||||
import { execAsync } from "../process/execAsync";
|
||||
|
||||
class LogRotationManager {
|
||||
private static instance: LogRotationManager;
|
||||
private stream: RotatingFileStream | null = null;
|
||||
|
||||
private constructor() {
|
||||
if (IS_CLOUD) {
|
||||
return;
|
||||
}
|
||||
this.initialize().catch(console.error);
|
||||
}
|
||||
|
||||
public static getInstance(): LogRotationManager {
|
||||
if (!LogRotationManager.instance) {
|
||||
LogRotationManager.instance = new LogRotationManager();
|
||||
}
|
||||
return LogRotationManager.instance;
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
const isActive = await this.getStateFromDB();
|
||||
if (isActive) {
|
||||
await this.activateStream();
|
||||
}
|
||||
}
|
||||
|
||||
private async getStateFromDB(): Promise<boolean> {
|
||||
const setting = await findAdmin();
|
||||
return setting?.enableLogRotation ?? false;
|
||||
}
|
||||
|
||||
private async setStateInDB(active: boolean): Promise<void> {
|
||||
const admin = await findAdmin();
|
||||
await updateAdmin(admin.authId, {
|
||||
enableLogRotation: active,
|
||||
});
|
||||
}
|
||||
|
||||
private async activateStream(): Promise<void> {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths();
|
||||
if (this.stream) {
|
||||
await this.deactivateStream();
|
||||
}
|
||||
|
||||
this.stream = createStream("access.log", {
|
||||
size: "100M",
|
||||
interval: "1d",
|
||||
path: DYNAMIC_TRAEFIK_PATH,
|
||||
rotate: 6,
|
||||
compress: "gzip",
|
||||
});
|
||||
|
||||
this.stream.on("rotation", this.handleRotation.bind(this));
|
||||
}
|
||||
|
||||
private async deactivateStream(): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (this.stream) {
|
||||
this.stream.end(() => {
|
||||
this.stream = null;
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async activate(): Promise<boolean> {
|
||||
const currentState = await this.getStateFromDB();
|
||||
if (currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.setStateInDB(true);
|
||||
await this.activateStream();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async deactivate(): Promise<boolean> {
|
||||
console.log("Deactivating log rotation...");
|
||||
const currentState = await this.getStateFromDB();
|
||||
if (!currentState) {
|
||||
console.log("Log rotation is already inactive in DB");
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.setStateInDB(false);
|
||||
await this.deactivateStream();
|
||||
console.log("Log rotation deactivated successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
private async handleRotation() {
|
||||
try {
|
||||
const status = await this.getStatus();
|
||||
if (!status) {
|
||||
await this.deactivateStream();
|
||||
}
|
||||
await execAsync(
|
||||
"docker kill -s USR1 $(docker ps -q --filter name=dokploy-traefik)",
|
||||
);
|
||||
console.log("USR1 Signal send to Traefik");
|
||||
} catch (error) {
|
||||
console.error("Error to send USR1 Signal to Traefik:", error);
|
||||
}
|
||||
}
|
||||
public async getStatus(): Promise<boolean> {
|
||||
const dbState = await this.getStateFromDB();
|
||||
return dbState;
|
||||
}
|
||||
}
|
||||
export const logRotationManager = LogRotationManager.getInstance();
|
||||
48
packages/server/src/utils/access-log/types.ts
Normal file
48
packages/server/src/utils/access-log/types.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export interface LogEntry {
|
||||
ClientAddr: string;
|
||||
ClientHost: string;
|
||||
ClientPort: string;
|
||||
ClientUsername: string;
|
||||
DownstreamContentSize: number;
|
||||
DownstreamStatus: number;
|
||||
Duration: number;
|
||||
OriginContentSize: number;
|
||||
OriginDuration: number;
|
||||
OriginStatus: number;
|
||||
Overhead: number;
|
||||
RequestAddr: string;
|
||||
RequestContentSize: number;
|
||||
RequestCount: number;
|
||||
RequestHost: string;
|
||||
RequestMethod: string;
|
||||
RequestPath: string;
|
||||
RequestPort: string;
|
||||
RequestProtocol: string;
|
||||
RequestScheme: string;
|
||||
RetryAttempts: number;
|
||||
RouterName: string;
|
||||
ServiceAddr: string;
|
||||
ServiceName: string;
|
||||
ServiceURL: {
|
||||
Scheme: string;
|
||||
Opaque: string;
|
||||
User: null;
|
||||
Host: string;
|
||||
Path: string;
|
||||
RawPath: string;
|
||||
ForceQuery: boolean;
|
||||
RawQuery: string;
|
||||
Fragment: string;
|
||||
RawFragment: string;
|
||||
};
|
||||
StartLocal: string;
|
||||
StartUTC: string;
|
||||
downstream_Content_Type: string;
|
||||
entryPointName: string;
|
||||
level: string;
|
||||
msg: string;
|
||||
origin_Content_Type: string;
|
||||
request_Content_Type: string;
|
||||
request_User_Agent: string;
|
||||
time: string;
|
||||
}
|
||||
119
packages/server/src/utils/access-log/utils.ts
Normal file
119
packages/server/src/utils/access-log/utils.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import _ from "lodash";
|
||||
import type { LogEntry } from "./types";
|
||||
|
||||
interface HourlyData {
|
||||
hour: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export function processLogs(logString: string): HourlyData[] {
|
||||
if (_.isEmpty(logString)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const hourlyData = _(logString)
|
||||
.split("\n")
|
||||
.compact()
|
||||
.map((entry) => {
|
||||
try {
|
||||
const log: LogEntry = JSON.parse(entry);
|
||||
if (log.ServiceName === "dokploy-service-app@file") {
|
||||
return null;
|
||||
}
|
||||
const date = new Date(log.StartUTC);
|
||||
return `${date.toISOString().slice(0, 13)}:00:00Z`;
|
||||
} catch (error) {
|
||||
console.error("Error parsing log entry:", error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.compact()
|
||||
.countBy()
|
||||
.map((count, hour) => ({ hour, count }))
|
||||
.value();
|
||||
|
||||
return _.sortBy(hourlyData, (entry) => new Date(entry.hour).getTime());
|
||||
}
|
||||
|
||||
interface PageInfo {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
interface SortInfo {
|
||||
id: string;
|
||||
desc: boolean;
|
||||
}
|
||||
|
||||
export function parseRawConfig(
|
||||
rawConfig: string,
|
||||
page?: PageInfo,
|
||||
sort?: SortInfo,
|
||||
search?: string,
|
||||
status?: string[],
|
||||
): { data: LogEntry[]; totalCount: number } {
|
||||
try {
|
||||
if (_.isEmpty(rawConfig)) {
|
||||
return { data: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
let parsedLogs = _(rawConfig)
|
||||
.split("\n")
|
||||
.compact()
|
||||
.map((line) => JSON.parse(line) as LogEntry)
|
||||
.value();
|
||||
|
||||
parsedLogs = parsedLogs.filter(
|
||||
(log) => log.ServiceName !== "dokploy-service-app@file",
|
||||
);
|
||||
|
||||
if (search) {
|
||||
parsedLogs = parsedLogs.filter((log) =>
|
||||
log.RequestPath.toLowerCase().includes(search.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
if (status && status.length > 0) {
|
||||
parsedLogs = parsedLogs.filter((log) =>
|
||||
status.some((range) => isStatusInRange(log.DownstreamStatus, range)),
|
||||
);
|
||||
}
|
||||
const totalCount = parsedLogs.length;
|
||||
|
||||
if (sort) {
|
||||
parsedLogs = _.orderBy(
|
||||
parsedLogs,
|
||||
[sort.id],
|
||||
[sort.desc ? "desc" : "asc"],
|
||||
);
|
||||
} else {
|
||||
parsedLogs = _.orderBy(parsedLogs, ["time"], ["desc"]);
|
||||
}
|
||||
|
||||
if (page) {
|
||||
const startIndex = page.pageIndex * page.pageSize;
|
||||
parsedLogs = parsedLogs.slice(startIndex, startIndex + page.pageSize);
|
||||
}
|
||||
|
||||
return { data: parsedLogs, totalCount };
|
||||
} catch (error) {
|
||||
console.error("Error parsing rawConfig:", error);
|
||||
throw new Error("Failed to parse rawConfig");
|
||||
}
|
||||
}
|
||||
const isStatusInRange = (status: number, range: string) => {
|
||||
switch (range) {
|
||||
case "info":
|
||||
return status >= 100 && status <= 199;
|
||||
case "success":
|
||||
return status >= 200 && status <= 299;
|
||||
case "redirect":
|
||||
return status >= 300 && status <= 399;
|
||||
case "client":
|
||||
return status >= 400 && status <= 499;
|
||||
case "server":
|
||||
return status >= 500 && status <= 599;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user