diff --git a/executors/internal/autoscaler/logger/logger.go b/executors/internal/autoscaler/logger/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..c3087532f53650505696ec44836f3aaa1d4ead06 --- /dev/null +++ b/executors/internal/autoscaler/logger/logger.go @@ -0,0 +1,186 @@ +package logger + +import ( + "fmt" + "io" + "log" + + "github.com/hashicorp/go-hclog" + "github.com/sirupsen/logrus" +) + +type Logger struct { + entry *logrus.Entry + name string +} + +var ( + logrusToHclog = []hclog.Level{ + logrus.PanicLevel: hclog.Error, + logrus.FatalLevel: hclog.Error, + logrus.ErrorLevel: hclog.Error, + logrus.WarnLevel: hclog.Warn, + logrus.InfoLevel: hclog.Info, + logrus.DebugLevel: hclog.Debug, + logrus.TraceLevel: hclog.Trace, + } + + hclogToLogrus = []logrus.Level{ + hclog.NoLevel: logrus.InfoLevel, + hclog.Trace: logrus.TraceLevel, + hclog.Debug: logrus.DebugLevel, + hclog.Info: logrus.InfoLevel, + hclog.Warn: logrus.WarnLevel, + hclog.Error: logrus.ErrorLevel, + hclog.Off: logrus.InfoLevel, + } +) + +func New(entry *logrus.Entry) *Logger { + entry = entry.Dup() + if entry.Logger == nil { + entry.Logger = logrus.StandardLogger() + } + + return &Logger{entry: entry} +} + +func (l *Logger) level(lvl hclog.Level) logrus.Level { + return hclogToLogrus[lvl] +} + +func (l *Logger) fields(args []any) logrus.Fields { + if len(args) == 0 { + return nil + } + + if len(args)%2 != 0 { + args = append(args, "") + } + + fields := make(logrus.Fields, len(args)/2) + for i := 0; i < len(args); i += 2 { + key, ok := args[i].(string) + if !ok { + key = fmt.Sprintf("%v", args[i]) + } + fields[key] = args[i+1] + } + + return fields +} + +func (l *Logger) Log(level hclog.Level, msg string, args ...interface{}) { + entry := l.entry + if len(args) > 0 { + entry = entry.WithFields(l.fields(args)) + } + + entry.Log(l.level(level), msg) +} + +func (l *Logger) Trace(msg string, args ...interface{}) { + l.Log(hclog.Trace, msg, args...) +} + +func (l *Logger) Debug(msg string, args ...interface{}) { + l.Log(hclog.Debug, msg, args...) +} + +func (l *Logger) Info(msg string, args ...interface{}) { + l.Log(hclog.Info, msg, args...) +} + +func (l *Logger) Warn(msg string, args ...interface{}) { + l.Log(hclog.Warn, msg, args...) +} + +func (l *Logger) Error(msg string, args ...interface{}) { + l.Log(hclog.Error, msg, args...) +} + +func (l *Logger) IsTrace() bool { + return l.entry.Logger.IsLevelEnabled(logrus.TraceLevel) +} + +func (l *Logger) IsDebug() bool { + return l.entry.Logger.IsLevelEnabled(logrus.DebugLevel) +} + +func (l *Logger) IsInfo() bool { + return l.entry.Logger.IsLevelEnabled(logrus.InfoLevel) +} + +func (l *Logger) IsWarn() bool { + return l.entry.Logger.IsLevelEnabled(logrus.WarnLevel) +} + +func (l *Logger) IsError() bool { + return l.entry.Logger.IsLevelEnabled(logrus.ErrorLevel) +} + +func (l *Logger) ImpliedArgs() []any { + if len(l.entry.Data) == 0 { + return nil + } + + fields := make([]any, len(l.entry.Data)*2) + for key, val := range l.entry.Data { + fields = append(fields, key, val) + } + + return fields +} + +func (l *Logger) With(args ...interface{}) hclog.Logger { + if len(args) == 0 { + return l + } + + return New(l.entry.WithFields(l.fields(args))) +} + +func (l *Logger) Name() string { + return l.name +} + +func (l *Logger) Named(name string) hclog.Logger { + if l.name != "" { + name = l.name + "." + name + } + + return l.ResetNamed(name) +} + +func (l *Logger) ResetNamed(name string) hclog.Logger { + logger := New(l.entry.WithFields(logrus.Fields{"subsystem": name})) + logger.name = name + + return logger +} + +func (l *Logger) SetLevel(level hclog.Level) { + l.entry.Logger.SetLevel(l.level(level)) +} + +func (l *Logger) GetLevel() hclog.Level { + return logrusToHclog[l.entry.Logger.GetLevel()] +} + +func (l *Logger) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { + logger := hclog.Default() + logger.SetLevel(l.GetLevel()) + logger.Named(l.name) + logger.With(l.ImpliedArgs()...) + + return logger.StandardLogger(opts) +} + +func (l *Logger) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + logger := hclog.Default() + logger.SetLevel(l.GetLevel()) + logger.Named(l.name) + logger.With(l.ImpliedArgs()...) + + return logger.StandardWriter(opts) +} diff --git a/executors/internal/autoscaler/logger/logger_test.go b/executors/internal/autoscaler/logger/logger_test.go new file mode 100644 index 0000000000000000000000000000000000000000..22f81dd2a0fe05fbb5baa19353bcce912e361e0d --- /dev/null +++ b/executors/internal/autoscaler/logger/logger_test.go @@ -0,0 +1,44 @@ +//go:build !integration + +package logger + +import ( + "bytes" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" +) + +func TestLogger(t *testing.T) { + buf := new(bytes.Buffer) + + l := logrus.StandardLogger() + l.Out = buf + + logger := New(logrus.NewEntry(l)) + logger.SetLevel(hclog.Trace) + require.Equal(t, hclog.Trace, logger.GetLevel()) + + logger.Trace("trace", "one", "two") + logger.Debug("debug", "three", "four") + logger.Info("info", "five", "six") + logger.Warn("warn", "seven", "eight") + logger.Error("error", "nine", "ten") + + subsystem := logger.Named("newname") + subsystem.Info("info") + subsystem.Named("another").Info("info") + + logger.Info("unbalanced", "key") + + require.Contains(t, buf.String(), "level=trace msg=trace one=two") + require.Contains(t, buf.String(), "level=debug msg=debug three=four") + require.Contains(t, buf.String(), "level=info msg=info five=six") + require.Contains(t, buf.String(), "level=warning msg=warn seven=eight") + require.Contains(t, buf.String(), "level=error msg=error nine=ten") + require.Contains(t, buf.String(), "level=info msg=info subsystem=newname") + require.Contains(t, buf.String(), "level=info msg=info subsystem=newname.another") + require.Contains(t, buf.String(), "level=info msg=unbalanced key=\"\"") +} diff --git a/executors/internal/autoscaler/provider.go b/executors/internal/autoscaler/provider.go index 73906bacb08a6522da63f4af6becb4154723aa2e..a4e5baea832962673c1d63714b20ba47b9ac735b 100644 --- a/executors/internal/autoscaler/provider.go +++ b/executors/internal/autoscaler/provider.go @@ -23,6 +23,7 @@ import ( tsprometheus "gitlab.com/gitlab-org/fleeting/taskscaler/metrics/prometheus" "gitlab.com/gitlab-org/gitlab-runner/common" + "gitlab.com/gitlab-org/gitlab-runner/executors/internal/autoscaler/logger" "gitlab.com/gitlab-org/gitlab-runner/helpers" ) @@ -45,7 +46,7 @@ type provider struct { // Testing hooks taskscalerNew func(context.Context, fleetingprovider.InstanceGroup, ...taskscaler.Option) (taskscaler.Taskscaler, error) - fleetingRunPlugin func(string, []byte) (fleetingPlugin, error) + fleetingRunPlugin func(string, []byte, ...fleeting.PluginOption) (fleetingPlugin, error) generateUniqueID func() (string, error) } @@ -75,13 +76,13 @@ func New(ep common.ExecutorProvider, cfg Config) common.ExecutorProvider { cfg: cfg, scalers: make(map[string]scaler), taskscalerNew: taskscaler.New, - fleetingRunPlugin: func(name string, config []byte) (fleetingPlugin, error) { + fleetingRunPlugin: func(name string, config []byte, opts ...fleeting.PluginOption) (fleetingPlugin, error) { pluginPath, err := installer.LookPath(name, "") if err != nil { return nil, fmt.Errorf("loading fleeting plugin: %w", err) } - return fleeting.RunPlugin(pluginPath, config) + return fleeting.RunPlugin(pluginPath, config, opts...) }, generateUniqueID: func() (string, error) { return helpers.GenerateRandomUUID(8) @@ -128,7 +129,9 @@ func (p *provider) init(config *common.RunnerConfig) (taskscaler.Taskscaler, boo return nil, false, fmt.Errorf("marshaling plugin config: %w", err) } - runner, err := p.fleetingRunPlugin(config.Autoscaler.Plugin, pluginCfg) + logger := logger.New(config.RunnerCredentials.Log()) + + runner, err := p.fleetingRunPlugin(config.Autoscaler.Plugin, pluginCfg, fleeting.WithPluginLogger(logger.Named("fleeting-plugin"))) if err != nil { return nil, false, fmt.Errorf("running autoscaler plugin: %w", err) } @@ -187,6 +190,7 @@ func (p *provider) init(config *common.RunnerConfig) (taskscaler.Taskscaler, boo taskscaler.WithInstanceUpFunc(instanceReadyUp(shutdownCtx, config)), taskscaler.WithUpdateInterval(time.Minute), taskscaler.WithUpdateIntervalWhenExpecting(time.Second), + taskscaler.WithLogger(logger.Named("taskscaler")), } if config.Autoscaler.DeleteInstancesOnShutdown { diff --git a/executors/internal/autoscaler/provider_test.go b/executors/internal/autoscaler/provider_test.go index 13e88f3e1c5a57cd8ff738ff052624007a814883..3382e825f89f72b5c3c7f1df96220c8d63dfa9a2 100644 --- a/executors/internal/autoscaler/provider_test.go +++ b/executors/internal/autoscaler/provider_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gitlab.com/gitlab-org/fleeting/fleeting" fleetingprovider "gitlab.com/gitlab-org/fleeting/fleeting/provider" "gitlab.com/gitlab-org/fleeting/taskscaler" "gitlab.com/gitlab-org/fleeting/taskscaler/mocks" @@ -229,8 +230,8 @@ func mockTaskscalerNew( } } -func mockFleetingRunPlugin(wantErr bool) func(string, []byte) (fleetingPlugin, error) { - return func(string, []byte) (fleetingPlugin, error) { +func mockFleetingRunPlugin(wantErr bool) func(string, []byte, ...fleeting.PluginOption) (fleetingPlugin, error) { + return func(string, []byte, ...fleeting.PluginOption) (fleetingPlugin, error) { if wantErr { return nil, fmt.Errorf("test error") }