mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* [MM-69464] Fix data race in pluginapi.ConfigureLogrus ConfigureLogrus registered the hook via logger.Hooks.Add, which mutates the hook map without holding the logger mutex. logrus copies that map under the mutex when firing hooks, so configuring the logger while another goroutine logs through it is a data race that can panic with "concurrent map read and map write". This is reachable when a plugin re-runs its activation while goroutines from a prior activation still log through logrus.StandardLogger() (originally reported as MM-52096). Use logger.AddHook, which acquires the logger mutex. Also configure the passed-in logger instead of the global standard logger: SetReportCaller and SetLevel previously called the package-level logrus functions, silently mutating logrus.StandardLogger() regardless of the logger argument. Adds a -race regression test that configures the logger concurrently with logging. * Use WaitGroup.Go in the regression test (golangci modernize) * Remove obvious comment on SetReportCaller --------- Co-authored-by: Hanzo Dev <[email protected]>
74 lines
2.1 KiB
Go
74 lines
2.1 KiB
Go
package pluginapi
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// LogrusHook is a logrus.Hook for emitting plugin logs through the RPC API for inclusion in the
|
|
// server logs.
|
|
//
|
|
// To configure the default Logrus logger for use with plugin logging, simply invoke:
|
|
//
|
|
// pluginapi.ConfigureLogrus(logrus.StandardLogger(), pluginAPIClient)
|
|
//
|
|
// Alternatively, construct your own logger to pass to pluginapi.ConfigureLogrus.
|
|
type LogrusHook struct {
|
|
log LogService
|
|
}
|
|
|
|
// NewLogrusHook creates a new instance of LogrusHook.
|
|
func NewLogrusHook(log LogService) *LogrusHook {
|
|
return &LogrusHook{
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
// Levels allows LogrusHook to process any log level.
|
|
func (lh *LogrusHook) Levels() []logrus.Level {
|
|
return logrus.AllLevels
|
|
}
|
|
|
|
// Fire proxies logrus entries through the plugin API at the appropriate level.
|
|
func (lh *LogrusHook) Fire(entry *logrus.Entry) error {
|
|
fields := []any{}
|
|
for key, value := range entry.Data {
|
|
fields = append(fields, key, fmt.Sprintf("%+v", value))
|
|
}
|
|
|
|
if entry.Caller != nil {
|
|
fields = append(fields, "plugin_caller", fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line))
|
|
}
|
|
|
|
switch entry.Level {
|
|
case logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel:
|
|
lh.log.Error(entry.Message, fields...)
|
|
case logrus.WarnLevel:
|
|
lh.log.Warn(entry.Message, fields...)
|
|
case logrus.InfoLevel:
|
|
lh.log.Info(entry.Message, fields...)
|
|
case logrus.DebugLevel, logrus.TraceLevel:
|
|
lh.log.Debug(entry.Message, fields...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConfigureLogrus configures the given logrus logger with a hook to proxy through the RPC API,
|
|
// discarding the default output to avoid duplicating the events across the standard STDOUT proxy.
|
|
func ConfigureLogrus(logger *logrus.Logger, client *Client) {
|
|
hook := NewLogrusHook(client.Log)
|
|
|
|
// AddHook (not Hooks.Add) takes the logger mutex, so registering the hook is
|
|
// safe against goroutines logging through the same logger concurrently.
|
|
logger.AddHook(hook)
|
|
logger.SetOutput(io.Discard)
|
|
|
|
logger.SetReportCaller(true)
|
|
|
|
// By default, log everything to the server, and let it decide what gets through.
|
|
logger.SetLevel(logrus.TraceLevel)
|
|
}
|