common/buildlogger/internal/tee.go (61 lines of code) (raw):
package internal
import (
"fmt"
"io"
"github.com/sirupsen/logrus"
"gitlab.com/gitlab-org/gitlab-runner/helpers"
)
// Tee is a log writer that targets both the job/build log _and_ the runner log,
// writing to both.
type Tee struct {
logFn func(args ...any)
entry *logrus.Entry
// disable stops teeing to the runner log, this is essentially used by
// runner tests where both the build and runner logs both use the same
// destination (like stdout)
disable bool
}
func NewTee(logFn func(args ...any), entry *logrus.Entry, disable bool) Tee {
return Tee{logFn, entry, disable}
}
func (t *Tee) WithFields(fields logrus.Fields) Tee {
return Tee{
logFn: t.logFn,
entry: t.entry.WithFields(fields),
disable: t.disable,
}
}
func (t *Tee) WriterLevel(level logrus.Level) *io.PipeWriter {
return t.entry.WriterLevel(level)
}
func (t *Tee) log(level logrus.Level, logPrefix string, args ...interface{}) {
if t.entry == nil {
return
}
// log lines have spaces between each argument, followed by an ANSI Reset and *then* a new-line.
//
// To achieve this, we use fmt.Sprintln and remove the newline, add the ANSI Reset and then
// append the newline again. The reason we don't use fmt.Sprint is that there's a greater
// difference between that and fmt.Sprintln than just the newline character being added
// (fmt.Sprintln consistently adds a space between arguments).
logLine := fmt.Sprintln(args...)
logLine = logLine[:len(logLine)-1]
logLine += helpers.ANSI_RESET + "\n"
if t.logFn != nil {
t.logFn(logPrefix + logLine)
}
// don't tee to logrus entry (runner log) when disabled or no args
if t.disable || len(args) == 0 {
return
}
t.entry.Logln(level, args...)
}
func (t *Tee) Debugln(args ...interface{}) {
if t.entry == nil {
return
}
t.entry.Debugln(args...)
}
func (t *Tee) Println(args ...interface{}) {
t.log(logrus.DebugLevel, helpers.ANSI_CLEAR, args...)
}
func (t *Tee) Infoln(args ...interface{}) {
t.log(logrus.InfoLevel, helpers.ANSI_BOLD_GREEN, args...)
}
func (t *Tee) Warningln(args ...interface{}) {
t.log(logrus.WarnLevel, helpers.ANSI_YELLOW+"WARNING: ", args...)
}
func (t *Tee) SoftErrorln(args ...interface{}) {
t.log(logrus.WarnLevel, helpers.ANSI_BOLD_RED+"ERROR: ", args...)
}
func (t *Tee) Errorln(args ...interface{}) {
t.log(logrus.ErrorLevel, helpers.ANSI_BOLD_RED+"ERROR: ", args...)
}