From 415eac5399dc816c06e60ddc12c55c82bc9ff983 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 Dec 2025 16:48:10 -0500 Subject: [PATCH] feat(plugins): integrate logs Signed-off-by: Deluan --- conf/configuration.go | 1 + log/log.go | 20 ++++++++--------- plugins/manager.go | 50 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/conf/configuration.go b/conf/configuration.go index 89cb7ab3e..0aa0a4f18 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -230,6 +230,7 @@ type pluginsOptions struct { Folder string CacheSize string AutoReload bool + LogLevel string } type extAuthOptions struct { diff --git a/log/log.go b/log/log.go index 24f3dff6e..3e8597bdd 100644 --- a/log/log.go +++ b/log/log.go @@ -88,11 +88,11 @@ func SetLevel(l Level) { } func SetLevelString(l string) { - level := levelFromString(l) + level := ParseLogLevel(l) SetLevel(level) } -func levelFromString(l string) Level { +func ParseLogLevel(l string) Level { envLevel := strings.ToLower(l) var level Level switch envLevel { @@ -118,7 +118,7 @@ func SetLogLevels(levels map[string]string) { defer loggerMu.Unlock() logLevels = nil for k, v := range levels { - logLevels = append(logLevels, levelPath{path: k, level: levelFromString(v)}) + logLevels = append(logLevels, levelPath{path: k, level: ParseLogLevel(v)}) } sort.Slice(logLevels, func(i, j int) bool { return logLevels[i].path > logLevels[j].path @@ -185,31 +185,31 @@ func IsGreaterOrEqualTo(level Level) bool { } func Fatal(args ...interface{}) { - log(LevelFatal, args...) + Log(LevelFatal, args...) os.Exit(1) } func Error(args ...interface{}) { - log(LevelError, args...) + Log(LevelError, args...) } func Warn(args ...interface{}) { - log(LevelWarn, args...) + Log(LevelWarn, args...) } func Info(args ...interface{}) { - log(LevelInfo, args...) + Log(LevelInfo, args...) } func Debug(args ...interface{}) { - log(LevelDebug, args...) + Log(LevelDebug, args...) } func Trace(args ...interface{}) { - log(LevelTrace, args...) + Log(LevelTrace, args...) } -func log(level Level, args ...interface{}) { +func Log(level Level, args ...interface{}) { if !shouldLog(level, 3) { return } diff --git a/plugins/manager.go b/plugins/manager.go index 56904d768..401c9a68c 100644 --- a/plugins/manager.go +++ b/plugins/manager.go @@ -59,9 +59,14 @@ type pluginInstance struct { } func (p *pluginInstance) create() (*extism.Plugin, error) { - return p.compiled.Instance(context.Background(), extism.PluginInstanceConfig{ + plugin, err := p.compiled.Instance(context.Background(), extism.PluginInstanceConfig{ ModuleConfig: wazero.NewModuleConfig().WithSysWalltime().WithRandSource(rand.Reader), }) + if err != nil { + return nil, err + } + plugin.SetLogger(extismLogger(p.name)) + return plugin, nil } // GetManager returns a singleton instance of the plugin manager. @@ -82,6 +87,13 @@ func (m *Manager) Start(ctx context.Context) error { return nil } + // Set extism log level based on plugin-specific config or global log level + pluginLogLevel := conf.Server.Plugins.LogLevel + if pluginLogLevel == "" { + pluginLogLevel = conf.Server.LogLevel + } + extism.SetLogLevel(toExtismLogLevel(log.ParseLogLevel(pluginLogLevel))) + m.ctx, m.cancel = context.WithCancel(ctx) // Initialize wazero compilation cache for better performance @@ -317,6 +329,7 @@ func (m *Manager) loadPlugin(name, wasmPath string) error { return err } defer tempPlugin.Close(m.ctx) + tempPlugin.SetLogger(extismLogger(name)) // Call nd_manifest to get plugin manifest exit, manifestBytes, err := tempPlugin.Call(manifestFunction, nil) @@ -348,6 +361,7 @@ func (m *Manager) loadPlugin(name, wasmPath string) error { AllowedHosts: manifest.AllowedHosts(), Timeout: uint64(defaultTimeout.Milliseconds()), } + log.Debug(m.ctx, "Loaded plugin", "plugin", name, "name", manifest.Name, "version", manifest.Version, "capabilities", capabilities) finalConfig := extism.PluginConfig{ EnableWasi: true, @@ -483,7 +497,7 @@ func callPluginFunction[I any, O any](ctx context.Context, plugin *pluginInstanc startCall := time.Now() exit, output, err := p.Call(funcName, inputBytes) if err != nil { - log.Trace(ctx, "Plugin call failed", "p", plugin.name, "function", funcName, "pluginDuration", time.Since(startCall), "navidromeDuration", startCall.Sub(start), err) + log.Trace(ctx, "Plugin call failed", "plugin", plugin.name, "function", funcName, "pluginDuration", time.Since(startCall), "navidromeDuration", startCall.Sub(start), err) return result, fmt.Errorf("plugin call failed: %w", err) } if exit != 0 { @@ -493,14 +507,42 @@ func callPluginFunction[I any, O any](ctx context.Context, plugin *pluginInstanc if len(output) > 0 { err = json.Unmarshal(output, &result) if err != nil { - log.Trace(ctx, "Plugin call failed", "p", plugin.name, "function", funcName, "pluginDuration", time.Since(startCall), "navidromeDuration", startCall.Sub(start), err) + log.Trace(ctx, "Plugin call failed", "plugin", plugin.name, "function", funcName, "pluginDuration", time.Since(startCall), "navidromeDuration", startCall.Sub(start), err) } } - log.Trace(ctx, "Plugin call succeeded", "p", plugin.name, "function", funcName, "pluginDuration", time.Since(startCall), "navidromeDuration", startCall.Sub(start)) + log.Trace(ctx, "Plugin call succeeded", "plugin", plugin.name, "function", funcName, "pluginDuration", time.Since(startCall), "navidromeDuration", startCall.Sub(start)) return result, err } +// extismLogger is a helper to log messages from Extism plugins +func extismLogger(pluginName string) func(level extism.LogLevel, msg string) { + return func(level extism.LogLevel, msg string) { + if level == extism.LogLevelOff { + return + } + log.Log(log.ParseLogLevel(level.String()), msg, "plugin", pluginName) + } +} + +// toExtismLogLevel converts a Navidrome log level to an extism LogLevel +func toExtismLogLevel(level log.Level) extism.LogLevel { + switch level { + case log.LevelTrace: + return extism.LogLevelTrace + case log.LevelDebug: + return extism.LogLevelDebug + case log.LevelInfo: + return extism.LogLevelInfo + case log.LevelWarn: + return extism.LogLevelWarn + case log.LevelError, log.LevelFatal: + return extism.LogLevelError + default: + return extism.LogLevelInfo + } +} + // Verify interface implementations at compile time var ( _ agents.PluginLoader = (*Manager)(nil)