import { IS_CLOUD, paths } from "@dokploy/server/constants";import { type RotatingFileStream, createStream } from "rotating-file-stream";import { execAsync } from "../process/execAsync";import { findAdmin } from "@dokploy/server/services/admin";import { updateUser } from "@dokploy/server/services/user"; 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 admin = await findAdmin(); return admin?.user.enableLogRotation ?? false; } private async setStateInDB(active: boolean): Promise<void> { const admin = await findAdmin(); if (!admin) { return; } await updateUser(admin.user.id, { 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 sending USR1 Signal to Traefik:", error); } } public async getStatus(): Promise<boolean> { const dbState = await this.getStateFromDB(); return dbState; }}export const logRotationManager = LogRotationManager.getInstance();
import { IS_CLOUD, paths } from "@dokploy/server/constants";
import { type RotatingFileStream, createStream } from "rotating-file-stream";
import { execAsync } from "../process/execAsync";
import { findAdmin } from "@dokploy/server/services/admin";
import { updateUser } from "@dokploy/server/services/user";
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 admin = await findAdmin();
return admin?.user.enableLogRotation ?? false;
}
private async setStateInDB(active: boolean): Promise<void> {
const admin = await findAdmin();
if (!admin) {
return;
}
await updateUser(admin.user.id, {
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 sending USR1 Signal to Traefik:", error);
}
}
public async getStatus(): Promise<boolean> {
const dbState = await this.getStateFromDB();
return dbState;
}
}
export const logRotationManager = LogRotationManager.getInstance();