diff --git a/common/sys_log.go b/common/sys_log.go index b29adc3e..6e5b3622 100644 --- a/common/sys_log.go +++ b/common/sys_log.go @@ -3,53 +3,60 @@ package common import ( "fmt" "os" + "sync" "time" "github.com/gin-gonic/gin" ) +// LogWriterMu protects concurrent access to gin.DefaultWriter/gin.DefaultErrorWriter +// during log file rotation. Acquire RLock when reading/writing through the writers, +// acquire Lock when swapping writers and closing old files. +var LogWriterMu sync.RWMutex + func SysLog(s string) { t := time.Now() + LogWriterMu.RLock() _, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s) + LogWriterMu.RUnlock() } func SysError(s string) { t := time.Now() + LogWriterMu.RLock() _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s) + LogWriterMu.RUnlock() } func FatalLog(v ...any) { t := time.Now() + LogWriterMu.RLock() _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v) + LogWriterMu.RUnlock() os.Exit(1) } func LogStartupSuccess(startTime time.Time, port string) { - duration := time.Since(startTime) durationMs := duration.Milliseconds() // Get network IPs networkIps := GetNetworkIps() - // Print blank line for spacing - fmt.Fprintf(gin.DefaultWriter, "\n") + LogWriterMu.RLock() + defer LogWriterMu.RUnlock() - // Print the main success message + fmt.Fprintf(gin.DefaultWriter, "\n") fmt.Fprintf(gin.DefaultWriter, " \033[32m%s %s\033[0m ready in %d ms\n", SystemName, Version, durationMs) fmt.Fprintf(gin.DefaultWriter, "\n") - // Skip fancy startup message in container environments if !IsRunningInContainer() { - // Print local URL fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mLocal:\033[0m http://localhost:%s/\n", port) } - // Print network URLs for _, ip := range networkIps { fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mNetwork:\033[0m http://%s:%s/\n", ip, port) } - // Print blank line for spacing fmt.Fprintf(gin.DefaultWriter, "\n") } diff --git a/controller/performance.go b/controller/performance.go index c6c19e21..69324475 100644 --- a/controller/performance.go +++ b/controller/performance.go @@ -1,6 +1,7 @@ package controller import ( + "fmt" "net/http" "os" "path/filepath" @@ -329,14 +330,25 @@ func CleanupLogFiles(c *gin.Context) { freedBytes += f.Size } + result := gin.H{ + "deleted_count": deletedCount, + "freed_bytes": freedBytes, + "failed_files": failedFiles, + } + + if len(failedFiles) > 0 { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": fmt.Sprintf("部分文件删除失败(%d/%d)", len(failedFiles), len(toDelete)), + "data": result, + }) + return + } + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": gin.H{ - "deleted_count": deletedCount, - "freed_bytes": freedBytes, - "failed_files": failedFiles, - }, + "data": result, }) } diff --git a/logger/logger.go b/logger/logger.go index 178aa118..7b0c82de 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -61,12 +61,15 @@ func SetupLogger() { oldFile := currentLogFile currentLogPath = logPath currentLogFile = fd + currentLogPathMu.Unlock() + + common.LogWriterMu.Lock() gin.DefaultWriter = io.MultiWriter(os.Stdout, fd) gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd) - currentLogPathMu.Unlock() if oldFile != nil { _ = oldFile.Close() } + common.LogWriterMu.Unlock() } } @@ -92,16 +95,18 @@ func LogDebug(ctx context.Context, msg string, args ...any) { } func logHelper(ctx context.Context, level string, msg string) { - writer := gin.DefaultErrorWriter - if level == loggerINFO { - writer = gin.DefaultWriter - } id := ctx.Value(common.RequestIdKey) if id == nil { id = "SYSTEM" } now := time.Now() + common.LogWriterMu.RLock() + writer := gin.DefaultErrorWriter + if level == loggerINFO { + writer = gin.DefaultWriter + } _, _ = fmt.Fprintf(writer, "[%s] %v | %s | %s \n", level, now.Format("2006/01/02 - 15:04:05"), id, msg) + common.LogWriterMu.RUnlock() logCount++ // we don't need accurate count, so no lock here if logCount > maxLogCount && !setupLogWorking { logCount = 0 diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 97c8fdc1..1482fb25 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -2772,7 +2772,7 @@ "请输入新的部署名称": "Please enter new deployment name", "请输入显示名称": "Please enter display name", "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "Please enter a valid JSON format request body. You can refer to the default request body format in the preview panel.", - "请输入有效的数值": "Please enter a valid value", + "请输入有效的数值": "Please enter a valid number", "请输入有效的数字": "Please enter a valid number", "请输入有效的镜像地址": "Please enter a valid image address", "请输入标签名称": "Please enter the tag name", diff --git a/web/src/pages/Setting/Performance/SettingsPerformance.jsx b/web/src/pages/Setting/Performance/SettingsPerformance.jsx index 75cd1139..5084bd3c 100644 --- a/web/src/pages/Setting/Performance/SettingsPerformance.jsx +++ b/web/src/pages/Setting/Performance/SettingsPerformance.jsx @@ -408,7 +408,7 @@ export default function SettingsPerformance(props) { )} style={{ marginBottom: 16 }} /> - {logInfo && logInfo.enabled ? ( + {logInfo === null ? null : logInfo.enabled ? ( <>