implement panic log for gateway and launcher

add file logger to gateway

ref issue: #1734

Signed-off-by: Cytown <cytown@gmail.com>
This commit is contained in:
Cytown
2026-03-23 15:40:17 +08:00
parent cff9065084
commit df17684dd4
10 changed files with 163 additions and 7 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ func NewGatewayCommand() *cobra.Command {
return nil
},
RunE: func(_ *cobra.Command, _ []string) error {
return gateway.Run(debug, internal.GetConfigPath(), allowEmpty)
return gateway.Run(debug, internal.GetPicoclawHome(), internal.GetConfigPath(), allowEmpty)
},
}
+17 -1
View File
@@ -47,6 +47,10 @@ const (
serviceShutdownTimeout = 30 * time.Second
providerReloadTimeout = 30 * time.Second
gracefulShutdownTimeout = 15 * time.Second
logPath = "logs"
panicFile = "gateway_panic.log"
logFile = "gateway.log"
)
type services struct {
@@ -79,7 +83,19 @@ func (p *startupBlockedProvider) GetDefaultModel() string {
}
// Run starts the gateway runtime using the configuration loaded from configPath.
func Run(debug bool, configPath string, allowEmptyStartup bool) error {
func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error {
panicPath := filepath.Join(homePath, logPath, panicFile)
panicFunc, err := logger.InitPanic(panicPath)
if err != nil {
return fmt.Errorf("error initializing panic log: %w", err)
}
defer panicFunc()
if err = logger.EnableFileLogging(filepath.Join(homePath, logPath, logFile)); err != nil {
panic(fmt.Sprintf("error enabling file logging: %v", err))
}
defer logger.DisableFileLogging()
cfg, err := config.LoadConfig(configPath)
if err != nil {
return fmt.Errorf("error loading config: %w", err)
+36
View File
@@ -0,0 +1,36 @@
package logger
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"time"
)
func InitPanic(filePath string) (func(), error) {
if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
return nil, fmt.Errorf("failed to create log directory: %w", err)
}
writer := initPanicFile(filePath)
if writer == nil {
return nil, fmt.Errorf("failed to create log file: %s", filePath)
}
return func() {
defer writer.Close()
if err := recover(); err != nil {
now := time.Now().Format("2006-01-02 15:04:05")
stack := debug.Stack()
logMsg := "\n\n====================\n[" + now + "] PANIC OCCURRED: " + fmt.Sprintf(
"%v",
err,
) + "\n" + string(
stack,
)
writer.Write([]byte(logMsg))
os.Exit(1)
}
}, nil
}
+21
View File
@@ -0,0 +1,21 @@
//go:build !windows
// +build !windows
package logger
import (
"fmt"
"io"
"os"
)
func initPanicFile(panicFile string) io.WriteCloser {
file, err := os.OpenFile(panicFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_SYNC, 0o600)
if err != nil {
panic(fmt.Sprintf("error in open panic: %v", err))
}
if err = Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
panic(fmt.Sprintf("error in syscall.Dup2: %v", err))
}
return file
}
+25
View File
@@ -0,0 +1,25 @@
//go:build windows
// +build windows
package logger
import (
"fmt"
"io"
"os"
"golang.org/x/sys/windows"
)
func initPanicFile(panicFile string) io.WriteCloser {
file, err := os.OpenFile(panicFile, os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0600)
if err != nil {
panic(fmt.Sprintf("error in open panic: %v", err))
}
err = windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(file.Fd()))
if err != nil {
panic(fmt.Sprintf("Failed to redirect stderr to file: %v", err))
}
os.Stderr = file
return file
}
+12
View File
@@ -0,0 +1,12 @@
//go:build linux && amd64
// +build linux,amd64
package logger
import (
"syscall"
)
func Dup2(oldfd int, newfd int) error {
return syscall.Dup2(oldfd, newfd)
}
+12
View File
@@ -0,0 +1,12 @@
//go:build linux && arm64
// +build linux,arm64
package logger
import (
"syscall"
)
func Dup2(oldfd int, newfd int) error {
return syscall.Dup3(oldfd, newfd, 0)
}
+12
View File
@@ -0,0 +1,12 @@
//go:build darwin
// +build darwin
package logger
import (
"syscall"
)
func Dup2(oldfd int, newfd int) error {
return syscall.Dup2(oldfd, newfd)
}
+12
View File
@@ -0,0 +1,12 @@
//go:build linux && loong64
// +build linux,loong64
package logger
import (
"syscall"
)
func Dup2(oldfd int, newfd int) error {
return syscall.Dup3(oldfd, newfd, 0)
}
+15 -5
View File
@@ -33,6 +33,10 @@ import (
const (
appName = "PicoClaw"
logPath = "logs"
panicFile = "launcher_panic.log"
logFile = "launcher.log"
)
var (
@@ -72,6 +76,14 @@ func main() {
// Initialize logger
picoHome := utils.GetPicoclawHome()
f := filepath.Join(picoHome, logPath, panicFile)
panicFunc, err := logger.InitPanic(f)
if err != nil {
panic(fmt.Sprintf("error initializing panic log: %v", err))
}
defer panicFunc()
// By default, detect terminal to decide console log behavior
// If -console-logs flag is explicitly set, it overrides the detection
enableConsole := *console
@@ -79,11 +91,9 @@ func main() {
// Disable console logging by setting level to Fatal (no output)
logger.SetConsoleLevel(logger.FATAL)
logPath := filepath.Join(picoHome, "logs", "web.log")
if err := logger.EnableFileLogging(logPath); err != nil {
// FIXME: https://github.com/sipeed/picoclaw/issues/1734
fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
os.Exit(1)
f := filepath.Join(picoHome, logPath, logFile)
if err = logger.EnableFileLogging(f); err != nil {
panic(fmt.Sprintf("error enabling file logging: %v", err))
}
defer logger.DisableFileLogging()
}