Skip to content

Commit

Permalink
feat: add json logger output (nektos#1026)
Browse files Browse the repository at this point in the history
* feat: add json logger output

This will allow to format log output as json.
This is helpful in cases where act is not executed on a 'local' machine.

* refactor: use runner config

Using the runner config to configure logging is cleaner.

Co-authored-by: Casey Lee <cplee@nektos.com>
  • Loading branch information
KnisterPeter and cplee authored Mar 14, 2022
1 parent 7d403b8 commit 14c9801
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 23 deletions.
1 change: 1 addition & 0 deletions cmd/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Input struct {
autoRemove bool
artifactServerPath string
artifactServerPort string
jsonLogger bool
}

func (i *Input) resolve(path string) string {
Expand Down
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func Execute(ctx context.Context, version string) {
rootCmd.PersistentFlags().BoolVarP(&input.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag")
rootCmd.PersistentFlags().StringVarP(&input.workdir, "directory", "C", ".", "working directory")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVar(&input.jsonLogger, "json", false, "Output logs in json format")
rootCmd.PersistentFlags().BoolVarP(&input.noOutput, "quiet", "q", false, "disable logging of output from steps")
rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "dryrun mode")
rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)")
Expand Down Expand Up @@ -156,6 +157,10 @@ func readEnvs(path string, envs map[string]string) bool {
//nolint:gocyclo
func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
if input.jsonLogger {
log.SetFormatter(&log.JSONFormatter{})
}

if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" && input.containerArchitecture == "" {
l := log.New()
l.SetFormatter(&log.TextFormatter{
Expand Down Expand Up @@ -266,6 +271,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
Workdir: input.Workdir(),
BindWorkdir: input.bindWorkdir,
LogOutput: !input.noOutput,
JSONLogger: input.jsonLogger,
Env: envs,
Secrets: secrets,
InsecureSecrets: input.insecureSecrets,
Expand Down
7 changes: 6 additions & 1 deletion pkg/runner/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,14 @@ func TestAddmaskUsemask(t *testing.T) {

a := assert.New(t)

config := &Config{
Secrets: map[string]string{},
InsecureSecrets: false,
}

re := captureOutput(t, func() {
ctx := context.Background()
ctx = WithJobLogger(ctx, "testjob", map[string]string{}, false, &rc.Masks)
ctx = WithJobLogger(ctx, "testjob", config, &rc.Masks)

handler := rc.commandHandler(ctx)
handler("::add-mask::secret\n")
Expand Down
68 changes: 47 additions & 21 deletions pkg/runner/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,22 @@ func init() {
}

// WithJobLogger attaches a new logger to context that is aware of steps
func WithJobLogger(ctx context.Context, jobName string, secrets map[string]string, insecureSecrets bool, masks *[]string) context.Context {
func WithJobLogger(ctx context.Context, jobName string, config *Config, masks *[]string) context.Context {
mux.Lock()
defer mux.Unlock()
formatter := new(stepLogFormatter)
formatter.color = colors[nextColor%len(colors)]
formatter.secrets = secrets
formatter.insecureSecrets = insecureSecrets
formatter.masks = masks

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++

Expand All @@ -64,30 +72,39 @@ func WithJobLogger(ctx context.Context, jobName string, secrets map[string]strin
return common.WithLogger(ctx, rtn)
}

type stepLogFormatter struct {
color int
secrets map[string]string
insecureSecrets bool
masks *[]string
}
type entryProcessor func(entry *logrus.Entry) *logrus.Entry

func (f *stepLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
b := &bytes.Buffer{}
func valueMasker(insecureSecrets bool, secrets map[string]string, masks *[]string) entryProcessor {
return func(entry *logrus.Entry) *logrus.Entry {
if insecureSecrets {
return entry
}

// Replace any secrets in the entry if insecure-secrets flag is not used
if !f.insecureSecrets {
for _, v := range f.secrets {
for _, v := range secrets {
if v != "" {
entry.Message = strings.ReplaceAll(entry.Message, v, "***")
}
}

for _, v := range *f.masks {
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)
Expand All @@ -99,7 +116,7 @@ func (f *stepLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return b.Bytes(), nil
}

func (f *stepLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
func (f *jobLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
entry.Message = strings.TrimSuffix(entry.Message, "\n")
jobName := entry.Data["job"]

Expand All @@ -112,7 +129,7 @@ func (f *stepLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
}
}

func (f *stepLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
func (f *jobLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
entry.Message = strings.TrimSuffix(entry.Message, "\n")
jobName := entry.Data["job"]

Expand All @@ -125,7 +142,7 @@ func (f *stepLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
}
}

func (f *stepLogFormatter) isColored(entry *logrus.Entry) bool {
func (f *jobLogFormatter) isColored(entry *logrus.Entry) bool {
isColored := checkIfTerminal(entry.Logger.Out)

if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
Expand All @@ -147,3 +164,12 @@ func checkIfTerminal(w io.Writer) bool {
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))
}
3 changes: 2 additions & 1 deletion pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Config struct {
ForcePull bool // force pulling of the image, even if already present
ForceRebuild bool // force rebuilding local docker image action
LogOutput bool // log the output from docker run
JSONLogger bool // use json or text logger
Env map[string]string // env for containers
Secrets map[string]string // list of secrets
InsecureSecrets bool // switch hiding output when printing to terminal
Expand Down Expand Up @@ -164,7 +165,7 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
}

return nil
})(common.WithJobErrorContainer(WithJobLogger(ctx, jobName, rc.Config.Secrets, rc.Config.InsecureSecrets, &rc.Masks)))
})(common.WithJobErrorContainer(WithJobLogger(ctx, jobName, rc.Config, &rc.Masks)))
})
}
pipeline = append(pipeline, common.NewParallelExecutor(maxParallel, stageExecutor...))
Expand Down

0 comments on commit 14c9801

Please sign in to comment.