github.com/nektos/act@v0.2.63/pkg/runner/logger.go (about) 1 package runner 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 "sync" 11 12 "github.com/nektos/act/pkg/common" 13 14 "github.com/sirupsen/logrus" 15 "golang.org/x/term" 16 ) 17 18 const ( 19 // nocolor = 0 20 red = 31 21 green = 32 22 yellow = 33 23 blue = 34 24 magenta = 35 25 cyan = 36 26 gray = 37 27 ) 28 29 var colors []int 30 var nextColor int 31 var mux sync.Mutex 32 33 func init() { 34 nextColor = 0 35 colors = []int{ 36 blue, yellow, green, magenta, red, gray, cyan, 37 } 38 } 39 40 type masksContextKey string 41 42 const masksContextKeyVal = masksContextKey("logrus.FieldLogger") 43 44 // Logger returns the appropriate logger for current context 45 func Masks(ctx context.Context) *[]string { 46 val := ctx.Value(masksContextKeyVal) 47 if val != nil { 48 if masks, ok := val.(*[]string); ok { 49 return masks 50 } 51 } 52 return &[]string{} 53 } 54 55 // WithMasks adds a value to the context for the logger 56 func WithMasks(ctx context.Context, masks *[]string) context.Context { 57 return context.WithValue(ctx, masksContextKeyVal, masks) 58 } 59 60 type JobLoggerFactory interface { 61 WithJobLogger() *logrus.Logger 62 } 63 64 type jobLoggerFactoryContextKey string 65 66 var jobLoggerFactoryContextKeyVal = (jobLoggerFactoryContextKey)("jobloggerkey") 67 68 func WithJobLoggerFactory(ctx context.Context, factory JobLoggerFactory) context.Context { 69 return context.WithValue(ctx, jobLoggerFactoryContextKeyVal, factory) 70 } 71 72 // WithJobLogger attaches a new logger to context that is aware of steps 73 func WithJobLogger(ctx context.Context, jobID string, jobName string, config *Config, masks *[]string, matrix map[string]interface{}) context.Context { 74 ctx = WithMasks(ctx, masks) 75 76 var logger *logrus.Logger 77 if jobLoggerFactory, ok := ctx.Value(jobLoggerFactoryContextKeyVal).(JobLoggerFactory); ok && jobLoggerFactory != nil { 78 logger = jobLoggerFactory.WithJobLogger() 79 } else { 80 var formatter logrus.Formatter 81 if config.JSONLogger { 82 formatter = &logrus.JSONFormatter{} 83 } else { 84 mux.Lock() 85 defer mux.Unlock() 86 nextColor++ 87 formatter = &jobLogFormatter{ 88 color: colors[nextColor%len(colors)], 89 logPrefixJobID: config.LogPrefixJobID, 90 } 91 } 92 93 logger = logrus.New() 94 logger.SetOutput(os.Stdout) 95 logger.SetLevel(logrus.GetLevel()) 96 logger.SetFormatter(formatter) 97 } 98 99 logger.SetFormatter(&maskedFormatter{ 100 Formatter: logger.Formatter, 101 masker: valueMasker(config.InsecureSecrets, config.Secrets), 102 }) 103 rtn := logger.WithFields(logrus.Fields{ 104 "job": jobName, 105 "jobID": jobID, 106 "dryrun": common.Dryrun(ctx), 107 "matrix": matrix, 108 }).WithContext(ctx) 109 110 return common.WithLogger(ctx, rtn) 111 } 112 113 func WithCompositeLogger(ctx context.Context, masks *[]string) context.Context { 114 ctx = WithMasks(ctx, masks) 115 return common.WithLogger(ctx, common.Logger(ctx).WithFields(logrus.Fields{}).WithContext(ctx)) 116 } 117 118 func WithCompositeStepLogger(ctx context.Context, stepID string) context.Context { 119 val := common.Logger(ctx) 120 stepIDs := make([]string, 0) 121 122 if logger, ok := val.(*logrus.Entry); ok { 123 if oldStepIDs, ok := logger.Data["stepID"].([]string); ok { 124 stepIDs = append(stepIDs, oldStepIDs...) 125 } 126 } 127 128 stepIDs = append(stepIDs, stepID) 129 130 return common.WithLogger(ctx, common.Logger(ctx).WithFields(logrus.Fields{ 131 "stepID": stepIDs, 132 }).WithContext(ctx)) 133 } 134 135 func withStepLogger(ctx context.Context, stepID string, stepName string, stageName string) context.Context { 136 rtn := common.Logger(ctx).WithFields(logrus.Fields{ 137 "step": stepName, 138 "stepID": []string{stepID}, 139 "stage": stageName, 140 }) 141 return common.WithLogger(ctx, rtn) 142 } 143 144 type entryProcessor func(entry *logrus.Entry) *logrus.Entry 145 146 func valueMasker(insecureSecrets bool, secrets map[string]string) entryProcessor { 147 return func(entry *logrus.Entry) *logrus.Entry { 148 if insecureSecrets { 149 return entry 150 } 151 152 masks := Masks(entry.Context) 153 154 for _, v := range secrets { 155 if v != "" { 156 entry.Message = strings.ReplaceAll(entry.Message, v, "***") 157 } 158 } 159 160 for _, v := range *masks { 161 if v != "" { 162 entry.Message = strings.ReplaceAll(entry.Message, v, "***") 163 } 164 } 165 166 return entry 167 } 168 } 169 170 type maskedFormatter struct { 171 logrus.Formatter 172 masker entryProcessor 173 } 174 175 func (f *maskedFormatter) Format(entry *logrus.Entry) ([]byte, error) { 176 return f.Formatter.Format(f.masker(entry)) 177 } 178 179 type jobLogFormatter struct { 180 color int 181 logPrefixJobID bool 182 } 183 184 func (f *jobLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { 185 b := &bytes.Buffer{} 186 187 if f.isColored(entry) { 188 f.printColored(b, entry) 189 } else { 190 f.print(b, entry) 191 } 192 193 b.WriteByte('\n') 194 return b.Bytes(), nil 195 } 196 197 func (f *jobLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) { 198 entry.Message = strings.TrimSuffix(entry.Message, "\n") 199 200 var job any 201 if f.logPrefixJobID { 202 job = entry.Data["jobID"] 203 } else { 204 job = entry.Data["job"] 205 } 206 207 debugFlag := "" 208 if entry.Level == logrus.DebugLevel { 209 debugFlag = "[DEBUG] " 210 } 211 212 if entry.Data["raw_output"] == true { 213 fmt.Fprintf(b, "\x1b[%dm|\x1b[0m %s", f.color, entry.Message) 214 } else if entry.Data["dryrun"] == true { 215 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) 216 } else { 217 fmt.Fprintf(b, "\x1b[%dm[%s] \x1b[0m%s%s", f.color, job, debugFlag, entry.Message) 218 } 219 } 220 221 func (f *jobLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) { 222 entry.Message = strings.TrimSuffix(entry.Message, "\n") 223 224 var job any 225 if f.logPrefixJobID { 226 job = entry.Data["jobID"] 227 } else { 228 job = entry.Data["job"] 229 } 230 231 debugFlag := "" 232 if entry.Level == logrus.DebugLevel { 233 debugFlag = "[DEBUG] " 234 } 235 236 if entry.Data["raw_output"] == true { 237 fmt.Fprintf(b, "[%s] | %s", job, entry.Message) 238 } else if entry.Data["dryrun"] == true { 239 fmt.Fprintf(b, "*DRYRUN* [%s] %s%s", job, debugFlag, entry.Message) 240 } else { 241 fmt.Fprintf(b, "[%s] %s%s", job, debugFlag, entry.Message) 242 } 243 } 244 245 func (f *jobLogFormatter) isColored(entry *logrus.Entry) bool { 246 isColored := checkIfTerminal(entry.Logger.Out) 247 248 if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" { 249 isColored = true 250 } else if ok && force == "0" { 251 isColored = false 252 } else if os.Getenv("CLICOLOR") == "0" { 253 isColored = false 254 } 255 256 return isColored 257 } 258 259 func checkIfTerminal(w io.Writer) bool { 260 switch v := w.(type) { 261 case *os.File: 262 return term.IsTerminal(int(v.Fd())) 263 default: 264 return false 265 } 266 }