fix: log management race condition, partial delete reporting, and UX issues
- Fix data race on gin.DefaultWriter during log rotation by adding LogWriterMu - Report partial failure when some log files fail to delete instead of always returning success - Fix misleading "logging disabled" banner shown before API responds - Fix en.json translation for numeric validation message
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
2
web/src/i18n/locales/en.json
vendored
2
web/src/i18n/locales/en.json
vendored
@@ -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",
|
||||
|
||||
@@ -408,7 +408,7 @@ export default function SettingsPerformance(props) {
|
||||
)}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
{logInfo && logInfo.enabled ? (
|
||||
{logInfo === null ? null : logInfo.enabled ? (
|
||||
<>
|
||||
<Descriptions
|
||||
data={[
|
||||
|
||||
Reference in New Issue
Block a user