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 <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1302
Reviewed-by: bircni <[email protected]>
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

+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