From 4e8037a93b5301a5b4d4399ee453d20e5bd0f353 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 12 Oct 2026 00:19:01 +0000 Subject: [PATCH] refactor(logging): use the logging package in internal (#1302) Move the log calls in `internal` onto `logging`, and add what the process-wide logger setup and its tests need: `SetTextFormat` with a line filter for secret masking (replacing `report.MaskingFormatter`), `SetReportCaller`, `SetDefault` and `logtest.SetDefault`. After this only the `logging` package imports logrus. Stacked on https://gitea.com/gitea/runner/pulls/1301. Assisted-by: Codet Co-authored-by: bircni Reviewed-on: https://gitea.com/gitea/runner/pulls/1302 Reviewed-by: bircni --- act/common/logging/format.go | 50 ++++++++++++++++ act/common/logging/global.go | 40 ++++++++----- act/common/logging/logtest/logtest.go | 11 ++++ internal/app/cmd/cache-server.go | 3 +- internal/app/cmd/daemon.go | 78 ++++++++++++------------- internal/app/cmd/daemon_test.go | 25 ++++---- internal/app/cmd/exec.go | 37 ++++++------ internal/app/cmd/register.go | 55 ++++++++--------- internal/app/poll/poller.go | 18 +++--- internal/app/poll/poller_test.go | 7 +-- internal/app/run/post_task_script.go | 16 ++--- internal/app/run/proxy.go | 10 ++-- internal/app/run/runner.go | 57 +++++++++--------- internal/app/run/runner_test.go | 8 +-- internal/pkg/config/config.example.yaml | 2 +- internal/pkg/config/config.go | 15 ++--- internal/pkg/config/config_test.go | 15 +++-- internal/pkg/metrics/server.go | 9 +-- internal/pkg/report/globalmask.go | 21 ++----- internal/pkg/report/globalmask_test.go | 20 +++++-- internal/pkg/report/joblog.go | 17 +++--- internal/pkg/report/reporter.go | 13 ++--- internal/pkg/report/reporter_test.go | 15 +++-- internal/pkg/telemetry/metrics.go | 4 +- internal/pkg/telemetry/telemetry.go | 8 +-- 25 files changed, 308 insertions(+), 246 deletions(-) create mode 100644 act/common/logging/format.go diff --git a/act/common/logging/format.go b/act/common/logging/format.go new file mode 100644 index 00000000..de1e8a64 --- /dev/null +++ b/act/common/logging/format.go @@ -0,0 +1,50 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package logging + +import ( + "runtime" + + "github.com/sirupsen/logrus" +) + +// TextOptions describes how the process-wide Logger writes a record as text. +type TextOptions struct { + DisableColors bool + DisableTimestamp bool + FullTimestamp bool + // CallerPrettyfier renames the function and file reported with a record when ReportCaller is on. + CallerPrettyfier func(*runtime.Frame) (function, file string) + // Filter rewrites each formatted line, for example to mask secrets. + Filter func(line []byte) []byte +} + +// SetTextFormat sets the text format of the process-wide Logger. +func SetTextFormat(o TextOptions) { + logrus.SetFormatter(&filteringFormatter{ + inner: &logrus.TextFormatter{ + DisableColors: o.DisableColors, + DisableTimestamp: o.DisableTimestamp, + FullTimestamp: o.FullTimestamp, + CallerPrettyfier: o.CallerPrettyfier, + }, + filter: o.Filter, + }) +} + +// SetReportCaller makes the process-wide Logger record where each call came from, which costs time. +func SetReportCaller(on bool) { logrus.SetReportCaller(on) } + +type filteringFormatter struct { + inner logrus.Formatter + filter func([]byte) []byte +} + +func (f *filteringFormatter) Format(e *logrus.Entry) ([]byte, error) { + line, err := f.inner.Format(e) + if err != nil || f.filter == nil { + return line, err + } + return f.filter(line), nil +} diff --git a/act/common/logging/global.go b/act/common/logging/global.go index 4e7c9de5..62873733 100644 --- a/act/common/logging/global.go +++ b/act/common/logging/global.go @@ -5,14 +5,24 @@ package logging import ( "io" + "sync/atomic" "github.com/sirupsen/logrus" ) -var std = New(NewLogrusHandler(logrus.StandardLogger())) +var std atomic.Pointer[Logger] + +func init() { + std.Store(New(NewLogrusHandler(logrus.StandardLogger()))) +} // Default returns the process-wide Logger. -func Default() *Logger { return std } +func Default() *Logger { return std.Load() } + +// SetDefault replaces the process-wide Logger, which tests use to capture what the package +// functions log. SetLevel, GetLevel, SetOutput and the format functions keep configuring the +// initial logrus-backed Logger. +func SetDefault(l *Logger) { std.Store(l) } // SetLevel sets the minimum level of the process-wide Logger. func SetLevel(level Level) { logrus.SetLevel(toLogrusLevel(level)) } @@ -26,17 +36,17 @@ func SetOutput(w io.Writer) { logrus.SetOutput(w) } // Output returns where the process-wide Logger writes. func Output() io.Writer { return logrus.StandardLogger().Out } -func Enabled(level Level) bool { return std.Enabled(level) } +func Enabled(level Level) bool { return Default().Enabled(level) } -func With(args ...any) *Logger { return std.With(args...) } -func WithError(err error) *Logger { return std.WithError(err) } -func Tracef(format string, args ...any) { std.logf(LevelTrace, format, args) } -func Debugf(format string, args ...any) { std.logf(LevelDebug, format, args) } -func Infof(format string, args ...any) { std.logf(LevelInfo, format, args) } -func Warnf(format string, args ...any) { std.logf(LevelWarn, format, args) } -func Errorf(format string, args ...any) { std.logf(LevelError, format, args) } -func Trace(args ...any) { std.log(LevelTrace, args) } -func Debug(args ...any) { std.log(LevelDebug, args) } -func Info(args ...any) { std.log(LevelInfo, args) } -func Warn(args ...any) { std.log(LevelWarn, args) } -func Error(args ...any) { std.log(LevelError, args) } +func With(args ...any) *Logger { return Default().With(args...) } +func WithError(err error) *Logger { return Default().WithError(err) } +func Tracef(format string, args ...any) { Default().logf(LevelTrace, format, args) } +func Debugf(format string, args ...any) { Default().logf(LevelDebug, format, args) } +func Infof(format string, args ...any) { Default().logf(LevelInfo, format, args) } +func Warnf(format string, args ...any) { Default().logf(LevelWarn, format, args) } +func Errorf(format string, args ...any) { Default().logf(LevelError, format, args) } +func Trace(args ...any) { Default().log(LevelTrace, args) } +func Debug(args ...any) { Default().log(LevelDebug, args) } +func Info(args ...any) { Default().log(LevelInfo, args) } +func Warn(args ...any) { Default().log(LevelWarn, args) } +func Error(args ...any) { Default().log(LevelError, args) } diff --git a/act/common/logging/logtest/logtest.go b/act/common/logging/logtest/logtest.go index a3a7c3dc..ad5cd3d3 100644 --- a/act/common/logging/logtest/logtest.go +++ b/act/common/logging/logtest/logtest.go @@ -8,6 +8,7 @@ import ( "context" "slices" "sync" + "testing" "gitea.com/gitea/runner/act/common/logging" ) @@ -76,3 +77,13 @@ func (h *Handler) Reset() { defer h.store.mu.Unlock() h.store.records = nil } + +// SetDefault makes the process-wide Logger record into a new Handler until the test ends. +func SetDefault(t testing.TB) *Handler { + t.Helper() + prev := logging.Default() + logger, h := NewLogger() + logging.SetDefault(logger) + t.Cleanup(func() { logging.SetDefault(prev) }) + return h +} diff --git a/internal/app/cmd/cache-server.go b/internal/app/cmd/cache-server.go index aeeb1876..d6b852ed 100644 --- a/internal/app/cmd/cache-server.go +++ b/internal/app/cmd/cache-server.go @@ -12,7 +12,6 @@ import ( "gitea.com/gitea/runner/internal/app/run" "gitea.com/gitea/runner/internal/pkg/config" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -67,7 +66,7 @@ func runCacheServer(configFile *string, cacheArgs *cacheServerArgs) func(cmd *co return err } - log.Infof("cache server is listening on %v", cacheHandler.ExternalURL()) + logging.Infof("cache server is listening on %v", cacheHandler.ExternalURL()) <-cmd.Context().Done() diff --git a/internal/app/cmd/daemon.go b/internal/app/cmd/daemon.go index a641b9a4..937435ba 100644 --- a/internal/app/cmd/daemon.go +++ b/internal/app/cmd/daemon.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/app/poll" "gitea.com/gitea/runner/internal/app/run" "gitea.com/gitea/runner/internal/pkg/client" @@ -34,7 +35,6 @@ import ( runnerv1 "gitea.dev/actionslib/runner/v1" "github.com/avast/retry-go/v5" "github.com/mattn/go-isatty" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -46,11 +46,11 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu } initLogging(cfg) - log.Infoln("Starting runner daemon") + logging.Info("Starting runner daemon") reg, err := config.LoadRegistration(cfg.Runner.File) if os.IsNotExist(err) { - log.Error("registration file not found, please register the runner first") + logging.Error("registration file not found, please register the runner first") return err } else if err != nil { return fmt.Errorf("failed to load registration file: %w", err) @@ -62,13 +62,13 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu releaseLock, err := lock.TryLock(cfg.Runner.File) switch { case errors.Is(err, lock.ErrLocked): - log.Errorf("another gitea-runner process is already using %q; each runner process needs its own runner file (runner.file)", cfg.Runner.File) + logging.Errorf("another gitea-runner process is already using %q; each runner process needs its own runner file (runner.file)", cfg.Runner.File) return err case err != nil: // Best-effort guard: if the lock file can't be created (e.g. a // read-only runner-file mount), warn and start anyway rather than // refusing to run. - log.Warnf("could not lock runner file %q, continuing without the single-process guard: %v", cfg.Runner.File, err) + logging.Warnf("could not lock runner file %q, continuing without the single-process guard: %v", cfg.Runner.File, err) default: // Held until shutdown finishes: the draining runner still owns this // identity on the server, so releasing early would let a restart @@ -78,13 +78,13 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu shutdownTelemetry, err := telemetry.Setup(ctx, reg.UUID, reg.Name, metrics.Registry) if err != nil { - log.WithError(err).Warn("OpenTelemetry export failed to start") + logging.WithError(err).Warn("OpenTelemetry export failed to start") } defer func() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := shutdownTelemetry(ctx); err != nil { - log.WithError(err).Warn("OpenTelemetry export failed to flush") + logging.WithError(err).Warn("OpenTelemetry export failed to flush") } }() @@ -94,13 +94,13 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu for _, l := range lbls { label, err := labels.Parse(l) if err != nil { - log.WithError(err).Warnf("ignored invalid label %q", l) + logging.WithError(err).Warnf("ignored invalid label %q", l) continue } ls = append(ls, label) } if len(ls) == 0 { - log.Warn("no labels configured, runner may not be able to pick up jobs") + logging.Warn("no labels configured, runner may not be able to pick up jobs") } // Before the first Docker API call: the standard library resolves the proxy @@ -108,7 +108,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu if dockerSocketPath, err := getDockerSocketPath(cfg.Container.DockerHost); err == nil { run.BypassProxyForDockerHost(dockerSocketPath) } else { - log.Debugf("cannot resolve the docker socket path, so Docker API calls are not exempted from the proxy: %v", err) + logging.Debugf("cannot resolve the docker socket path, so Docker API calls are not exempted from the proxy: %v", err) } if ls.RequireDocker() || cfg.Container.RequireDocker { @@ -120,20 +120,20 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu for keepRunning { dockerSocketPath, err := getDockerSocketPath(cfg.Container.DockerHost) if err != nil { - log.Errorf("Failed to get socket path: %s", err.Error()) + logging.Errorf("Failed to get socket path: %s", err.Error()) } else if err = envcheck.CheckIfDockerRunning(tctx, dockerSocketPath); errors.Is(err, context.Canceled) { - log.Infof("Docker wait timeout of %s expired", timeout.String()) + logging.Infof("Docker wait timeout of %s expired", timeout.String()) break } else if err != nil { - log.Errorf("Docker connection failed: %s", err.Error()) + logging.Errorf("Docker connection failed: %s", err.Error()) } else { - log.Infof("Docker is ready") + logging.Infof("Docker is ready") break } select { case <-time.After(time.Second): case <-tctx.Done(): - log.Infof("Docker wait timeout of %s expired", timeout.String()) + logging.Infof("Docker wait timeout of %s expired", timeout.String()) keepRunning = false } } @@ -169,7 +169,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil { return fmt.Errorf("failed to save runner config: %w", err) } - log.Infof("labels updated to: %v", reg.Labels) + logging.Infof("labels updated to: %v", reg.Labels) } cli := client.New( @@ -184,7 +184,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu runner := run.NewRunner(cfg, reg, cli) defer func() { if err := runner.Close(); err != nil { - log.Warnf("runner %s: cache server shutdown: %v", reg.Name, err) + logging.Warnf("runner %s: cache server shutdown: %v", reg.Name, err) } }() @@ -197,9 +197,9 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu retry.RetryIf(shouldRetryDeclare), retry.OnRetry(func(attempt uint, err error) { if attempt == 0 { - log.WithError(err).Warn("fail to invoke Declare, retrying until Gitea is reachable") + logging.WithError(err).Warn("fail to invoke Declare, retrying until Gitea is reachable") } else { - log.WithError(err).Debug("fail to invoke Declare, retrying") + logging.WithError(err).Debug("fail to invoke Declare, retrying") } }), ).Do(func() (*connect.Response[runnerv1.DeclareResponse], error) { @@ -209,16 +209,16 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu case err != nil && ctx.Err() != nil && !client.IsRegistrationRejected(err): return nil case err != nil && connect.CodeOf(err) == connect.CodeUnimplemented: - log.Errorf("Your Gitea version is too old to support runner declare, please upgrade to v1.21 or later") + logging.Errorf("Your Gitea version is too old to support runner declare, please upgrade to v1.21 or later") return err case err != nil: - log.WithError(err).Error("fail to invoke Declare") + logging.WithError(err).Error("fail to invoke Declare") if client.IsRegistrationRejected(err) { removeEphemeralRegistration(reg, cfg.Runner.File) } return err default: - log.Infof("runner: %s, with version: %s, with labels: %v, declare successfully", + logging.Infof("runner: %s, with version: %s, with labels: %v, declare successfully", resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels) } runner.SetCapabilitiesFromDeclare(resp) @@ -268,14 +268,14 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu } } - log.Infof("runner: %s shutdown initiated, waiting %s for running jobs to complete before shutting down", resp.Msg.Runner.Name, cfg.Runner.ShutdownTimeout) + logging.Infof("runner: %s shutdown initiated, waiting %s for running jobs to complete before shutting down", resp.Msg.Runner.Name, cfg.Runner.ShutdownTimeout) ctx, cancel := context.WithTimeout(context.Background(), cfg.Runner.ShutdownTimeout) defer cancel() err = poller.Shutdown(ctx) if err != nil { - log.Warnf("runner: %s cancelled in progress jobs during shutdown", resp.Msg.Runner.Name) + logging.Warnf("runner: %s cancelled in progress jobs during shutdown", resp.Msg.Runner.Name) } if poller.RegistrationRejected() { @@ -295,9 +295,9 @@ func removeEphemeralRegistration(reg *config.Registration, file string) { return } if err := os.Remove(file); err == nil { - log.Infof("removed the spent ephemeral registration %q", file) + logging.Infof("removed the spent ephemeral registration %q", file) } else if !os.IsNotExist(err) { - log.WithError(err).Warn("failed to remove the spent ephemeral registration") + logging.WithError(err).Warn("failed to remove the spent ephemeral registration") } } @@ -328,7 +328,7 @@ func splitLabels(s string) []string { return lbls } -// initLogging setup the global logrus logger. +// initLogging sets up the process-wide logger. func initLogging(cfg *config.Config) { callPrettyfier := func(f *runtime.Frame) (string, string) { // get function name @@ -341,35 +341,35 @@ func initLogging(cfg *config.Config) { } isTerm := isatty.IsTerminal(os.Stdout.Fd()) - format := &log.TextFormatter{ + logging.SetTextFormat(logging.TextOptions{ DisableColors: !isTerm, FullTimestamp: true, CallerPrettyfier: callPrettyfier, - } - log.SetFormatter(report.MaskingFormatter(format)) + Filter: report.MaskGlobal, + }) l := cfg.Log.Level if l == "" { - log.Infof("Log level not set, sticking to info") + logging.Infof("Log level not set, sticking to info") return } - level, err := log.ParseLevel(l) + level, err := logging.ParseLevel(l) if err != nil { - log.WithError(err). + logging.WithError(err). Errorf("invalid log level: %q", l) } // debug level switch level { - case log.DebugLevel, log.TraceLevel: - log.SetReportCaller(true) // Only in debug or trace because it takes a performance toll - log.Infof("Log level %s requested, setting up report caller for further debugging", level) + case logging.LevelDebug, logging.LevelTrace: + logging.SetReportCaller(true) // Only in debug or trace because it takes a performance toll + logging.Infof("Log level %s requested, setting up report caller for further debugging", level) } - if log.GetLevel() != level { - log.Infof("log level set to %v", level) - log.SetLevel(level) + if logging.GetLevel() != level { + logging.Infof("log level set to %v", level) + logging.SetLevel(level) } } diff --git a/internal/app/cmd/daemon_test.go b/internal/app/cmd/daemon_test.go index 308b970a..a13cb580 100644 --- a/internal/app/cmd/daemon_test.go +++ b/internal/app/cmd/daemon_test.go @@ -11,17 +11,17 @@ import ( "net/http/httptest" "os" "path/filepath" + "strings" "sync/atomic" "testing" "time" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/config" - "gitea.com/gitea/runner/internal/pkg/report" "connectrpc.com/connect" runnerv1 "gitea.dev/actionslib/runner/v1" "gitea.dev/actionslib/runner/v1/runnerv1connect" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/structpb" ) @@ -64,24 +64,23 @@ func TestGetDockerSocketPathUsesConfigAndEnvironment(t *testing.T) { } func TestInitLoggingSetsLevelAndCaller(t *testing.T) { - oldLevel := log.GetLevel() - oldReportCaller := log.StandardLogger().ReportCaller + oldLevel, oldOut := logging.GetLevel(), logging.Output() t.Cleanup(func() { - log.SetLevel(oldLevel) - log.SetReportCaller(oldReportCaller) + logging.SetLevel(oldLevel) + logging.SetOutput(oldOut) + logging.SetReportCaller(false) + logging.SetTextFormat(logging.TextOptions{}) }) - oldFormatter := log.StandardLogger().Formatter - t.Cleanup(func() { log.SetFormatter(oldFormatter) }) - cfg := &config.Config{} cfg.Log.Level = "debug" initLogging(cfg) - require.Equal(t, log.DebugLevel, log.GetLevel()) - require.True(t, log.StandardLogger().ReportCaller) - // act plans a job on this logger, so a live task's secrets have to be masked out of it - require.IsType(t, report.MaskingFormatter(nil), log.StandardLogger().Formatter) + require.Equal(t, logging.LevelDebug, logging.GetLevel()) + var buf strings.Builder + logging.SetOutput(&buf) + logging.Debugf("line") + require.Contains(t, buf.String(), "[daemon_test.go:") } var errUnregisteredRunner = errors.New("rpc error: code = Unauthenticated desc = unregistered runner") diff --git a/internal/app/cmd/exec.go b/internal/app/cmd/exec.go index bed8d891..3daa1dec 100644 --- a/internal/app/cmd/exec.go +++ b/internal/app/cmd/exec.go @@ -31,7 +31,6 @@ import ( "gitea.dev/actionslib/pkg/model" "github.com/joho/godotenv" "github.com/moby/moby/api/types/container" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/term" ) @@ -106,7 +105,7 @@ func (i *executeArgs) LoadSecrets() map[string]string { secretPairParts := strings.SplitN(secretPair, "=", 2) secretPairParts[0] = strings.ToUpper(secretPairParts[0]) if strings.EqualFold(s[secretPairParts[0]], secretPairParts[0]) { - log.Errorf("Secret %s is already defined (secrets are case insensitive)", secretPairParts[0]) + logging.Errorf("Secret %s is already defined (secrets are case insensitive)", secretPairParts[0]) } if len(secretPairParts) == 2 { s[secretPairParts[0]] = secretPairParts[1] @@ -117,7 +116,7 @@ func (i *executeArgs) LoadSecrets() map[string]string { val, err := term.ReadPassword(int(os.Stdin.Fd())) fmt.Println() if err != nil { - log.Errorf("failed to read input: %v", err) + logging.Errorf("failed to read input: %v", err) os.Exit(1) } s[secretPairParts[0]] = string(val) @@ -130,7 +129,8 @@ func readEnvs(path string, envs map[string]string) bool { if _, err := os.Stat(path); err == nil { env, err := godotenv.Read(path) if err != nil { - log.Fatalf("Error loading from %s: %v", path, err) + logging.Errorf("Error loading from %s: %v", path, err) + os.Exit(1) } maps.Copy(envs, env) return true @@ -221,7 +221,8 @@ func (i *executeArgs) Workdir() string { func (i *executeArgs) resolve(path string) string { basedir, err := filepath.Abs(i.workdir) if err != nil { - log.Fatal(err) + logging.Error(err) + os.Exit(1) } if path == "" { return path @@ -337,7 +338,7 @@ func runExecList(planner model.WorkflowPlanner, execArgs *executeArgs) error { var filterEventName string if len(execArgs.event) > 0 { - log.Infof("Using chosed event for filtering: %s", execArgs.event) + logging.Infof("Using chosed event for filtering: %s", execArgs.event) filterEventName = execArgs.event } else if execArgs.autodetectEvent { // collect all events from loaded workflows @@ -345,7 +346,7 @@ func runExecList(planner model.WorkflowPlanner, execArgs *executeArgs) error { // set default event type to first event from many available // this way user dont have to specify the event. - log.Infof("Using first detected workflow event for filtering: %s", events[0]) + logging.Infof("Using first detected workflow event for filtering: %s", events[0]) filterEventName = events[0] } @@ -353,19 +354,19 @@ func runExecList(planner model.WorkflowPlanner, execArgs *executeArgs) error { var err error switch { case execArgs.job != "": - log.Infof("Preparing plan with a job: %s", execArgs.job) + logging.Infof("Preparing plan with a job: %s", execArgs.job) filterPlan, err = planner.PlanJob(execArgs.job) if err != nil { return err } case filterEventName != "": - log.Infof("Preparing plan for a event: %s", filterEventName) + logging.Infof("Preparing plan for a event: %s", filterEventName) filterPlan, err = planner.PlanEvent(filterEventName) if err != nil { return err } default: - log.Infof("Preparing plan with all jobs") + logging.Infof("Preparing plan with all jobs") filterPlan, err = planner.PlanAll() if err != nil { return err @@ -445,30 +446,30 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command switch { case len(execArgs.event) > 0: - log.Infof("Using chosed event for filtering: %s", execArgs.event) + logging.Infof("Using chosed event for filtering: %s", execArgs.event) eventName = execArgs.event case len(events) == 1 && len(events[0]) > 0: - log.Infof("Using the only detected workflow event: %s", events[0]) + logging.Infof("Using the only detected workflow event: %s", events[0]) eventName = events[0] case execArgs.autodetectEvent && len(events) > 0 && len(events[0]) > 0: // set default event type to first event from many available // this way user dont have to specify the event. - log.Infof("Using first detected workflow event: %s", events[0]) + logging.Infof("Using first detected workflow event: %s", events[0]) eventName = events[0] default: - log.Infof("Using default workflow event: push") + logging.Infof("Using default workflow event: push") eventName = "push" } // build the plan for this run if execArgs.job != "" { - log.Infof("Planning job: %s", execArgs.job) + logging.Infof("Planning job: %s", execArgs.job) plan, err = planner.PlanJob(execArgs.job) if err != nil { return err } } else { - log.Infof("Planning jobs for event: %s", eventName) + logging.Infof("Planning jobs for event: %s", eventName) plan, err = planner.PlanEvent(eventName) if err != nil { return err @@ -488,7 +489,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command if err != nil { return err } - log.Infof("cache handler listens on: %v", handler.ExternalURL()) + logging.Infof("cache handler listens on: %v", handler.ExternalURL()) execArgs.cacheHandler = handler if len(execArgs.artifactServerAddr) == 0 { @@ -564,7 +565,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command } artifactCancel := artifacts.Serve(ctx, execArgs.artifactServerPath, execArgs.artifactServerAddr, execArgs.artifactServerPort) - log.Debugf("artifacts server started at %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort) + logging.Debugf("artifacts server started at %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort) ctx = common.WithDryrun(ctx, execArgs.dryrun) executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error { diff --git a/internal/app/cmd/register.go b/internal/app/cmd/register.go index 249297fb..fc9e5c1e 100644 --- a/internal/app/cmd/register.go +++ b/internal/app/cmd/register.go @@ -13,6 +13,7 @@ import ( goruntime "runtime" "strings" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/app/run" "gitea.com/gitea/runner/internal/pkg/client" "gitea.com/gitea/runner/internal/pkg/config" @@ -24,28 +25,27 @@ import ( pingv1 "gitea.dev/actionslib/ping/v1" runnerv1 "gitea.dev/actionslib/runner/v1" "github.com/mattn/go-isatty" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // runRegister registers a runner to the server func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - log.SetReportCaller(false) + logging.SetReportCaller(false) isTerm := isatty.IsTerminal(os.Stdout.Fd()) - log.SetFormatter(&log.TextFormatter{ + logging.SetTextFormat(logging.TextOptions{ DisableColors: !isTerm, DisableTimestamp: true, }) - log.SetLevel(log.DebugLevel) + logging.SetLevel(logging.LevelDebug) - log.Infof("Registering runner, arch=%s, os=%s, version=%s.", + logging.Infof("Registering runner, arch=%s, os=%s, version=%s.", goruntime.GOARCH, goruntime.GOOS, ver.Version()) // runner always needs root permission if os.Getuid() != 0 { // TODO: use a better way to check root permission - log.Warnf("Runner in user-mode.") + logging.Warnf("Runner in user-mode.") } if regArgs.NoInteractive { @@ -55,7 +55,8 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) } else { go func() { if err := registerInteractive(ctx, *configFile, regArgs); err != nil { - log.Fatal(err) + logging.Error(err) + os.Exit(1) return } os.Exit(0) @@ -182,13 +183,13 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co for _, l := range cfg.Runner.Labels { _, err := labels.Parse(l) if err != nil { - log.WithError(err).Warnf("ignored invalid label %q", l) + logging.WithError(err).Warnf("ignored invalid label %q", l) continue } ls = append(ls, l) } if len(ls) == 0 { - log.Warn("no valid labels configured in config file, runner may not be able to pick up jobs") + logging.Warn("no valid labels configured in config file, runner may not be able to pick up jobs") } r.Labels = ls return StageWaitingForRegistration @@ -201,7 +202,7 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co } if validateLabels(r.Labels) != nil { - log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest)") + logging.Info("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest)") r.Labels = nil return StageInputLabels } @@ -273,11 +274,11 @@ func registerInteractive(ctx context.Context, configFile string, regArgs *regist stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString), cfg) if stage == StageWaitingForRegistration { - log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels) + logging.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels) if err := doRegister(ctx, cfg, inputs); err != nil { return fmt.Errorf("failed to register runner: %w", err) } - log.Infof("Runner registered successfully.") + logging.Infof("Runner registered successfully.") return nil } @@ -286,7 +287,7 @@ func registerInteractive(ctx context.Context, configFile string, regArgs *regist } if stage <= StageUnknown { - log.Errorf("Invalid input, please re-run act command.") + logging.Errorf("Invalid input, please re-run act command.") return nil } } @@ -295,18 +296,18 @@ func registerInteractive(ctx context.Context, configFile string, regArgs *regist func printStageHelp(stage registerStage) { switch stage { case StageOverwriteLocalConfig: - log.Infoln("Runner is already registered, overwrite local config? [y/N]") + logging.Info("Runner is already registered, overwrite local config? [y/N]") case StageInputInstance: - log.Infoln("Enter the Gitea instance URL (for example, https://gitea.com/):") + logging.Info("Enter the Gitea instance URL (for example, https://gitea.com/):") case StageInputToken: - log.Infoln("Enter the runner token:") + logging.Info("Enter the runner token:") case StageInputRunnerName: hostname, _ := os.Hostname() - log.Infof("Enter the runner name (if set empty, use hostname: %s):\n", hostname) + logging.Infof("Enter the runner name (if set empty, use hostname: %s):\n", hostname) case StageInputLabels: - log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest):") + logging.Info("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest):") case StageWaitingForRegistration: - log.Infoln("Waiting for registration...") + logging.Info("Waiting for registration...") } } @@ -322,7 +323,7 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi // specify labels in config file. if len(cfg.Runner.Labels) > 0 { if regArgs.Labels != "" { - log.Warn("Labels from command will be ignored, use labels defined in config file.") + logging.Warn("Labels from command will be ignored, use labels defined in config file.") } inputs.Labels = cfg.Runner.Labels } @@ -332,16 +333,16 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi if inputs.RunnerName == "" { inputs.RunnerName, _ = os.Hostname() - log.Infof("Runner name is empty, use hostname '%s'.", inputs.RunnerName) + logging.Infof("Runner name is empty, use hostname '%s'.", inputs.RunnerName) } if err := inputs.validate(); err != nil { - log.WithError(err).Errorf("Invalid input, please re-run act command.") + logging.WithError(err).Errorf("Invalid input, please re-run act command.") return err } if err := doRegister(ctx, cfg, inputs); err != nil { return fmt.Errorf("failed to register runner: %w", err) } - log.Infof("Runner registered successfully.") + logging.Infof("Runner registered successfully.") return nil } @@ -355,7 +356,7 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) // Best-effort guard: if the lock file can't be created, warn and // register anyway; writing the runner file will surface any real // permission problem with a clearer error. - log.Warnf("could not lock runner file %q, continuing without the single-process guard: %v", cfg.Runner.File, err) + logging.Warnf("could not lock runner file %q, continuing without the single-process guard: %v", cfg.Runner.File, err) default: defer func() { _ = releaseLock() }() } @@ -375,7 +376,7 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) })); err != nil { return fmt.Errorf("cannot ping the Gitea instance server: %w", err) } - log.Debugln("Successfully pinged the Gitea instance server") + logging.Debug("Successfully pinged the Gitea instance server") reg := &config.Registration{ Name: inputs.RunnerName, @@ -403,7 +404,7 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) Capabilities: run.RunnerCapabilities(), })) if err != nil { - log.WithError(err).Error("poller: cannot register new runner") + logging.WithError(err).Error("poller: cannot register new runner") return err } @@ -414,7 +415,7 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) if inputs.Ephemeral != resp.Msg.Runner.Ephemeral { // TODO we cannot remove the configuration via runner api, if we return an error here we just fill the database - log.Error("poller: cannot register new runner as ephemeral upgrade Gitea to gain security, run-once will be used automatically") + logging.Error("poller: cannot register new runner as ephemeral upgrade Gitea to gain security, run-once will be used automatically") } if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil { diff --git a/internal/app/poll/poller.go b/internal/app/poll/poller.go index 235b3de7..3fb04f96 100644 --- a/internal/app/poll/poller.go +++ b/internal/app/poll/poller.go @@ -12,13 +12,13 @@ import ( "sync/atomic" "time" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/client" "gitea.com/gitea/runner/internal/pkg/config" "gitea.com/gitea/runner/internal/pkg/metrics" "connectrpc.com/connect" runnerv1 "gitea.dev/actionslib/runner/v1" - log "github.com/sirupsen/logrus" ) // TaskRunner abstracts task execution so the poller can be tested @@ -236,9 +236,9 @@ func (p *Poller) reportAvailability(ready bool, reason string) { defer p.availabilityMu.Unlock() switch { case !ready && p.availabilityReady: - log.Warnf("runner temporarily unavailable: %s", reason) + logging.Warnf("runner temporarily unavailable: %s", reason) case ready && !p.availabilityReady: - log.Info("runner local health recovered, resuming task polling") + logging.Info("runner local health recovered, resuming task polling") } p.availabilityReady = ready p.availabilityReason = reason @@ -339,12 +339,12 @@ func (p *Poller) runTaskWithRecover(ctx context.Context, task *runnerv1.Task) { defer func() { if r := recover(); r != nil { err := fmt.Errorf("panic: %v", r) - log.WithError(err).Error("panic in runTaskWithRecover") + logging.WithError(err).Error("panic in runTaskWithRecover") } }() if err := p.runner.Run(ctx, task); err != nil { - log.WithError(err).Error("failed to run task") + logging.WithError(err).Error("failed to run task") } } @@ -366,7 +366,7 @@ func (p *Poller) fetchTask(ctx context.Context, s *workerState) (*runnerv1.Task, if errors.Is(err, context.DeadlineExceeded) { if !s.fetchTimedOut { s.fetchTimedOut = true - log.Warnf("fetching a task timed out after %s, raise runner.fetch_timeout if this persists", p.cfg.Runner.FetchTimeout) + logging.Warnf("fetching a task timed out after %s, raise runner.fetch_timeout if this persists", p.cfg.Runner.FetchTimeout) } s.consecutiveEmpty++ metrics.PollFetchTotal.WithLabelValues(metrics.LabelResultEmpty).Inc() @@ -381,7 +381,7 @@ func (p *Poller) fetchTask(ctx context.Context, s *workerState) (*runnerv1.Task, // polling and let the daemon exit with an error instead of spinning. rejected := client.IsRegistrationRejected(err) if rejected || connect.CodeOf(err) == connect.CodeUnauthenticated { - log.WithError(err).Error("server rejected the runner as unregistered, stopping poller") + logging.WithError(err).Error("server rejected the runner as unregistered, stopping poller") p.unregistered.Store(true) p.registrationRejected.Store(rejected) p.shutdownPolling() @@ -390,9 +390,9 @@ func (p *Poller) fetchTask(ctx context.Context, s *workerState) (*runnerv1.Task, // Not a long poll, so a deadline can mean the server assigned a task // this runner never received. if errors.Is(err, context.DeadlineExceeded) { - log.WithError(err).Errorf("fetching a task timed out after %s", p.cfg.Runner.FetchTimeout) + logging.WithError(err).Errorf("fetching a task timed out after %s", p.cfg.Runner.FetchTimeout) } else { - log.WithError(err).Error("failed to fetch task") + logging.WithError(err).Error("failed to fetch task") } p.lastPollFailed.Store(true) s.consecutiveErrors++ diff --git a/internal/app/poll/poller_test.go b/internal/app/poll/poller_test.go index 6936617a..8d876188 100644 --- a/internal/app/poll/poller_test.go +++ b/internal/app/poll/poller_test.go @@ -11,12 +11,12 @@ import ( "testing" "time" + "gitea.com/gitea/runner/act/common/logging/logtest" "gitea.com/gitea/runner/internal/pkg/client/mocks" "gitea.com/gitea/runner/internal/pkg/config" connect_go "connectrpc.com/connect" runnerv1 "gitea.dev/actionslib/runner/v1" - "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -87,8 +87,7 @@ func TestPoller_FetchTimeoutIsNoSignal(t *testing.T) { p := &Poller{client: client, cfg: cfg} p.lastPollFailed.Store(true) - hook := test.NewGlobal() - defer hook.Reset() + hook := logtest.SetDefault(t) s := &workerState{consecutiveErrors: 2} _, ok := p.fetchTask(context.Background(), s) @@ -99,7 +98,7 @@ func TestPoller_FetchTimeoutIsNoSignal(t *testing.T) { // An idle runner against a server holding the request open must not flood. _, _ = p.fetchTask(context.Background(), s) - assert.Len(t, hook.AllEntries(), 1) + assert.Len(t, hook.AllRecords(), 1) } // TestPoller_FetchUnauthenticatedStopsPolling verifies that an Unauthenticated diff --git a/internal/app/run/post_task_script.go b/internal/app/run/post_task_script.go index 89ef2cc2..60177f8b 100644 --- a/internal/app/run/post_task_script.go +++ b/internal/app/run/post_task_script.go @@ -14,13 +14,13 @@ import ( "time" "gitea.com/gitea/runner/act/common" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/config" "gitea.com/gitea/runner/internal/pkg/metrics" "gitea.com/gitea/runner/internal/pkg/process" "gitea.com/gitea/runner/internal/pkg/report" runnerv1 "gitea.dev/actionslib/runner/v1" - log "github.com/sirupsen/logrus" ) func (r *Runner) runPostTaskScript(ctx context.Context, reporter *report.Reporter, task *runnerv1.Task, workdir string) { @@ -38,7 +38,7 @@ func (r *Runner) runPostTaskScript(ctx context.Context, reporter *report.Reporte defer cancel() env := r.postTaskScriptEnv(reporter, task, workdir) - log.Infof("running post-task script %q for task %d", script, task.Id) + logging.Infof("running post-task script %q for task %d", script, task.Id) cmd := exec.CommandContext(scriptCtx, script) cmd.Env = envListFromMap(env) @@ -55,11 +55,11 @@ func (r *Runner) runPostTaskScript(ctx context.Context, reporter *report.Reporte treeKill := process.NewTreeKill(cmd) if err := cmd.Start(); err != nil { - log.Warnf("post-task script %q for task %d: %v", script, task.Id, err) + logging.Warnf("post-task script %q for task %d: %v", script, task.Id, err) return } if k, kerr := treeKill.Capture(cmd.Process); kerr != nil { - log.Warnf("post-task script %q for task %d: process tree kill setup failed, falling back to single-process kill: %v", script, task.Id, kerr) + logging.Warnf("post-task script %q for task %d: process tree kill setup failed, falling back to single-process kill: %v", script, task.Id, kerr) } else { defer k.Close() } @@ -71,14 +71,14 @@ func (r *Runner) runPostTaskScript(ctx context.Context, reporter *report.Reporte common.FlushWriter(stderr) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - log.Warnf("post-task script %q for task %d: %v", script, task.Id, err) + logging.Warnf("post-task script %q for task %d: %v", script, task.Id, err) return } if exitErr, ok := errors.AsType[*exec.ExitError](err); ok { - log.Warnf("post-task script %q for task %d exited with code %d", script, task.Id, exitErr.ExitCode()) + logging.Warnf("post-task script %q for task %d exited with code %d", script, task.Id, exitErr.ExitCode()) return } - log.Warnf("post-task script %q for task %d: %v", script, task.Id, err) + logging.Warnf("post-task script %q for task %d: %v", script, task.Id, err) } } @@ -125,7 +125,7 @@ func envListFromMap(env map[string]string) []string { // cmd.Wait() returns. func postTaskScriptLogWriter(stream string) io.Writer { return common.NewLineWriter(func(line string) bool { - log.Infof("post-task script %s: %s", stream, strings.TrimRight(line, "\r\n")) + logging.Infof("post-task script %s: %s", stream, strings.TrimRight(line, "\r\n")) return true }) } diff --git a/internal/app/run/proxy.go b/internal/app/run/proxy.go index 125458bb..ea0dca90 100644 --- a/internal/app/run/proxy.go +++ b/internal/app/run/proxy.go @@ -10,9 +10,9 @@ import ( "slices" "strings" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/act/container" - log "github.com/sirupsen/logrus" "golang.org/x/net/http/httpproxy" ) @@ -87,10 +87,10 @@ func BypassProxyForDockerHost(dockerHost string) { noProxy := appendNoProxy(cfg.NoProxy, host) for _, name := range []string{"no_proxy", "NO_PROXY"} { if err := os.Setenv(name, noProxy); err != nil { - log.Warnf("cannot set %s for the runner process: %v", name, err) + logging.Warnf("cannot set %s for the runner process: %v", name, err) } } - log.Debugf("docker host %s is reached directly, no_proxy is now %q", host, noProxy) + logging.Debugf("docker host %s is reached directly, no_proxy is now %q", host, noProxy) } // WarnIfDaemonHasNoProxy points at the one part the runner cannot set: the docker daemon @@ -101,11 +101,11 @@ func WarnIfDaemonHasNoProxy(ctx context.Context) { } info, err := container.GetHostInfo(ctx) if err != nil { - log.Debugf("cannot read the docker daemon's proxy configuration: %v", err) + logging.Debugf("cannot read the docker daemon's proxy configuration: %v", err) return } if info.HTTPProxy == "" && info.HTTPSProxy == "" { - log.Warn("the runner has a proxy but the docker daemon reports none, so image pulls will not use it: https://docs.docker.com/engine/daemon/proxy/") + logging.Warn("the runner has a proxy but the docker daemon reports none, so image pulls will not use it: https://docs.docker.com/engine/daemon/proxy/") } } diff --git a/internal/app/run/runner.go b/internal/app/run/runner.go index 84c90158..e2e7df22 100644 --- a/internal/app/run/runner.go +++ b/internal/app/run/runner.go @@ -45,7 +45,6 @@ import ( "gitea.dev/actionslib/pkg/model" runnerv1 "gitea.dev/actionslib/runner/v1" docker_container "github.com/moby/moby/api/types/container" - log "github.com/sirupsen/logrus" "go.yaml.in/yaml/v4" ) @@ -113,7 +112,7 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) Logger: logging.Default().With("module", "cache_request"), }) if err != nil { - log.Errorf("cannot init cache server, it will be disabled: %v", err) + logging.Errorf("cannot init cache server, it will be disabled: %v", err) // go on } else { cacheHandler = handler @@ -184,7 +183,7 @@ func (r *Runner) OnIdle(ctx context.Context) { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() if err := removeOrphanKubernetesResources(ctx, r.kubernetesOptions(nil), r.now().Add(-r.cfg.Runner.WorkdirCleanupAge)); err != nil { - log.Warnf("failed to clean up job pods left behind by earlier jobs: %v", err) + logging.Warnf("failed to clean up job pods left behind by earlier jobs: %v", err) } } } @@ -195,10 +194,10 @@ func (r *Runner) cleanupOrphanDockerResources(ctx context.Context) { } cutoff := r.now().Add(-r.cfg.Runner.WorkdirCleanupAge) if err := removeOrphanNetworks(ctx, r.uuid, r.isolatedCacheContainer(), cutoff); err != nil { - log.Warnf("failed to clean up networks left behind by earlier jobs: %v", err) + logging.Warnf("failed to clean up networks left behind by earlier jobs: %v", err) } if err := removeOrphanJobVolumes(ctx, r.uuid, cutoff); err != nil { - log.Warnf("failed to clean up volumes left behind by earlier jobs: %v", err) + logging.Warnf("failed to clean up volumes left behind by earlier jobs: %v", err) } } @@ -255,7 +254,7 @@ func (r *Runner) cleanupStaleDirs(ctx context.Context, root string, match func(n if errors.Is(err, os.ErrNotExist) { return } - log.Warnf("failed to list directory %s for stale cleanup: %v", root, err) + logging.Warnf("failed to list directory %s for stale cleanup: %v", root, err) return } @@ -275,7 +274,7 @@ func (r *Runner) cleanupStaleDirs(ctx context.Context, root string, match func(n } info, err := entry.Info() if err != nil { - log.Warnf("failed to stat %s: %v", filepath.Join(root, entry.Name()), err) + logging.Warnf("failed to stat %s: %v", filepath.Join(root, entry.Name()), err) continue } if info.ModTime().After(cutoff) { @@ -283,10 +282,10 @@ func (r *Runner) cleanupStaleDirs(ctx context.Context, root string, match func(n } dir := filepath.Join(root, entry.Name()) if err := os.RemoveAll(dir); err != nil { - log.Warnf("failed to clean stale directory %s: %v", dir, err) + logging.Warnf("failed to clean stale directory %s: %v", dir, err) continue } - log.Infof("cleaned stale directory %s", dir) + logging.Infof("cleaned stale directory %s", dir) } } @@ -331,7 +330,7 @@ func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error { } _ = reporter.Close(lastWords) if err := cleanupJobVolumes(ctx, volumeCleanup); err != nil { - log.Warnf("task %d volume cleanup after reporting: %v", task.Id, err) + logging.Warnf("task %d volume cleanup after reporting: %v", task.Id, err) } metrics.JobDuration.Observe(time.Since(start).Seconds()) @@ -463,9 +462,8 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report. if err != nil { return err } - log.WithFields(taskLogFields(task, jobID)). - WithField("actions_url", r.getDefaultActionsURL(task)). - WithField("address", r.client.Address()). + logging.With(taskLogFields(task, jobID)...). + With("actions_url", r.getDefaultActionsURL(task), "address", r.client.Address()). Info("task started") plan, err := model.CombineWorkflowPlanner(workflow).PlanJob(jobID) @@ -648,7 +646,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report. // add logger recorders ctx = common.WithLoggerHandler(ctx, reporter) - if !log.IsLevelEnabled(log.DebugLevel) { + if !logging.Enabled(logging.LevelDebug) { ctx = runner.WithJobLoggerFactory(ctx, NullLogger{}) } @@ -659,7 +657,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report. // Remove the entire task-specific directory (e.g. /workspace/). taskDir := filepath.FromSlash("/" + workdirParent) if err := os.RemoveAll(taskDir); err != nil { - log.Warnf("failed to clean up workspace %s: %v", taskDir, err) + logging.Warnf("failed to clean up workspace %s: %v", taskDir, err) } } @@ -741,7 +739,7 @@ func (r *Runner) registerExternalCacheJob(token, repo, publicURL string, reporte if err := postInternalCache(base+"/_internal/register", r.cfg.Cache.ExternalSecret, map[string]any{ "token": token, "repo": repo, "public_url": publicURL, }); err != nil { - log.Warnf("cache external_server register failed (%s): %v", base, err) + logging.Warnf("cache external_server register failed (%s): %v", base, err) if reporter != nil { reporter.Logf("::warning::%s", runner.EscapeCommandData(fmt.Sprintf( "cache external_server register failed (%s): %v — cache requests from this job will be unauthenticated and likely return 401", base, err))) @@ -750,7 +748,7 @@ func (r *Runner) registerExternalCacheJob(token, repo, publicURL string, reporte return func() { if err := postInternalCache(base+"/_internal/revoke", r.cfg.Cache.ExternalSecret, map[string]any{"token": token}); err != nil { - log.Warnf("cache external_server revoke failed (%s): %v", base, err) + logging.Warnf("cache external_server revoke failed (%s): %v", base, err) if reporter != nil { reporter.Logf("::warning::%s", runner.EscapeCommandData(fmt.Sprintf( "cache external_server revoke failed (%s): %v", base, err))) @@ -893,9 +891,9 @@ func warnIgnoredCachePolicy(cfg *config.Config) { cfg.Cache.SizeLimit != defaults.SizeLimit switch { case cfg.Cache.ExternalServer != "" && (limits || cfg.Cache.SweepInterval != defaults.SweepInterval): - log.Warn("cache eviction settings are ignored when cache.external_server is set; configure them on that server instead") + logging.Warn("cache eviction settings are ignored when cache.external_server is set; configure them on that server instead") case cfg.Cache.S3 != nil && limits: - log.Warn("cache eviction settings are ignored when cache.s3 is set; use lifecycle rules on the bucket instead") + logging.Warn("cache eviction settings are ignored when cache.s3 is set; use lifecycle rules on the bucket instead") } } @@ -913,7 +911,7 @@ func warnIgnoredCacheSecret(cfg *config.Config) { if cfg.Cache.ExternalSecretFile != "" { key = "cache.external_secret_file" } - log.Warnf("%s is set but cache.external_server is not; the built-in cache server does not use a shared secret, so the value is ignored", key) + logging.Warnf("%s is set but cache.external_server is not; the built-in cache server does not use a shared secret, so the value is ignored", key) } // cacheForJob returns the cache address for the job, and the container to attach to its network to reach it there. @@ -943,25 +941,26 @@ func (r *Runner) detectIsolatedCacheContainer() string { defer cancel() cacheContainer, err := container.IsolatedCacheContainer(ctx, addr, r.cfg.Container.Network) if err != nil { - log.Warnf("cannot check whether jobs reach the cache server: %v", err) + logging.Warnf("cannot check whether jobs reach the cache server: %v", err) } return cacheContainer } -func taskLogFields(task *runnerv1.Task, job string) log.Fields { +// taskLogFields returns key/value pairs for logging.With. +func taskLogFields(task *runnerv1.Task, job string) []any { taskContext := task.GetContext().GetFields() - fields := log.Fields{ - "task_id": task.Id, - "job": job, - "repository": taskContext["repository"].GetStringValue(), - "run_id": taskContext["run_id"].GetStringValue(), - "run_number": taskContext["run_number"].GetStringValue(), + fields := []any{ + "task_id", task.Id, + "job", job, + "repository", taskContext["repository"].GetStringValue(), + "run_id", taskContext["run_id"].GetStringValue(), + "run_number", taskContext["run_number"].GetStringValue(), } if parts := strings.Split(taskContext["gitea_runtime_token"].GetStringValue(), "."); len(parts) == 3 { var claims struct{ JobID int64 } // Gitea's actionsClaims fields are untagged, so the claim key is "JobID" payload, err := base64.RawURLEncoding.DecodeString(parts[1]) if err == nil && json.Unmarshal(payload, &claims) == nil && claims.JobID > 0 { - fields["job_id"] = claims.JobID + fields = append(fields, "job_id", claims.JobID) } } return fields diff --git a/internal/app/run/runner_test.go b/internal/app/run/runner_test.go index de2d02a5..de497707 100644 --- a/internal/app/run/runner_test.go +++ b/internal/app/run/runner_test.go @@ -28,7 +28,6 @@ import ( "connectrpc.com/connect" "gitea.dev/actionslib/pkg/model" runnerv1 "gitea.dev/actionslib/runner/v1" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -418,13 +417,12 @@ func TestRestoreCalledWorkflowContext(t *testing.T) { } func TestTaskLogFields(t *testing.T) { - fields := func(token string) log.Fields { + fields := func(token string) []any { taskContext, err := structpb.NewStruct(map[string]any{"repository": "owner/repo", "run_id": "159", "run_number": "14", "gitea_runtime_token": token}) require.NoError(t, err) return taskLogFields(&runnerv1.Task{Id: 72, Context: taskContext}, "build") } - want := log.Fields{"task_id": int64(72), "job": "build", "repository": "owner/repo", "run_id": "159", "run_number": "14"} + want := []any{"task_id", int64(72), "job", "build", "repository", "owner/repo", "run_id", "159", "run_number", "14"} assert.Equal(t, want, fields("malformed")) - want["job_id"] = int64(221) - assert.Equal(t, want, fields("header."+base64.RawURLEncoding.EncodeToString([]byte(`{"TaskID":72,"JobID":221}`))+".signature")) + assert.Equal(t, append(want, "job_id", int64(221)), fields("header."+base64.RawURLEncoding.EncodeToString([]byte(`{"TaskID":72,"JobID":221}`))+".signature")) } diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml index ca44160a..5a90db19 100644 --- a/internal/pkg/config/config.example.yaml +++ b/internal/pkg/config/config.example.yaml @@ -6,7 +6,7 @@ # each task's log kept under log.job. Neither controls how workflow step output is streamed # to the Gitea UI; tune that with runner.log_report_* below. log: - # logrus severity: trace, debug, info, warn, error, fatal, panic. + # Severity: trace, debug, info, warn, error. fatal and panic are accepted and mean error. # trace and debug turn on caller/file:line in log lines. Default if omitted: info. #level: info # Write a copy of each task's log to dir as -task-.log, so a job's output diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 73b19012..c6a8f06d 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -15,9 +15,10 @@ import ( "strings" "time" + "gitea.com/gitea/runner/act/common/logging" + "github.com/docker/go-units" "github.com/joho/godotenv" - log "github.com/sirupsen/logrus" "go.yaml.in/yaml/v4" "golang.org/x/net/http/httpguts" ) @@ -408,26 +409,26 @@ func LoadDefault(file string) (*Config, error) { // Validate and fix invalid config combinations to prevent confusing behavior. if cfg.Runner.ToolCacheMode == ToolCacheModeShared && cfg.Runner.Capacity > 1 { - log.Warnf("runner.tool_cache_mode %q with capacity %d: two jobs writing the same tool version at once corrupt it", + logging.Warnf("runner.tool_cache_mode %q with capacity %d: two jobs writing the same tool version at once corrupt it", ToolCacheModeShared, cfg.Runner.Capacity) } if cfg.Runner.FetchTimeout > RequestTimeout { - log.Warnf("fetch_timeout (%v) exceeds the RPC timeout (%v), capping it", cfg.Runner.FetchTimeout, RequestTimeout) + logging.Warnf("fetch_timeout (%v) exceeds the RPC timeout (%v), capping it", cfg.Runner.FetchTimeout, RequestTimeout) cfg.Runner.FetchTimeout = RequestTimeout } if cfg.Runner.FetchIntervalMax < cfg.Runner.FetchInterval { - log.Warnf("fetch_interval_max (%v) is less than fetch_interval (%v), setting fetch_interval_max to fetch_interval", + logging.Warnf("fetch_interval_max (%v) is less than fetch_interval (%v), setting fetch_interval_max to fetch_interval", cfg.Runner.FetchIntervalMax, cfg.Runner.FetchInterval) cfg.Runner.FetchIntervalMax = cfg.Runner.FetchInterval } if cfg.Runner.LogReportMaxLatency >= cfg.Runner.LogReportInterval { - log.Warnf("log_report_max_latency (%v) >= log_report_interval (%v), the max-latency timer will never fire before the periodic ticker; consider lowering log_report_max_latency", + logging.Warnf("log_report_max_latency (%v) >= log_report_interval (%v), the max-latency timer will never fire before the periodic ticker; consider lowering log_report_max_latency", cfg.Runner.LogReportMaxLatency, cfg.Runner.LogReportInterval) } // although `container.network_mode` will be deprecated, but we have to be compatible with it for now. if cfg.Container.NetworkMode != "" && cfg.Container.Network == "" { - log.Warn("You are trying to use deprecated configuration item of `container.network_mode`, please use `container.network` instead.") + logging.Warn("You are trying to use deprecated configuration item of `container.network_mode`, please use `container.network` instead.") if cfg.Container.NetworkMode == "bridge" { // Previously, if the value of `container.network_mode` is `bridge`, we will create a new network for job. // But “bridge” is easily confused with the bridge network created by Docker by default. @@ -493,7 +494,7 @@ func warnUnknownKeys(file string, document *yaml.Node) { var loadErrs *yaml.LoadErrors if err := document.Load(&Config{}, yaml.WithV3Defaults(), yaml.WithKnownFields()); errors.As(err, &loadErrs) { for _, loadErr := range loadErrs.Errors { - log.Warnf("config file %q: %s, it will be ignored", file, loadErr) + logging.Warnf("config file %q: %s, it will be ignored", file, loadErr) } } } diff --git a/internal/pkg/config/config_test.go b/internal/pkg/config/config_test.go index e4786713..230d8c1d 100644 --- a/internal/pkg/config/config_test.go +++ b/internal/pkg/config/config_test.go @@ -16,7 +16,8 @@ import ( "testing" "time" - "github.com/sirupsen/logrus/hooks/test" + "gitea.com/gitea/runner/act/common/logging/logtest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -391,14 +392,13 @@ func TestLoadDefault_WarnsOnUnknownKeysButStillLoads(t *testing.T) { path := filepath.Join(dir, "config.yaml") require.NoError(t, os.WriteFile(path, []byte("container:\n volumes:\n - /host:/ctr\n privileged: true\n"), 0o600)) - hook := test.NewGlobal() - defer hook.Reset() + hook := logtest.SetDefault(t) cfg, err := LoadDefault(path) require.NoError(t, err) assert.True(t, cfg.Container.Privileged) - require.Len(t, hook.Entries, 1) - assert.Contains(t, hook.LastEntry().Message, "field volumes not found") + require.Len(t, hook.AllRecords(), 1) + assert.Contains(t, hook.LastRecord().Message, "field volumes not found") } func TestContainerNetworkCreateOptions(t *testing.T) { @@ -539,8 +539,7 @@ cache: // The shipped configs must parse, hold no key the config does not know, and leave // every option at its default, as all of their values are commented out. func TestLoadDefault_ShippedConfigsChangeNothing(t *testing.T) { - hook := test.NewGlobal() - defer hook.Reset() + hook := logtest.SetDefault(t) defaults, err := LoadDefault("") require.NoError(t, err) @@ -554,7 +553,7 @@ func TestLoadDefault_ShippedConfigsChangeNothing(t *testing.T) { require.NoError(t, err, name) assert.Equal(t, defaults, cfg, name) } - assert.Empty(t, hook.AllEntries()) + assert.Empty(t, hook.AllRecords()) } func TestLoadDefault_ClampsFetchTimeout(t *testing.T) { diff --git a/internal/pkg/metrics/server.go b/internal/pkg/metrics/server.go index 9876e081..8707e7fc 100644 --- a/internal/pkg/metrics/server.go +++ b/internal/pkg/metrics/server.go @@ -8,8 +8,9 @@ import ( "net/http" "time" + "gitea.com/gitea/runner/act/common/logging" + "github.com/prometheus/client_golang/prometheus/promhttp" - log "github.com/sirupsen/logrus" ) // StartServer starts an HTTP server that serves Prometheus metrics on /metrics @@ -26,9 +27,9 @@ func StartServer(ctx context.Context, addr string, readiness func() (bool, strin } go func() { - log.Infof("metrics server listening on %s", addr) + logging.Infof("metrics server listening on %s", addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.WithError(err).Error("metrics server failed") + logging.WithError(err).Error("metrics server failed") } }() @@ -37,7 +38,7 @@ func StartServer(ctx context.Context, addr string, readiness func() (bool, strin shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(shutCtx); err != nil { - log.WithError(err).Warn("metrics server shutdown error") + logging.WithError(err).Warn("metrics server shutdown error") } }() } diff --git a/internal/pkg/report/globalmask.go b/internal/pkg/report/globalmask.go index 979bcbc2..6edf59ef 100644 --- a/internal/pkg/report/globalmask.go +++ b/internal/pkg/report/globalmask.go @@ -9,8 +9,6 @@ import ( "sync/atomic" "gitea.com/gitea/runner/act/runner" - - log "github.com/sirupsen/logrus" ) // A job's plan runs before its job logger exists and logs through the process-wide one, which @@ -47,21 +45,12 @@ func rebuildGlobalReplacer() { // caller holds globalMu globalReplacer.Store(runner.NewSecretReplacer(oldnew)) } -// MaskingFormatter wraps f so a registered value cannot reach the process-wide log, fields included. -func MaskingFormatter(f log.Formatter) log.Formatter { - return &maskingFormatter{inner: f} -} - -type maskingFormatter struct{ inner log.Formatter } - -func (m *maskingFormatter) Format(entry *log.Entry) ([]byte, error) { - line, err := m.inner.Format(entry) - if err != nil { - return nil, err - } +// MaskGlobal hides every registered value in a formatted line of the process-wide log, fields +// included. It is meant for logging.TextOptions.Filter. +func MaskGlobal(line []byte) []byte { replacer := globalReplacer.Load() if replacer == nil { // nothing to hide, so an idle daemon pays no copy - return line, nil + return line } - return []byte(replacer.Replace(string(line))), nil + return []byte(replacer.Replace(string(line))) } diff --git a/internal/pkg/report/globalmask_test.go b/internal/pkg/report/globalmask_test.go index 4720dd2c..eb61b697 100644 --- a/internal/pkg/report/globalmask_test.go +++ b/internal/pkg/report/globalmask_test.go @@ -4,19 +4,27 @@ package report import ( + "strings" "testing" - log "github.com/sirupsen/logrus" + "gitea.com/gitea/runner/act/common/logging" + "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestGlobalMasks(t *testing.T) { - formatter := MaskingFormatter(&log.TextFormatter{DisableTimestamp: true}) + prevOut := logging.Output() + t.Cleanup(func() { + logging.SetOutput(prevOut) + logging.SetTextFormat(logging.TextOptions{}) + }) + var out strings.Builder + logging.SetOutput(&out) + logging.SetTextFormat(logging.TextOptions{DisableTimestamp: true, Filter: MaskGlobal}) format := func(job string) string { - line, err := formatter.Format(log.WithField("job", job)) - require.NoError(t, err) - return string(line) + out.Reset() + logging.With("job", job).Info("line") + return out.String() } register := func(oldnew ...string) *Reporter { r := &Reporter{oldnew: oldnew} diff --git a/internal/pkg/report/joblog.go b/internal/pkg/report/joblog.go index ce1fb832..feb68030 100644 --- a/internal/pkg/report/joblog.go +++ b/internal/pkg/report/joblog.go @@ -10,9 +10,8 @@ import ( "strings" "time" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/config" - - log "github.com/sirupsen/logrus" ) const ( @@ -37,7 +36,7 @@ func openJobLog(cfg config.LogJob, taskID int64, started time.Time) *jobLog { } if err := os.MkdirAll(cfg.Dir, 0o700); err != nil { // repository output, readable by this user only - log.Warnf("cannot create job log directory %s: %v", cfg.Dir, err) + logging.Warnf("cannot create job log directory %s: %v", cfg.Dir, err) return nil } pruneJobLogs(cfg.Dir, cfg.Retention, started) @@ -45,10 +44,10 @@ func openJobLog(cfg config.LogJob, taskID int64, started time.Time) *jobLog { name := fmt.Sprintf("%s-task-%d.log", started.UTC().Format(jobLogNameLayout), taskID) file, err := os.OpenFile(filepath.Join(cfg.Dir, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { - log.Warnf("cannot create job log: %v", err) + logging.Warnf("cannot create job log: %v", err) return nil } - log.Infof("writing the log of task %d to %s", taskID, file.Name()) + logging.Infof("writing the log of task %d to %s", taskID, file.Name()) return &jobLog{file: file, max: int64(cfg.MaxSize)} } @@ -72,7 +71,7 @@ func (j *jobLog) close(trailer string) { j.closed = true j.line(runnerLine(trailer)) // past the cap on purpose: no trailer means the runner died mid-job if err := j.file.Close(); err != nil { - log.Warnf("cannot close %s: %v", j.file.Name(), err) + logging.Warnf("cannot close %s: %v", j.file.Name(), err) } } @@ -83,7 +82,7 @@ func (j *jobLog) line(content string) { j.size += int64(n) if err != nil { j.stopped = true // reported once, a failing write is a full disk and retrying floods the log - log.Warnf("cannot write %s: %v", j.file.Name(), err) + logging.Warnf("cannot write %s: %v", j.file.Name(), err) } } @@ -99,7 +98,7 @@ func pruneJobLogs(root string, retention time.Duration, now time.Time) { } entries, err := os.ReadDir(root) if err != nil { - log.Warnf("cannot list job log directory %s: %v", root, err) + logging.Warnf("cannot list job log directory %s: %v", root, err) return } @@ -114,7 +113,7 @@ func pruneJobLogs(root string, retention time.Duration, now time.Time) { } name := filepath.Join(root, entry.Name()) if err := os.Remove(name); err != nil { - log.Warnf("cannot remove expired job log %s: %v", name, err) + logging.Warnf("cannot remove expired job log %s: %v", name, err) } } } diff --git a/internal/pkg/report/reporter.go b/internal/pkg/report/reporter.go index d9894947..17edd05b 100644 --- a/internal/pkg/report/reporter.go +++ b/internal/pkg/report/reporter.go @@ -23,7 +23,6 @@ import ( "connectrpc.com/connect" runnerv1 "gitea.dev/actionslib/runner/v1" "github.com/avast/retry-go/v5" - log "github.com/sirupsen/logrus" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -357,10 +356,10 @@ func (r *Reporter) noteReport(method string, err error) { switch { case err != nil && !r.reportFailing[method]: r.reportFailing[method] = true - log.Warnf("%s error: %v, retrying until reconnected", method, err) + logging.Warnf("%s error: %v, retrying until reconnected", method, err) case err == nil && r.reportFailing[method]: delete(r.reportFailing, method) - log.Infof("%s reconnected", method) + logging.Infof("%s reconnected", method) } } @@ -497,17 +496,17 @@ func (r *Reporter) SetOutputs(outputs map[string]string) { } for k, v := range outputs { if l := len(k); l > maxOutputKeyLen { - log.Warnf("ignore output %q because the key is too long: %d > %d", k, l, maxOutputKeyLen) + logging.Warnf("ignore output %q because the key is too long: %d > %d", k, l, maxOutputKeyLen) r.logf("ignore output %q because the key is too long: %d > %d", k, l, maxOutputKeyLen) continue } if l := len(v); l > maxOutputValueLen { - log.Warnf("ignore output %q because the value is too long: %d > %d", k, l, maxOutputValueLen) + logging.Warnf("ignore output %q because the value is too long: %d > %d", k, l, maxOutputValueLen) r.logf("ignore output %q because the value is too long: %d > %d", k, l, maxOutputValueLen) continue } if r.logReplacer.Replace(v) != v { // GitHub skips an output that may carry a secret rather than masking it - log.Warnf("ignore output %q because it may contain a secret", k) + logging.Warnf("ignore output %q because it may contain a secret", k) r.logf("ignore output %q because it may contain a secret", k) continue } @@ -559,7 +558,7 @@ func (r *Reporter) Close(lastWords string) error { select { case <-r.daemon: case <-time.After(r.daemonWait): - log.Errorf("No Response from RunDaemon for %s, continue best effort", r.daemonWait) + logging.Errorf("No Response from RunDaemon for %s, continue best effort", r.daemonWait) } // Gitea's UpdateLog short-circuits on len(Rows)==0 before honoring NoMore, diff --git a/internal/pkg/report/reporter_test.go b/internal/pkg/report/reporter_test.go index 1f4b3f32..98e38829 100644 --- a/internal/pkg/report/reporter_test.go +++ b/internal/pkg/report/reporter_test.go @@ -18,6 +18,7 @@ import ( "time" "gitea.com/gitea/runner/act/common/logging" + "gitea.com/gitea/runner/act/common/logging/logtest" "gitea.com/gitea/runner/act/runner" "gitea.com/gitea/runner/internal/pkg/client/mocks" "gitea.com/gitea/runner/internal/pkg/config" @@ -26,7 +27,6 @@ import ( connect_go "connectrpc.com/connect" "gitea.dev/actionslib/pkg/model" runnerv1 "gitea.dev/actionslib/runner/v1" - logrustest "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -1311,24 +1311,23 @@ func TestReporter_ServerFailureResultIsReportedAsFailure(t *testing.T) { } func TestReporter_NoteReport(t *testing.T) { - hook := logrustest.NewGlobal() - defer hook.Reset() + hook := logtest.SetDefault(t) reporter := &Reporter{reportFailing: map[string]bool{}} // A pending output is not a transport failure and must not read as one. reporter.noteReport(metrics.LabelMethodUpdateTask, fmt.Errorf("wrapped: %w", errOutputsNotSent)) - assert.Empty(t, hook.AllEntries()) + assert.Empty(t, hook.AllRecords()) // An outage logs once at each end, not every report interval for hours. reporter.noteReport(metrics.LabelMethodUpdateTask, errors.New("connection refused")) reporter.noteReport(metrics.LabelMethodUpdateTask, errors.New("connection refused")) - require.Len(t, hook.AllEntries(), 1) - assert.Contains(t, hook.LastEntry().Message, "connection refused") + require.Len(t, hook.AllRecords(), 1) + assert.Contains(t, hook.LastRecord().Message, "connection refused") reporter.noteReport(metrics.LabelMethodUpdateTask, nil) - require.Len(t, hook.AllEntries(), 2) - assert.Contains(t, hook.LastEntry().Message, "reconnected") + require.Len(t, hook.AllRecords(), 2) + assert.Contains(t, hook.LastRecord().Message, "reconnected") } // A job's final error can carry a secret, e.g. one interpolated into a failing diff --git a/internal/pkg/telemetry/metrics.go b/internal/pkg/telemetry/metrics.go index fbf89702..2f93b23e 100644 --- a/internal/pkg/telemetry/metrics.go +++ b/internal/pkg/telemetry/metrics.go @@ -12,12 +12,12 @@ import ( "strings" "time" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/metrics" "gitea.dev/actionslib/runner/v1/runnerv1connect" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" - log "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" commonpb "go.opentelemetry.io/proto/otlp/common/v1" @@ -61,7 +61,7 @@ func pushMetrics(exporter *client, res *resource.Resource, gatherer prometheus.G return case <-ticker.C: if err := push(ctx); err != nil && ctx.Err() == nil { - log.WithError(err).Warn("OTLP metrics export failed, set OTEL_METRICS_EXPORTER=none to disable it") + logging.WithError(err).Warn("OTLP metrics export failed, set OTEL_METRICS_EXPORTER=none to disable it") } } } diff --git a/internal/pkg/telemetry/telemetry.go b/internal/pkg/telemetry/telemetry.go index 20fa7359..3dacadb8 100644 --- a/internal/pkg/telemetry/telemetry.go +++ b/internal/pkg/telemetry/telemetry.go @@ -21,6 +21,7 @@ import ( "sync" "time" + "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/ver" "gitea.dev/actionslib/pkg/model" @@ -28,7 +29,6 @@ import ( "github.com/avast/retry-go/v5" "github.com/docker/go-connections/tlsconfig" "github.com/prometheus/client_golang/prometheus" - log "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" @@ -90,7 +90,7 @@ func Setup(ctx context.Context, uuid, name string, gatherer prometheus.Gatherer) compressionKey := otlpKey(signal, "COMPRESSION") compression := os.Getenv(compressionKey) if !slices.Contains([]string{"", "none", "gzip"}, strings.ToLower(compression)) { - log.Warnf("ignoring unsupported %s=%q", compressionKey, compression) + logging.Warnf("ignoring unsupported %s=%q", compressionKey, compression) } httpClient, err := newHTTPClient(signal) if err != nil { @@ -144,7 +144,7 @@ func exportsOTLP(signal string) bool { case "none": known = true default: - log.Warnf("ignoring unsupported %s value %q", key, value) + logging.Warnf("ignoring unsupported %s value %q", key, value) } } return otlp || !known @@ -166,7 +166,7 @@ func millis(key string, fallback, zero time.Duration) time.Duration { } ms, err := strconv.ParseInt(value, 10, 32) if err != nil || ms < 0 { - log.Warnf("ignoring %s=%q, it must be a non-negative number of milliseconds", key, value) + logging.Warnf("ignoring %s=%q, it must be a non-negative number of milliseconds", key, value) return fallback } return cmp.Or(time.Duration(ms)*time.Millisecond, zero)