summaryrefslogtreecommitdiffstats
path: root/pkg/runner/logger.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/runner/logger.go')
-rw-r--r--pkg/runner/logger.go278
1 files changed, 278 insertions, 0 deletions
diff --git a/pkg/runner/logger.go b/pkg/runner/logger.go
new file mode 100644
index 0000000..6890293
--- /dev/null
+++ b/pkg/runner/logger.go
@@ -0,0 +1,278 @@
+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,
+ }
+}
+
+type masksContextKey string
+
+const masksContextKeyVal = masksContextKey("logrus.FieldLogger")
+
+// Logger returns the appropriate logger for current context
+func Masks(ctx context.Context) *[]string {
+ val := ctx.Value(masksContextKeyVal)
+ if val != nil {
+ if masks, ok := val.(*[]string); ok {
+ return masks
+ }
+ }
+ return &[]string{}
+}
+
+// WithLogger adds a value to the context for the logger
+func WithMasks(ctx context.Context, masks *[]string) context.Context {
+ return context.WithValue(ctx, masksContextKeyVal, masks)
+}
+
+type JobLoggerFactory interface {
+ WithJobLogger() *logrus.Logger
+}
+
+type jobLoggerFactoryContextKey string
+
+var jobLoggerFactoryContextKeyVal = (jobLoggerFactoryContextKey)("jobloggerkey")
+
+func WithJobLoggerFactory(ctx context.Context, factory JobLoggerFactory) context.Context {
+ return context.WithValue(ctx, jobLoggerFactoryContextKeyVal, factory)
+}
+
+// WithJobLogger attaches a new logger to context that is aware of steps
+func WithJobLogger(ctx context.Context, jobID string, jobName string, config *Config, masks *[]string, matrix map[string]interface{}) context.Context {
+ ctx = WithMasks(ctx, masks)
+
+ var logger *logrus.Logger
+ if jobLoggerFactory, ok := ctx.Value(jobLoggerFactoryContextKeyVal).(JobLoggerFactory); ok && jobLoggerFactory != nil {
+ logger = jobLoggerFactory.WithJobLogger()
+ } else {
+ var formatter logrus.Formatter
+ if config.JSONLogger {
+ formatter = &logrus.JSONFormatter{}
+ } else {
+ mux.Lock()
+ defer mux.Unlock()
+ nextColor++
+ formatter = &jobLogFormatter{
+ color: colors[nextColor%len(colors)],
+ logPrefixJobID: config.LogPrefixJobID,
+ }
+ }
+
+ logger = logrus.New()
+ logger.SetOutput(os.Stdout)
+ logger.SetLevel(logrus.GetLevel())
+ logger.SetFormatter(formatter)
+ }
+
+ { // Adapt to Gitea
+ if hook := common.LoggerHook(ctx); hook != nil {
+ logger.AddHook(hook)
+ }
+ if config.JobLoggerLevel != nil {
+ logger.SetLevel(*config.JobLoggerLevel)
+ } else {
+ logger.SetLevel(logrus.TraceLevel)
+ }
+ }
+
+ logger.SetFormatter(&maskedFormatter{
+ Formatter: logger.Formatter,
+ masker: valueMasker(config.InsecureSecrets, config.Secrets),
+ })
+ rtn := logger.WithFields(logrus.Fields{
+ "job": jobName,
+ "jobID": jobID,
+ "dryrun": common.Dryrun(ctx),
+ "matrix": matrix,
+ }).WithContext(ctx)
+
+ return common.WithLogger(ctx, rtn)
+}
+
+func WithCompositeLogger(ctx context.Context, masks *[]string) context.Context {
+ ctx = WithMasks(ctx, masks)
+ return common.WithLogger(ctx, common.Logger(ctx).WithFields(logrus.Fields{}).WithContext(ctx))
+}
+
+func WithCompositeStepLogger(ctx context.Context, stepID string) context.Context {
+ val := common.Logger(ctx)
+ stepIDs := make([]string, 0)
+
+ if logger, ok := val.(*logrus.Entry); ok {
+ if oldStepIDs, ok := logger.Data["stepID"].([]string); ok {
+ stepIDs = append(stepIDs, oldStepIDs...)
+ }
+ }
+
+ stepIDs = append(stepIDs, stepID)
+
+ return common.WithLogger(ctx, common.Logger(ctx).WithFields(logrus.Fields{
+ "stepID": stepIDs,
+ }).WithContext(ctx))
+}
+
+func withStepLogger(ctx context.Context, stepNumber int, stepID, stepName, stageName string) context.Context {
+ rtn := common.Logger(ctx).WithFields(logrus.Fields{
+ "stepNumber": stepNumber,
+ "step": stepName,
+ "stepID": []string{stepID},
+ "stage": stageName,
+ })
+ return common.WithLogger(ctx, rtn)
+}
+
+type entryProcessor func(entry *logrus.Entry) *logrus.Entry
+
+func valueMasker(insecureSecrets bool, secrets map[string]string) entryProcessor {
+ return func(entry *logrus.Entry) *logrus.Entry {
+ if insecureSecrets {
+ return entry
+ }
+
+ masks := Masks(entry.Context)
+
+ 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 maskedFormatter struct {
+ logrus.Formatter
+ masker entryProcessor
+}
+
+func (f *maskedFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ return f.Formatter.Format(f.masker(entry))
+}
+
+type jobLogFormatter struct {
+ color int
+ logPrefixJobID bool
+}
+
+func (f *jobLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ b := &bytes.Buffer{}
+
+ 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")
+
+ var job any
+ if f.logPrefixJobID {
+ job = entry.Data["jobID"]
+ } else {
+ job = entry.Data["job"]
+ }
+
+ debugFlag := ""
+ if entry.Level == logrus.DebugLevel {
+ debugFlag = "[DEBUG] "
+ }
+
+ 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%s", gray, f.color, job, debugFlag, entry.Message)
+ } else {
+ fmt.Fprintf(b, "\x1b[%dm[%s] \x1b[0m%s%s", f.color, job, debugFlag, entry.Message)
+ }
+}
+
+func (f *jobLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
+ entry.Message = strings.TrimSuffix(entry.Message, "\n")
+
+ var job any
+ if f.logPrefixJobID {
+ job = entry.Data["jobID"]
+ } else {
+ job = entry.Data["job"]
+ }
+
+ debugFlag := ""
+ if entry.Level == logrus.DebugLevel {
+ debugFlag = "[DEBUG] "
+ }
+
+ if entry.Data["raw_output"] == true {
+ fmt.Fprintf(b, "[%s] | %s", job, entry.Message)
+ } else if entry.Data["dryrun"] == true {
+ fmt.Fprintf(b, "*DRYRUN* [%s] %s%s", job, debugFlag, entry.Message)
+ } else {
+ fmt.Fprintf(b, "[%s] %s%s", job, debugFlag, 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
+ }
+}