act/pkg/runner/logger.go
Markus Wolf 6b059572ff
feat: add step name to logger field (#1027)
* feat: add step name to logger field

This change does add the step name to the logger fields. This does
not change the output for our users, but for the json logger, it
does make each step output traceable.

* fix: remove new logger

Since logrus and context both are immutable for our case, we
can just add a new field and store the logger in the context.

Co-authored-by: Casey Lee <cplee@nektos.com>
Co-authored-by: Björn Brauer <bjoern.brauer@new-work.se>
2022-03-14 10:38:30 -07:00

180 lines
4 KiB
Go

package runner
import (
"bytes"
"context"
"fmt"
"io"
"os"
"strings"
"sync"
"github.com/nektos/act/pkg/common"
"github.com/sirupsen/logrus"
"golang.org/x/term"
)
const (
// nocolor = 0
red = 31
green = 32
yellow = 33
blue = 34
magenta = 35
cyan = 36
gray = 37
)
var colors []int
var nextColor int
var mux sync.Mutex
func init() {
nextColor = 0
colors = []int{
blue, yellow, green, magenta, red, gray, cyan,
}
}
// WithJobLogger attaches a new logger to context that is aware of steps
func WithJobLogger(ctx context.Context, jobName string, config *Config, masks *[]string) context.Context {
mux.Lock()
defer mux.Unlock()
var formatter logrus.Formatter
if config.JSONLogger {
formatter = &jobLogJSONFormatter{
formatter: &logrus.JSONFormatter{},
masker: valueMasker(config.InsecureSecrets, config.Secrets, masks),
}
} else {
formatter = &jobLogFormatter{
color: colors[nextColor%len(colors)],
masker: valueMasker(config.InsecureSecrets, config.Secrets, masks),
}
}
nextColor++
logger := logrus.New()
if common.TestContext(ctx) {
fieldLogger := common.Logger(ctx)
if fieldLogger != nil {
logger = fieldLogger.(*logrus.Logger)
}
}
logger.SetFormatter(formatter)
logger.SetOutput(os.Stdout)
logger.SetLevel(logrus.GetLevel())
rtn := logger.WithFields(logrus.Fields{"job": jobName, "dryrun": common.Dryrun(ctx)})
return common.WithLogger(ctx, rtn)
}
func withStepLogger(ctx context.Context, stepName string) context.Context {
rtn := common.Logger(ctx).WithFields(logrus.Fields{"step": stepName})
return common.WithLogger(ctx, rtn)
}
type entryProcessor func(entry *logrus.Entry) *logrus.Entry
func valueMasker(insecureSecrets bool, secrets map[string]string, masks *[]string) entryProcessor {
return func(entry *logrus.Entry) *logrus.Entry {
if insecureSecrets {
return entry
}
for _, v := range secrets {
if v != "" {
entry.Message = strings.ReplaceAll(entry.Message, v, "***")
}
}
for _, v := range *masks {
if v != "" {
entry.Message = strings.ReplaceAll(entry.Message, v, "***")
}
}
return entry
}
}
type jobLogFormatter struct {
color int
masker entryProcessor
}
func (f *jobLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
b := &bytes.Buffer{}
entry = f.masker(entry)
if f.isColored(entry) {
f.printColored(b, entry)
} else {
f.print(b, entry)
}
b.WriteByte('\n')
return b.Bytes(), nil
}
func (f *jobLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
entry.Message = strings.TrimSuffix(entry.Message, "\n")
jobName := entry.Data["job"]
if entry.Data["raw_output"] == true {
fmt.Fprintf(b, "\x1b[%dm|\x1b[0m %s", f.color, entry.Message)
} else if entry.Data["dryrun"] == true {
fmt.Fprintf(b, "\x1b[1m\x1b[%dm\x1b[7m*DRYRUN*\x1b[0m \x1b[%dm[%s] \x1b[0m%s", gray, f.color, jobName, entry.Message)
} else {
fmt.Fprintf(b, "\x1b[%dm[%s] \x1b[0m%s", f.color, jobName, entry.Message)
}
}
func (f *jobLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
entry.Message = strings.TrimSuffix(entry.Message, "\n")
jobName := entry.Data["job"]
if entry.Data["raw_output"] == true {
fmt.Fprintf(b, "[%s] | %s", jobName, entry.Message)
} else if entry.Data["dryrun"] == true {
fmt.Fprintf(b, "*DRYRUN* [%s] %s", jobName, entry.Message)
} else {
fmt.Fprintf(b, "[%s] %s", jobName, entry.Message)
}
}
func (f *jobLogFormatter) isColored(entry *logrus.Entry) bool {
isColored := checkIfTerminal(entry.Logger.Out)
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
isColored = true
} else if ok && force == "0" {
isColored = false
} else if os.Getenv("CLICOLOR") == "0" {
isColored = false
}
return isColored
}
func checkIfTerminal(w io.Writer) bool {
switch v := w.(type) {
case *os.File:
return term.IsTerminal(int(v.Fd()))
default:
return false
}
}
type jobLogJSONFormatter struct {
masker entryProcessor
formatter *logrus.JSONFormatter
}
func (f *jobLogJSONFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return f.formatter.Format(f.masker(entry))
}