* update cf worker hostname
* set remote_url cookie from param
* ephemeral backends v1
* nit
* Run queue server
* ntis
* timeout
* better db process management
* commit hash and worktree
* nit use map
* nit
* err handling
* Revert "err handling"
This reverts commit 19de00c0c0.
* nits
* auto cleanup
* Ephemeral backend command action
* remove checkout
* checkout ee repo
* nits
* process.env.GIT_EE_DEPLOY_KEY_FILE
* resumeURLs logic
* nit
* use windmill flow for ephemeral backend action
* fixes
* new token
* worktree pools
* Delete GH secret on cleanup
* linux deploy
* nit
* nit
* unhandled promises
* nit
* fix docker bridge IP on linux
* pass cf_frontend_url to wmill flow
* git fetch
* release worktree when binary started
* send error
* logger
* logging
* logging 2
* delete log files periodically
* redirect to raw app with logs
* CORS
* MANAGER_AUTH_TOKEN
* Check organization membership
* nit
* bwrap
* nit
* return timeoutAt in resumeUrl
* nit
* Change password
* nit remove https
132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
import {
|
|
appendFileSync,
|
|
mkdirSync,
|
|
unlinkSync,
|
|
readdirSync,
|
|
statSync,
|
|
} from "fs";
|
|
import path from "path";
|
|
|
|
const LOG_DIR = "/tmp/windmill-ephemeral-logs";
|
|
const LOG_RETENTION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
|
export class Logger {
|
|
private logFilePath: string;
|
|
private prefix: string;
|
|
|
|
constructor(identifier: string, prefix: string = "") {
|
|
// Sanitize identifier to prevent path traversal
|
|
const sanitizedId = identifier.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
|
|
// Ensure log directory exists
|
|
try {
|
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
} catch (error) {
|
|
// Directory might already exist
|
|
}
|
|
|
|
this.logFilePath = path.join(LOG_DIR, `${sanitizedId}.log`);
|
|
this.prefix = prefix;
|
|
|
|
// Write initial log entry
|
|
const startTime = new Date().toISOString();
|
|
this.log(`=== Log started at ${startTime} ===`);
|
|
}
|
|
|
|
private formatMessage(level: string, message: string): string {
|
|
const timestamp = new Date().toISOString();
|
|
const prefixStr = this.prefix ? `[${this.prefix}] ` : "";
|
|
return `${timestamp} ${level} ${prefixStr}${message}\n`;
|
|
}
|
|
|
|
log(message: string): void {
|
|
const formatted = this.formatMessage("stdin ", message);
|
|
try {
|
|
appendFileSync(this.logFilePath, formatted);
|
|
} catch (error) {
|
|
// Fallback to console if file write fails
|
|
console.error("Failed to write to log file:", error);
|
|
console.log(formatted);
|
|
}
|
|
// Also log to stdout for systemd journal
|
|
process.stdout.write(formatted);
|
|
}
|
|
|
|
error(message: string): void {
|
|
const formatted = this.formatMessage("stderr", message);
|
|
try {
|
|
appendFileSync(this.logFilePath, formatted);
|
|
} catch (error) {
|
|
console.error("Failed to write to log file:", error);
|
|
console.error(formatted);
|
|
}
|
|
// Also log to stderr for systemd journal
|
|
process.stderr.write(formatted);
|
|
}
|
|
|
|
warn(message: string): void {
|
|
const formatted = this.formatMessage("warn ", message);
|
|
try {
|
|
appendFileSync(this.logFilePath, formatted);
|
|
} catch (error) {
|
|
console.error("Failed to write to log file:", error);
|
|
console.warn(formatted);
|
|
}
|
|
process.stdout.write(formatted);
|
|
}
|
|
|
|
getLogFilePath(): string {
|
|
return this.logFilePath;
|
|
}
|
|
static getLogFilePathForCommit(commitHash: string): string {
|
|
// Sanitize commit hash to prevent path traversal
|
|
const sanitizedHash = commitHash.replace(/[^a-f0-9]/g, "");
|
|
if (sanitizedHash.length < 7 || sanitizedHash.length > 40) {
|
|
throw new Error("Invalid commit hash");
|
|
}
|
|
return path.join(LOG_DIR, `${sanitizedHash}.log`);
|
|
}
|
|
|
|
/**
|
|
* Clean up old log files (older than 24 hours)
|
|
*/
|
|
static cleanupOldLogs(): void {
|
|
try {
|
|
const files = readdirSync(LOG_DIR);
|
|
const now = Date.now();
|
|
let deletedCount = 0;
|
|
|
|
for (const file of files) {
|
|
if (!file.endsWith(".log")) continue;
|
|
|
|
const filePath = path.join(LOG_DIR, file);
|
|
try {
|
|
const stats = statSync(filePath);
|
|
const ageMs = now - stats.mtimeMs;
|
|
|
|
if (ageMs > LOG_RETENTION_MS) {
|
|
unlinkSync(filePath);
|
|
deletedCount++;
|
|
console.log(
|
|
`Deleted old log file: ${file} (age: ${Math.floor(
|
|
ageMs / 1000 / 60 / 60
|
|
)}h)`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
// Skip files we can't stat or delete
|
|
console.error(`Failed to process log file ${file}:`, error);
|
|
}
|
|
}
|
|
|
|
if (deletedCount > 0) {
|
|
console.log(
|
|
`Cleanup complete: deleted ${deletedCount} old log file(s)`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to cleanup old logs:", error);
|
|
}
|
|
}
|
|
}
|