diff --git a/agent/rpc/auth_interceptor.go b/agent/rpc/auth_interceptor.go index 8ee40c9858..b08c5044c1 100644 --- a/agent/rpc/auth_interceptor.go +++ b/agent/rpc/auth_interceptor.go @@ -2,9 +2,9 @@ package rpc import ( "context" - "log" "time" + "github.com/rs/zerolog/log" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) @@ -93,7 +93,7 @@ func (interceptor *AuthInterceptor) refreshToken() error { } interceptor.accessToken = accessToken - log.Printf("Token refreshed: %v", accessToken) + log.Debug().Msgf("Token refreshed: %v", accessToken) return nil } diff --git a/cli/common/flags.go b/cli/common/flags.go index 5465a2d7d0..5538ae4765 100644 --- a/cli/common/flags.go +++ b/cli/common/flags.go @@ -14,9 +14,13 @@ package common -import "github.com/urfave/cli/v2" +import ( + "github.com/urfave/cli/v2" -var GlobalFlags = []cli.Flag{ + "github.com/woodpecker-ci/woodpecker/cmd/common" +) + +var GlobalFlags = append([]cli.Flag{ &cli.StringFlag{ EnvVars: []string{"WOODPECKER_TOKEN"}, Name: "token", @@ -47,13 +51,7 @@ var GlobalFlags = []cli.Flag{ Usage: "socks proxy ignored", Hidden: true, }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_LEVEL"}, - Name: "log-level", - Usage: "set logging level", - Value: "info", - }, -} +}, common.GlobalLoggerFlags...) // FormatFlag return format flag with value set based on template // if hidden value is set, flag will be hidden diff --git a/cli/common/zerologger.go b/cli/common/zerologger.go index 3f48da0464..5d127afe87 100644 --- a/cli/common/zerologger.go +++ b/cli/common/zerologger.go @@ -1,21 +1,12 @@ package common import ( - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" + + "github.com/woodpecker-ci/woodpecker/cmd/common" ) -func SetupConsoleLogger(c *cli.Context) error { - level := c.String("log-level") - lvl, err := zerolog.ParseLevel(level) - if err != nil { - log.Fatal().Msgf("unknown logging level: %s", level) - } - zerolog.SetGlobalLevel(lvl) - if zerolog.GlobalLevel() <= zerolog.DebugLevel { - log.Logger = log.With().Caller().Logger() - log.Log().Msgf("LogLevel = %s", zerolog.GlobalLevel().String()) - } +func SetupGlobalLogger(c *cli.Context) error { + common.SetupGlobalLogger(c) return nil } diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 7b5ce3dc6e..f972c285db 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -27,7 +27,6 @@ import ( "sync" "time" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/tevino/abool" "github.com/urfave/cli/v2" @@ -41,6 +40,7 @@ import ( "github.com/woodpecker-ci/woodpecker/agent" agentRpc "github.com/woodpecker-ci/woodpecker/agent/rpc" + "github.com/woodpecker-ci/woodpecker/cmd/common" "github.com/woodpecker-ci/woodpecker/pipeline/backend" "github.com/woodpecker-ci/woodpecker/pipeline/backend/types" "github.com/woodpecker-ci/woodpecker/pipeline/rpc" @@ -49,6 +49,8 @@ import ( ) func run(c *cli.Context) error { + common.SetupGlobalLogger(c) + agentConfigPath := c.String("agent-config") hostname := c.String("hostname") if len(hostname) == 0 { @@ -57,28 +59,6 @@ func run(c *cli.Context) error { platform := runtime.GOOS + "/" + runtime.GOARCH - if c.Bool("pretty") { - log.Logger = log.Output( - zerolog.ConsoleWriter{ - Out: os.Stderr, - NoColor: c.Bool("nocolor"), - }, - ) - } - - zerolog.SetGlobalLevel(zerolog.InfoLevel) - if c.IsSet("log-level") { - logLevelFlag := c.String("log-level") - lvl, err := zerolog.ParseLevel(logLevelFlag) - if err != nil { - log.Fatal().Msgf("unknown logging level: %s", logLevelFlag) - } - zerolog.SetGlobalLevel(lvl) - } - if zerolog.GlobalLevel() <= zerolog.DebugLevel { - log.Logger = log.With().Caller().Logger() - } - counter.Polling = c.Int("max-workflows") counter.Running = 0 diff --git a/cmd/agent/flags.go b/cmd/agent/flags.go index e814ac5db7..4d70fb3165 100644 --- a/cmd/agent/flags.go +++ b/cmd/agent/flags.go @@ -20,9 +20,11 @@ import ( "time" "github.com/urfave/cli/v2" + + "github.com/woodpecker-ci/woodpecker/cmd/common" ) -var flags = []cli.Flag{ +var flags = append([]cli.Flag{ &cli.StringFlag{ EnvVars: []string{"WOODPECKER_SERVER"}, Name: "server", @@ -46,22 +48,6 @@ var flags = []cli.Flag{ Usage: "should the grpc server certificate be verified, only valid when WOODPECKER_GRPC_SECURE is true", Value: true, }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_LEVEL"}, - Name: "log-level", - Usage: "set logging level", - }, - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DEBUG_PRETTY"}, - Name: "pretty", - Usage: "enable pretty-printed debug output", - }, - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DEBUG_NOCOLOR"}, - Name: "nocolor", - Usage: "disable colored debug output", - Value: true, - }, &cli.StringFlag{ EnvVars: []string{"WOODPECKER_HOSTNAME"}, Name: "hostname", @@ -208,4 +194,4 @@ var flags = []cli.Flag{ Usage: "duration to wait before retrying to connect to the server", Value: time.Second * 2, }, -} +}, common.GlobalLoggerFlags...) diff --git a/cmd/cli/app.go b/cmd/cli/app.go index c3051ca206..cda2405266 100644 --- a/cmd/cli/app.go +++ b/cmd/cli/app.go @@ -15,10 +15,6 @@ package main import ( - "os" - - "github.com/rs/zerolog" - zlog "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" "github.com/woodpecker-ci/woodpecker/cli/common" @@ -60,13 +56,8 @@ func newApp() *cli.App { cron.Command, } - zlog.Logger = zlog.Output( - zerolog.ConsoleWriter{ - Out: os.Stderr, - }, - ) for _, command := range app.Commands { - command.Before = common.SetupConsoleLogger + command.Before = common.SetupGlobalLogger } return app diff --git a/cmd/common/common.go b/cmd/common/common.go new file mode 100644 index 0000000000..08d21b1d8d --- /dev/null +++ b/cmd/common/common.go @@ -0,0 +1,26 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "os" + + "golang.org/x/term" +) + +// IsInteractive checks if the output is piped, but NOT if the session is run interactively. +func IsInteractive() bool { + return term.IsTerminal(int(os.Stdout.Fd())) +} diff --git a/cmd/common/logger.go b/cmd/common/logger.go new file mode 100644 index 0000000000..c43804ffb9 --- /dev/null +++ b/cmd/common/logger.go @@ -0,0 +1,98 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" +) + +var GlobalLoggerFlags = []cli.Flag{ + &cli.StringFlag{ + EnvVars: []string{"WOODPECKER_LOG_LEVEL"}, + Name: "log-level", + Usage: "set logging level", + Value: "info", + }, + &cli.StringFlag{ + EnvVars: []string{"WOODPECKER_LOG_FILE"}, + Name: "log-file", + Usage: "where logs are written to. 'stdout' and 'stderr' can be used as special keywords", + Value: "stderr", + }, + &cli.BoolFlag{ + EnvVars: []string{"WOODPECKER_DEBUG_PRETTY"}, + Name: "pretty", + Usage: "enable pretty-printed debug output", + Value: IsInteractive(), // make pretty on interactive terminal by default + }, + &cli.BoolFlag{ + EnvVars: []string{"WOODPECKER_DEBUG_NOCOLOR"}, + Name: "nocolor", + Usage: "disable colored debug output, only has effect if pretty output is set too", + Value: !IsInteractive(), // do color on interactive terminal by default + }, +} + +func SetupGlobalLogger(c *cli.Context) { + logLevel := c.String("log-level") + pretty := c.Bool("pretty") + noColor := c.Bool("nocolor") + logFile := c.String("log-file") + + var file *os.File + switch logFile { + case "", "stderr": // default case + file = os.Stderr + case "stdout": + file = os.Stdout + default: // a file was set + openFile, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o660) + if err != nil { + log.Fatal().Err(err).Msgf("could not open log file '%s'", logFile) + } + file = openFile + noColor = true + } + + log.Logger = zerolog.New(file).With().Timestamp().Logger() + + if pretty { + log.Logger = log.Output( + zerolog.ConsoleWriter{ + Out: file, + NoColor: noColor, + }, + ) + } + + // TODO: format output & options to switch to json aka. option to add channels to send logs to + + lvl, err := zerolog.ParseLevel(logLevel) + if err != nil { + log.Fatal().Msgf("unknown logging level: %s", logLevel) + } + zerolog.SetGlobalLevel(lvl) + + // if debug or trace also log the caller + if zerolog.GlobalLevel() <= zerolog.DebugLevel { + log.Logger = log.With().Caller().Logger() + } + + log.Log().Msgf("LogLevel = %s", zerolog.GlobalLevel().String()) +} diff --git a/cmd/server/flags.go b/cmd/server/flags.go index 7272e0d01e..6e9eef4620 100644 --- a/cmd/server/flags.go +++ b/cmd/server/flags.go @@ -20,15 +20,11 @@ import ( "github.com/urfave/cli/v2" + "github.com/woodpecker-ci/woodpecker/cmd/common" "github.com/woodpecker-ci/woodpecker/shared/constant" ) -var flags = []cli.Flag{ - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_LEVEL"}, - Name: "log-level", - Usage: "set logging level", - }, +var flags = append([]cli.Flag{ &cli.BoolFlag{ EnvVars: []string{"WOODPECKER_LOG_XORM"}, Name: "log-xorm", @@ -39,17 +35,6 @@ var flags = []cli.Flag{ Name: "log-xorm-sql", Usage: "enable xorm sql command logging", }, - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DEBUG_PRETTY"}, - Name: "pretty", - Usage: "enable pretty-printed debug output", - }, - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DEBUG_NOCOLOR"}, - Name: "nocolor", - Usage: "disable colored debug output", - Value: true, - }, &cli.StringFlag{ EnvVars: []string{"WOODPECKER_HOST"}, Name: "server-host", @@ -470,4 +455,4 @@ var flags = []cli.Flag{ Name: "encryption-disable-flag", Usage: "Flag to decrypt all encrypted data and disable encryption on server", }, -} +}, common.GlobalLoggerFlags...) diff --git a/cmd/server/server.go b/cmd/server/server.go index eff96000c7..841278e093 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -35,6 +35,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/keepalive" + "github.com/woodpecker-ci/woodpecker/cmd/common" "github.com/woodpecker-ci/woodpecker/pipeline/rpc/proto" "github.com/woodpecker-ci/woodpecker/server" "github.com/woodpecker-ci/woodpecker/server/cron" @@ -55,31 +56,12 @@ import ( ) func run(c *cli.Context) error { - if c.Bool("pretty") { - log.Logger = log.Output( - zerolog.ConsoleWriter{ - Out: os.Stderr, - NoColor: c.Bool("nocolor"), - }, - ) - } + common.SetupGlobalLogger(c) - // TODO: format output & options to switch to json aka. option to add channels to send logs to - zerolog.SetGlobalLevel(zerolog.InfoLevel) - if c.IsSet("log-level") { - logLevelFlag := c.String("log-level") - lvl, err := zerolog.ParseLevel(logLevelFlag) - if err != nil { - log.Fatal().Msgf("unknown logging level: %s", logLevelFlag) - } - zerolog.SetGlobalLevel(lvl) - } - if zerolog.GlobalLevel() <= zerolog.DebugLevel { - log.Logger = log.With().Caller().Logger() - } else { + // set gin mode based on log level + if zerolog.GlobalLevel() > zerolog.DebugLevel { gin.SetMode(gin.ReleaseMode) } - log.Log().Msgf("LogLevel = %s", zerolog.GlobalLevel().String()) if c.String("server-host") == "" { log.Fatal().Msg("WOODPECKER_HOST is not properly configured") diff --git a/go.mod b/go.mod index 19f1e686f8..c505a79bb1 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( golang.org/x/net v0.10.0 golang.org/x/oauth2 v0.8.0 golang.org/x/sync v0.2.0 + golang.org/x/term v0.11.0 google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 @@ -138,8 +139,7 @@ require ( go.uber.org/zap v1.23.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect + golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect diff --git a/go.sum b/go.sum index 9afa5fa54d..e845491766 100644 --- a/go.sum +++ b/go.sum @@ -765,14 +765,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=