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 <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1302
Reviewed-by: bircni <bircni@icloud.com>
This commit is contained in:
Lunny Xiaoandbircni committed 2026-10-12 00:19:01 +00:00
1 parent 76110557ab
commit 4e8037a93b
25 files changed
+308 -246

No files matched your search

+50
View File
@@ -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
}
+25 -15
View File
@@ -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) }
+11
View File
@@ -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
}
+1 -2
View File
@@ -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()
+39 -39
View File
@@ -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)
}
}
+12 -13
View File
@@ -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")
+19 -18
View File
@@ -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 {
+28 -27
View File
@@ -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 {
+9 -9
View File
@@ -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++
+3 -4
View File
@@ -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
+8 -8
View File
@@ -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
})
}
+5 -5
View File
@@ -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/")
}
}
+28 -29
View File
@@ -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/<task_id>).
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
+3 -5
View File
@@ -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"))
}
+1 -1
View File
@@ -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 <start time>-task-<id>.log, so a job's output
+8 -7
View File
@@ -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)
}
}
}
+7 -8
View File
@@ -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) {
+5 -4
View File
@@ -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")
}
}()
}
+5 -16
View File
@@ -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)))
}
+14 -6
View File
@@ -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}
+8 -9
View File
@@ -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)
}
}
}
+6 -7
View File
@@ -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,
+7 -8
View File
@@ -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
+2 -2
View File
@@ -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")
}
}
}
+4 -4
View File
@@ -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)