feat(logging): add config option to capture GTK/WebKit logs
Closes #124
- commit
- 858295236ce7970634fcf3df12d3c3c02e93c888
- author
- brice · · 2026-01-23 04:48 UTC
- parent
- 7d32950
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +82 -0
M internal/bootstrap/webkit_stack.go +7 -0
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "github.com/bnema/dumber/internal/infrastructure/env" | |
| 12 | 12 | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| 13 | 13 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 14 | + | "github.com/bnema/dumber/internal/logging" | |
| 14 | 15 | "github.com/bnema/dumber/internal/ui/theme" | |
| 15 | 16 | "github.com/rs/zerolog" | |
| 16 | 17 | ) | |
| @@ -222,4 +223,10 @@ func configureRenderingEnvironment( | |||
| 222 | 223 | Str("gpu", string(gpuVendor)). | |
| 223 | 224 | Interface("vars", renderEnv.GetAppliedVars()). | |
| 224 | 225 | Msg("rendering environment configured") | |
| 226 | + | ||
| 227 | + | // Install GLib log handler if configured (captures GTK4/WebKitGTK6 messages) | |
| 228 | + | if cfg.Logging.CaptureGTKLogs { | |
| 229 | + | enableDebug := cfg.Logging.Level == "debug" || cfg.Logging.Level == "trace" | |
| 230 | + | logging.InstallGLibLogHandler(ctx, logger, enableDebug) | |
| 231 | + | } | |
| 225 | 232 | } | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "github.com/bnema/dumber/internal/infrastructure/env" | 11 | "github.com/bnema/dumber/internal/infrastructure/env" | ||
| 12 | "github.com/bnema/dumber/internal/infrastructure/filtering" | 12 | "github.com/bnema/dumber/internal/infrastructure/filtering" | ||
| 13 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 13 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 14 | + | "github.com/bnema/dumber/internal/logging" | |||
| 14 | "github.com/bnema/dumber/internal/ui/theme" | 15 | "github.com/bnema/dumber/internal/ui/theme" | ||
| 15 | "github.com/rs/zerolog" | 16 | "github.com/rs/zerolog" | ||
| 16 | ) | 17 | ) | ||
| @@ -222,4 +223,10 @@ func configureRenderingEnvironment( | |||||
| 222 | Str("gpu", string(gpuVendor)). | 223 | Str("gpu", string(gpuVendor)). | ||
| 223 | Interface("vars", renderEnv.GetAppliedVars()). | 224 | Interface("vars", renderEnv.GetAppliedVars()). | ||
| 224 | Msg("rendering environment configured") | 225 | Msg("rendering environment configured") | ||
| 226 | + | ||||
| 227 | + | // Install GLib log handler if configured (captures GTK4/WebKitGTK6 messages) | |||
| 228 | + | if cfg.Logging.CaptureGTKLogs { | |||
| 229 | + | enableDebug := cfg.Logging.Level == "debug" || cfg.Logging.Level == "trace" | |||
| 230 | + | logging.InstallGLibLogHandler(ctx, logger, enableDebug) | |||
| 231 | + | } | |||
| 225 | } | 232 | } | ||
M internal/infrastructure/config/defaults.go +1 -0
| @@ -110,6 +110,7 @@ func DefaultConfig() *Config { | |||
| 110 | 110 | LogDir: getDefaultLogDir(), | |
| 111 | 111 | EnableFileLog: true, | |
| 112 | 112 | CaptureConsole: false, // Disabled by default | |
| 113 | + | CaptureGTKLogs: false, // Disabled by default | |
| 113 | 114 | }, | |
| 114 | 115 | Appearance: AppearanceConfig{ | |
| 115 | 116 | SansFont: "Fira Sans", | |
| @@ -110,6 +110,7 @@ func DefaultConfig() *Config { | |||||
| 110 | LogDir: getDefaultLogDir(), | 110 | LogDir: getDefaultLogDir(), | ||
| 111 | EnableFileLog: true, | 111 | EnableFileLog: true, | ||
| 112 | CaptureConsole: false, // Disabled by default | 112 | CaptureConsole: false, // Disabled by default | ||
| 113 | + | CaptureGTKLogs: false, // Disabled by default | |||
| 113 | }, | 114 | }, | ||
| 114 | Appearance: AppearanceConfig{ | 115 | Appearance: AppearanceConfig{ | ||
| 115 | SansFont: "Fira Sans", | 116 | SansFont: "Fira Sans", | ||
M internal/infrastructure/config/loader.go +1 -0
| @@ -397,6 +397,7 @@ func (m *Manager) setLoggingDefaults(defaults *Config) { | |||
| 397 | 397 | m.viper.SetDefault("logging.log_dir", defaults.Logging.LogDir) | |
| 398 | 398 | m.viper.SetDefault("logging.enable_file_log", defaults.Logging.EnableFileLog) | |
| 399 | 399 | m.viper.SetDefault("logging.capture_console", defaults.Logging.CaptureConsole) | |
| 400 | + | m.viper.SetDefault("logging.capture_gtk_logs", defaults.Logging.CaptureGTKLogs) | |
| 400 | 401 | } | |
| 401 | 402 | ||
| 402 | 403 | func (m *Manager) setDebugDefaults(defaults *Config) { | |
| @@ -397,6 +397,7 @@ func (m *Manager) setLoggingDefaults(defaults *Config) { | |||||
| 397 | m.viper.SetDefault("logging.log_dir", defaults.Logging.LogDir) | 397 | m.viper.SetDefault("logging.log_dir", defaults.Logging.LogDir) | ||
| 398 | m.viper.SetDefault("logging.enable_file_log", defaults.Logging.EnableFileLog) | 398 | m.viper.SetDefault("logging.enable_file_log", defaults.Logging.EnableFileLog) | ||
| 399 | m.viper.SetDefault("logging.capture_console", defaults.Logging.CaptureConsole) | 399 | m.viper.SetDefault("logging.capture_console", defaults.Logging.CaptureConsole) | ||
| 400 | + | m.viper.SetDefault("logging.capture_gtk_logs", defaults.Logging.CaptureGTKLogs) | |||
| 400 | } | 401 | } | ||
| 401 | 402 | ||||
| 402 | func (m *Manager) setDebugDefaults(defaults *Config) { | 403 | func (m *Manager) setDebugDefaults(defaults *Config) { | ||
M internal/infrastructure/config/schema.go +3 -0
| @@ -298,6 +298,9 @@ type LoggingConfig struct { | |||
| 298 | 298 | ||
| 299 | 299 | // Capture browser console to logs | |
| 300 | 300 | CaptureConsole bool `mapstructure:"capture_console" yaml:"capture_console" toml:"capture_console" json:"capture_console"` | |
| 301 | + | ||
| 302 | + | // Capture GTK4/WebKitGTK6 messages to session logs | |
| 303 | + | CaptureGTKLogs bool `mapstructure:"capture_gtk_logs" yaml:"capture_gtk_logs" toml:"capture_gtk_logs" json:"capture_gtk_logs"` | |
| 301 | 304 | } | |
| 302 | 305 | ||
| 303 | 306 | // AppearanceConfig holds UI/rendering preferences. | |
| @@ -298,6 +298,9 @@ type LoggingConfig struct { | |||||
| 298 | 298 | ||||
| 299 | // Capture browser console to logs | 299 | // Capture browser console to logs | ||
| 300 | CaptureConsole bool `mapstructure:"capture_console" yaml:"capture_console" toml:"capture_console" json:"capture_console"` | 300 | CaptureConsole bool `mapstructure:"capture_console" yaml:"capture_console" toml:"capture_console" json:"capture_console"` | ||
| 301 | + | ||||
| 302 | + | // Capture GTK4/WebKitGTK6 messages to session logs | |||
| 303 | + | CaptureGTKLogs bool `mapstructure:"capture_gtk_logs" yaml:"capture_gtk_logs" toml:"capture_gtk_logs" json:"capture_gtk_logs"` | |||
| 301 | } | 304 | } | ||
| 302 | 305 | ||||
| 303 | // AppearanceConfig holds UI/rendering preferences. | 306 | // AppearanceConfig holds UI/rendering preferences. | ||
A internal/logging/glib.go +70 -0
| @@ -0,0 +1,70 @@ | |||
| 1 | + | package logging | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "sync" | |
| 6 | + | ||
| 7 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |
| 8 | + | "github.com/rs/zerolog" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // glibLogger holds the logger for the GLib handler. | |
| 12 | + | // We need this because the GLib callback doesn't support passing Go pointers directly. | |
| 13 | + | var ( | |
| 14 | + | glibLogger zerolog.Logger | |
| 15 | + | glibLoggerOnce sync.Once | |
| 16 | + | ) | |
| 17 | + | ||
| 18 | + | // InstallGLibLogHandler installs a custom GLib log handler that routes | |
| 19 | + | // GTK4/WebKitGTK6/GLib messages to the provided zerolog logger. | |
| 20 | + | // This must be called before GTK/WebKit initialization. | |
| 21 | + | // The handler captures messages from all GLib-based libraries (GTK, GDK, WebKit, etc.) | |
| 22 | + | // If enableDebug is true, GLib debug messages will also be captured (useful when log level is debug/trace). | |
| 23 | + | func InstallGLibLogHandler(ctx context.Context, logger zerolog.Logger, enableDebug bool) { | |
| 24 | + | log := FromContext(ctx) | |
| 25 | + | ||
| 26 | + | glibLoggerOnce.Do(func() { | |
| 27 | + | glibLogger = logger | |
| 28 | + | ||
| 29 | + | // Only enable GLib debug messages if requested (matches app log level) | |
| 30 | + | if enableDebug { | |
| 31 | + | glib.LogSetDebugEnabled(true) | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // Install our custom handler for all log messages | |
| 35 | + | handler := glib.LogFunc(glibLogHandler) | |
| 36 | + | glib.LogSetDefaultHandler(&handler, 0) | |
| 37 | + | ||
| 38 | + | log.Info().Bool("debug_enabled", enableDebug).Msg("GLib log handler installed") | |
| 39 | + | }) | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | // glibLogHandler is the callback invoked by GLib for all log messages. | |
| 43 | + | func glibLogHandler(domain string, level glib.LogLevelFlags, message string, _ uintptr) { | |
| 44 | + | // Map GLib log levels to zerolog levels | |
| 45 | + | var event *zerolog.Event | |
| 46 | + | ||
| 47 | + | switch { | |
| 48 | + | case level&glib.GLogLevelErrorValue != 0: | |
| 49 | + | event = glibLogger.Error() | |
| 50 | + | case level&glib.GLogLevelCriticalValue != 0: | |
| 51 | + | event = glibLogger.Error() | |
| 52 | + | case level&glib.GLogLevelWarningValue != 0: | |
| 53 | + | event = glibLogger.Warn() | |
| 54 | + | case level&glib.GLogLevelMessageValue != 0: | |
| 55 | + | event = glibLogger.Info() | |
| 56 | + | case level&glib.GLogLevelInfoValue != 0: | |
| 57 | + | event = glibLogger.Info() | |
| 58 | + | case level&glib.GLogLevelDebugValue != 0: | |
| 59 | + | event = glibLogger.Debug() | |
| 60 | + | default: | |
| 61 | + | event = glibLogger.Debug() | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // Add domain if present | |
| 65 | + | if domain != "" { | |
| 66 | + | event = event.Str("glib_domain", domain) | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | event.Msg(message) | |
| 70 | + | } | |
| @@ -0,0 +1,70 @@ | |||||
| 1 | + | package logging | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "sync" | |||
| 6 | + | ||||
| 7 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |||
| 8 | + | "github.com/rs/zerolog" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // glibLogger holds the logger for the GLib handler. | |||
| 12 | + | // We need this because the GLib callback doesn't support passing Go pointers directly. | |||
| 13 | + | var ( | |||
| 14 | + | glibLogger zerolog.Logger | |||
| 15 | + | glibLoggerOnce sync.Once | |||
| 16 | + | ) | |||
| 17 | + | ||||
| 18 | + | // InstallGLibLogHandler installs a custom GLib log handler that routes | |||
| 19 | + | // GTK4/WebKitGTK6/GLib messages to the provided zerolog logger. | |||
| 20 | + | // This must be called before GTK/WebKit initialization. | |||
| 21 | + | // The handler captures messages from all GLib-based libraries (GTK, GDK, WebKit, etc.) | |||
| 22 | + | // If enableDebug is true, GLib debug messages will also be captured (useful when log level is debug/trace). | |||
| 23 | + | func InstallGLibLogHandler(ctx context.Context, logger zerolog.Logger, enableDebug bool) { | |||
| 24 | + | log := FromContext(ctx) | |||
| 25 | + | ||||
| 26 | + | glibLoggerOnce.Do(func() { | |||
| 27 | + | glibLogger = logger | |||
| 28 | + | ||||
| 29 | + | // Only enable GLib debug messages if requested (matches app log level) | |||
| 30 | + | if enableDebug { | |||
| 31 | + | glib.LogSetDebugEnabled(true) | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // Install our custom handler for all log messages | |||
| 35 | + | handler := glib.LogFunc(glibLogHandler) | |||
| 36 | + | glib.LogSetDefaultHandler(&handler, 0) | |||
| 37 | + | ||||
| 38 | + | log.Info().Bool("debug_enabled", enableDebug).Msg("GLib log handler installed") | |||
| 39 | + | }) | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | // glibLogHandler is the callback invoked by GLib for all log messages. | |||
| 43 | + | func glibLogHandler(domain string, level glib.LogLevelFlags, message string, _ uintptr) { | |||
| 44 | + | // Map GLib log levels to zerolog levels | |||
| 45 | + | var event *zerolog.Event | |||
| 46 | + | ||||
| 47 | + | switch { | |||
| 48 | + | case level&glib.GLogLevelErrorValue != 0: | |||
| 49 | + | event = glibLogger.Error() | |||
| 50 | + | case level&glib.GLogLevelCriticalValue != 0: | |||
| 51 | + | event = glibLogger.Error() | |||
| 52 | + | case level&glib.GLogLevelWarningValue != 0: | |||
| 53 | + | event = glibLogger.Warn() | |||
| 54 | + | case level&glib.GLogLevelMessageValue != 0: | |||
| 55 | + | event = glibLogger.Info() | |||
| 56 | + | case level&glib.GLogLevelInfoValue != 0: | |||
| 57 | + | event = glibLogger.Info() | |||
| 58 | + | case level&glib.GLogLevelDebugValue != 0: | |||
| 59 | + | event = glibLogger.Debug() | |||
| 60 | + | default: | |||
| 61 | + | event = glibLogger.Debug() | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // Add domain if present | |||
| 65 | + | if domain != "" { | |||
| 66 | + | event = event.Str("glib_domain", domain) | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | event.Msg(message) | |||
| 70 | + | } | |||