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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"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) {
|
func SysLog(s string) {
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
|
LogWriterMu.RLock()
|
||||||
_, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
|
_, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
|
||||||
|
LogWriterMu.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SysError(s string) {
|
func SysError(s string) {
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
|
LogWriterMu.RLock()
|
||||||
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
|
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
|
||||||
|
LogWriterMu.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func FatalLog(v ...any) {
|
func FatalLog(v ...any) {
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
|
LogWriterMu.RLock()
|
||||||
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v)
|
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v)
|
||||||
|
LogWriterMu.RUnlock()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogStartupSuccess(startTime time.Time, port string) {
|
func LogStartupSuccess(startTime time.Time, port string) {
|
||||||
|
|
||||||
duration := time.Since(startTime)
|
duration := time.Since(startTime)
|
||||||
durationMs := duration.Milliseconds()
|
durationMs := duration.Milliseconds()
|
||||||
|
|
||||||
// Get network IPs
|
// Get network IPs
|
||||||
networkIps := GetNetworkIps()
|
networkIps := GetNetworkIps()
|
||||||
|
|
||||||
// Print blank line for spacing
|
LogWriterMu.RLock()
|
||||||
fmt.Fprintf(gin.DefaultWriter, "\n")
|
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, " \033[32m%s %s\033[0m ready in %d ms\n", SystemName, Version, durationMs)
|
||||||
fmt.Fprintf(gin.DefaultWriter, "\n")
|
fmt.Fprintf(gin.DefaultWriter, "\n")
|
||||||
|
|
||||||
// Skip fancy startup message in container environments
|
|
||||||
if !IsRunningInContainer() {
|
if !IsRunningInContainer() {
|
||||||
// Print local URL
|
|
||||||
fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mLocal:\033[0m http://localhost:%s/\n", port)
|
fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mLocal:\033[0m http://localhost:%s/\n", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print network URLs
|
|
||||||
for _, ip := range networkIps {
|
for _, ip := range networkIps {
|
||||||
fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mNetwork:\033[0m http://%s:%s/\n", ip, port)
|
fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mNetwork:\033[0m http://%s:%s/\n", ip, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print blank line for spacing
|
|
||||||
fmt.Fprintf(gin.DefaultWriter, "\n")
|
fmt.Fprintf(gin.DefaultWriter, "\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -329,14 +330,25 @@ func CleanupLogFiles(c *gin.Context) {
|
|||||||
freedBytes += f.Size
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "",
|
"message": "",
|
||||||
"data": gin.H{
|
"data": result,
|
||||||
"deleted_count": deletedCount,
|
|
||||||
"freed_bytes": freedBytes,
|
|
||||||
"failed_files": failedFiles,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,12 +61,15 @@ func SetupLogger() {
|
|||||||
oldFile := currentLogFile
|
oldFile := currentLogFile
|
||||||
currentLogPath = logPath
|
currentLogPath = logPath
|
||||||
currentLogFile = fd
|
currentLogFile = fd
|
||||||
|
currentLogPathMu.Unlock()
|
||||||
|
|
||||||
|
common.LogWriterMu.Lock()
|
||||||
gin.DefaultWriter = io.MultiWriter(os.Stdout, fd)
|
gin.DefaultWriter = io.MultiWriter(os.Stdout, fd)
|
||||||
gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd)
|
gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd)
|
||||||
currentLogPathMu.Unlock()
|
|
||||||
if oldFile != nil {
|
if oldFile != nil {
|
||||||
_ = oldFile.Close()
|
_ = 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) {
|
func logHelper(ctx context.Context, level string, msg string) {
|
||||||
writer := gin.DefaultErrorWriter
|
|
||||||
if level == loggerINFO {
|
|
||||||
writer = gin.DefaultWriter
|
|
||||||
}
|
|
||||||
id := ctx.Value(common.RequestIdKey)
|
id := ctx.Value(common.RequestIdKey)
|
||||||
if id == nil {
|
if id == nil {
|
||||||
id = "SYSTEM"
|
id = "SYSTEM"
|
||||||
}
|
}
|
||||||
now := time.Now()
|
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)
|
_, _ = 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
|
logCount++ // we don't need accurate count, so no lock here
|
||||||
if logCount > maxLogCount && !setupLogWorking {
|
if logCount > maxLogCount && !setupLogWorking {
|
||||||
logCount = 0
|
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 new deployment name",
|
||||||
"请输入显示名称": "Please enter display 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.",
|
"请输入有效的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 number",
|
||||||
"请输入有效的镜像地址": "Please enter a valid image address",
|
"请输入有效的镜像地址": "Please enter a valid image address",
|
||||||
"请输入标签名称": "Please enter the tag name",
|
"请输入标签名称": "Please enter the tag name",
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ export default function SettingsPerformance(props) {
|
|||||||
)}
|
)}
|
||||||
style={{ marginBottom: 16 }}
|
style={{ marginBottom: 16 }}
|
||||||
/>
|
/>
|
||||||
{logInfo && logInfo.enabled ? (
|
{logInfo === null ? null : logInfo.enabled ? (
|
||||||
<>
|
<>
|
||||||
<Descriptions
|
<Descriptions
|
||||||
data={[
|
data={[
|
||||||
|
|||||||
Reference in New Issue
Block a user