wails/v3/pkg/application/logger_ios.go
Lea Anthony 4dce80d887
fix(v3): revert goccy/go-json to stdlib encoding/json to fix Windows panic (#4859)
* fix(v3): warm up dialog types in go-json cache to prevent Windows panic

Add FileFilter, OpenFileDialogOptions, SaveFileDialogOptions, and
MessageDialogOptions to the init() warmup to prevent index out of bounds
panic on Windows when these types are first unmarshaled.

Fixes goccy/go-json#474 for Wails internal dialog types.

* fix(v3): revert goccy/go-json to stdlib encoding/json to fix Windows panic

goccy/go-json has a type address calculation bug on Windows that causes
index out of bounds panic when decoding user-defined types for the first time.

This reverts all runtime usages of goccy/go-json back to stdlib encoding/json.
Test and benchmark files are left unchanged.

Partially reverts PR #4843.
2026-01-05 08:26:35 +11:00

128 lines
2.7 KiB
Go

//go:build ios
package application
/*
#cgo CFLAGS: -x objective-c -fobjc-arc
#cgo LDFLAGS: -framework Foundation -framework UIKit -framework WebKit
#include "webview_window_ios.h"
*/
import "C"
import (
"bytes"
"context"
"log/slog"
"strings"
"time"
"unsafe"
"encoding/json"
)
// iosConsoleHandler implements slog.Handler and forwards records to the WKWebView console.
type iosConsoleHandler struct {
level slog.Leveler
attrs []slog.Attr
group string
}
func (h *iosConsoleHandler) Enabled(_ context.Context, lvl slog.Level) bool {
if h.level == nil {
return true
}
return lvl >= h.level.Level()
}
func (h *iosConsoleHandler) Handle(_ context.Context, r slog.Record) error {
// Build a compact log line
var b bytes.Buffer
b.WriteString(r.Time.Format(time.Kitchen))
b.WriteString(" ")
if h.group != "" {
b.WriteString("[")
b.WriteString(h.group)
b.WriteString("] ")
}
b.WriteString(r.Message)
writeAttr := func(a slog.Attr) {
// Resolve attr values
a.Value = a.Value.Resolve()
b.WriteString(" ")
b.WriteString(a.Key)
b.WriteString("=")
// Use JSON for complex values
switch a.Value.Kind() {
case slog.KindString:
b.WriteString(strconvQuote(a.Value.String()))
default:
js, _ := json.Marshal(a.Value.Any())
b.Write(js)
}
}
for _, a := range h.attrs {
writeAttr(a)
}
r.Attrs(func(a slog.Attr) bool { writeAttr(a); return true })
lvl := levelToConsole(r.Level)
msg := b.String()
cmsg := C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
clvl := C.CString(lvl)
defer C.free(unsafe.Pointer(clvl))
C.ios_console_log(clvl, cmsg)
return nil
}
func (h *iosConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
clone := *h
clone.attrs = append(append([]slog.Attr{}, h.attrs...), attrs...)
return &clone
}
func (h *iosConsoleHandler) WithGroup(name string) slog.Handler {
clone := *h
if clone.group == "" {
clone.group = name
} else if name != "" {
clone.group = clone.group + "." + name
}
return &clone
}
func levelToConsole(l slog.Level) string {
switch {
case l <= slog.LevelDebug:
return "debug"
case l <= slog.LevelInfo:
return "info"
case l <= slog.LevelWarn:
return "warn"
default:
return "error"
}
}
// strconvQuote quotes a string for compact key=value output (no surrounding quotes if no spaces).
func strconvQuote(s string) string {
if strings.IndexFunc(s, func(r rune) bool { return r == ' ' || r == '\n' || r == '\t' }) >= 0 {
bs, _ := json.Marshal(s)
return string(bs)
}
return s
}
// DefaultLogger for iOS forwards all logs to the browser console.
func DefaultLogger(level slog.Leveler) *slog.Logger {
// Ensure there's always a leveler
if level == nil {
var lv slog.LevelVar
lv.Set(slog.LevelInfo)
level = &lv
}
return slog.New(&iosConsoleHandler{level: level})
}