The panic handling has been ported to v2. Here's a summary of the changes:

## Summary of Changes

**1. Created `v2/internal/frontend/desktop/linux/panic_handler.go`**
- Ported the panic recovery logic from v3
- Includes `getStackTrace()` function for generating readable stack traces
- `handlePanic()` function that recovers from panics and either calls the custom handler or logs the error

**2. Added to `v2/pkg/options/options.go`**
- Added `PanicDetails` struct with `StackTrace`, `Error`, `Time`, and `FullStackTrace` fields
- Added `PanicHandler` type: `func(*PanicDetails)`
- Added `PanicHandler` field to the `App` struct with documentation

**3. Modified `v2/internal/frontend/desktop/linux/frontend.go`**
- Added `defer handlePanic(f.frontendOptions.PanicHandler, f.logger)` to the goroutine in `processMessage()` (line 468)

## Usage Example

Users can now configure a custom panic handler in their v2 Wails application:

```go
app := wails.Run(&options.App{
    Title: "My App",
    // ... other options
    PanicHandler: func(details *options.PanicDetails) {
        // Custom panic handling logic
        log.Printf("Panic occurred at %v: %v\n%s",
            details.Time,
            details.Error,
            details.StackTrace)
        // Could show error dialog, send to error tracking service, etc.
    },
})
```

If no `PanicHandler` is set, panics will be logged via the application logger and the application will continue running instead of crashing with a signal handler error.
This commit is contained in:
Lea Anthony 2026-01-28 07:00:33 +11:00
commit ca206452cd
3 changed files with 121 additions and 0 deletions

View file

@ -501,6 +501,8 @@ func (f *Frontend) processMessage(message string) {
}
go func() {
defer handlePanic(f.frontendOptions.PanicHandler, f.logger)
result, err := f.dispatcher.ProcessMessage(message, f)
if err != nil {
f.logger.Error(err.Error())

View file

@ -0,0 +1,100 @@
//go:build linux
// +build linux
package linux
import (
"fmt"
"runtime"
"runtime/debug"
"strings"
"time"
"github.com/wailsapp/wails/v2/pkg/options"
)
func getStackTrace(skipStart int, skipEnd int) string {
// Get all program counters first
pc := make([]uintptr, 32)
n := runtime.Callers(skipStart+1, pc)
if n == 0 {
return ""
}
pc = pc[:n]
frames := runtime.CallersFrames(pc)
// Collect all frames first
var allFrames []runtime.Frame
for {
frame, more := frames.Next()
allFrames = append(allFrames, frame)
if !more {
break
}
}
// Remove frames from the end
if len(allFrames) > skipEnd {
allFrames = allFrames[:len(allFrames)-skipEnd]
}
// Build the output string
var builder strings.Builder
for _, frame := range allFrames {
fmt.Fprintf(&builder, "%s\n\tat %s:%d\n",
frame.Function, frame.File, frame.Line)
}
return builder.String()
}
type handlePanicOptions struct {
skipEnd int
}
func newPanicDetails(err error, trace string) *options.PanicDetails {
return &options.PanicDetails{
Error: err,
Time: time.Now(),
StackTrace: trace,
FullStackTrace: string(debug.Stack()),
}
}
// handlePanic recovers from panics and processes them through the configured handler.
// Returns true if a panic was recovered.
func handlePanic(handler options.PanicHandler, logger interface{ Error(string, ...interface{}) }, opts ...handlePanicOptions) bool {
// Try to recover
e := recover()
if e == nil {
return false
}
// Get the error
err, ok := e.(error)
if !ok {
err = fmt.Errorf("%v", e)
}
// Get the stack trace
var stackTrace string
skipEnd := 0
if len(opts) > 0 {
skipEnd = opts[0].skipEnd
}
stackTrace = getStackTrace(3, skipEnd)
panicDetails := newPanicDetails(err, stackTrace)
// Use custom handler if provided
if handler != nil {
handler(panicDetails)
return true
}
// Default behavior: log the panic
if logger != nil {
logger.Error("panic error: %v\n%s", panicDetails.Error, panicDetails.StackTrace)
}
return true
}

View file

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"runtime"
"time"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/linux"
@ -69,6 +70,12 @@ type App struct {
// ErrorFormatter overrides the formatting of errors returned by backend methods
ErrorFormatter ErrorFormatter
// PanicHandler is called when a panic occurs in a bound method.
// If not set, the panic will be logged and the application will continue.
// This is particularly useful on Linux where panics in cgo callbacks
// can cause signal handler issues.
PanicHandler PanicHandler
// CSS property to test for draggable elements. Default "--wails-draggable"
CSSDragProperty string
@ -108,6 +115,18 @@ type App struct {
type ErrorFormatter func(error) any
// PanicDetails contains information about a panic that occurred in a bound method
type PanicDetails struct {
StackTrace string
Error error
Time time.Time
FullStackTrace string
}
// PanicHandler is a function that handles panics in bound methods.
// If not set, panics will be logged to the application logger.
type PanicHandler func(*PanicDetails)
type RGBA struct {
R uint8 `json:"r"`
G uint8 `json:"g"`