From 8e658c135d2eb8dd07915f238914ce531b8912a0 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 18 Oct 2021 18:25:20 -0500 Subject: [PATCH] Add log level API (#444) * Add log level API Signed-off-by: jolheiser * Update cli/loglevel/loglevel.go Co-authored-by: Anbraten * Move API to api routes Signed-off-by: jolheiser Co-authored-by: Anbraten --- cli/loglevel/loglevel.go | 45 +++++++++++++++++++++++ cmd/cli/main.go | 14 ++++++-- server/api/z.go | 32 +++++++++++++++++ server/router/api.go | 7 ++++ woodpecker-go/woodpecker/client.go | 18 ++++++++++ woodpecker-go/woodpecker/client_test.go | 47 +++++++++++++++++++++++++ woodpecker-go/woodpecker/interface.go | 10 +++++- woodpecker-go/woodpecker/types.go | 5 +++ 8 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 cli/loglevel/loglevel.go diff --git a/cli/loglevel/loglevel.go b/cli/loglevel/loglevel.go new file mode 100644 index 000000000..1aee4262e --- /dev/null +++ b/cli/loglevel/loglevel.go @@ -0,0 +1,45 @@ +package loglevel + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/urfave/cli" + + "github.com/woodpecker-ci/woodpecker/cli/internal" + "github.com/woodpecker-ci/woodpecker/woodpecker-go/woodpecker" +) + +// Command exports the log-level command used to change the servers log-level. +var Command = cli.Command{ + Name: "log-level", + ArgsUsage: "[level]", + Usage: "get the logging level of the server, or set it with [level]", + Action: logLevel, +} + +func logLevel(c *cli.Context) error { + client, err := internal.NewClient(c) + if err != nil { + return err + } + + var ll *woodpecker.LogLevel + arg := c.Args().First() + if arg != "" { + lvl, err := zerolog.ParseLevel(arg) + if err != nil { + return err + } + ll, err = client.SetLogLevel(&woodpecker.LogLevel{ + Level: lvl.String(), + }) + } else { + ll, err = client.LogLevel() + } + if err != nil { + return err + } + + log.Info().Msgf("Logging level: %s", ll.Level) + return nil +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go index cd1769c61..640f7ff10 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -15,10 +15,11 @@ package main import ( - "fmt" "os" _ "github.com/joho/godotenv/autoload" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" "github.com/urfave/cli" "github.com/woodpecker-ci/woodpecker/cli/build" @@ -27,6 +28,7 @@ import ( "github.com/woodpecker-ci/woodpecker/cli/info" "github.com/woodpecker-ci/woodpecker/cli/lint" "github.com/woodpecker-ci/woodpecker/cli/log" + "github.com/woodpecker-ci/woodpecker/cli/loglevel" "github.com/woodpecker-ci/woodpecker/cli/registry" "github.com/woodpecker-ci/woodpecker/cli/repo" "github.com/woodpecker-ci/woodpecker/cli/secret" @@ -84,10 +86,16 @@ func main() { repo.Command, user.Command, lint.Command, + loglevel.Command, } + zlog.Logger = zlog.Output( + zerolog.ConsoleWriter{ + Out: os.Stderr, + }, + ) + if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + zlog.Fatal().Err(err).Msg("error running cli") } } diff --git a/server/api/z.go b/server/api/z.go index 8c71f5404..664c836b9 100644 --- a/server/api/z.go +++ b/server/api/z.go @@ -15,7 +15,11 @@ package api import ( + "net/http" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/woodpecker-ci/woodpecker/server/store" "github.com/woodpecker-ci/woodpecker/version" @@ -37,3 +41,31 @@ func Version(c *gin.Context) { "version": version.String(), }) } + +// LogLevel endpoint returns the current logging level +func LogLevel(c *gin.Context) { + c.JSON(200, gin.H{ + "log-level": zerolog.GlobalLevel().String(), + }) +} + +// SetLogLevel endpoint allows setting the logging level via API +func SetLogLevel(c *gin.Context) { + logLevel := struct { + LogLevel string `json:"log-level"` + }{} + if err := c.Bind(&logLevel); err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + lvl, err := zerolog.ParseLevel(logLevel.LogLevel) + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + log.Log().Msgf("log level set to %s", lvl.String()) + zerolog.SetGlobalLevel(lvl) + c.JSON(200, logLevel) +} diff --git a/server/router/api.go b/server/router/api.go index eb8072364..99ead7d30 100644 --- a/server/router/api.go +++ b/server/router/api.go @@ -135,6 +135,13 @@ func apiRoutes(e *gin.Engine) { debugger.GET("/pprof/trace", debug.TraceHandler()) } + logLevel := e.Group("/api/log-level") + { + logLevel.Use(session.MustAdmin()) + logLevel.GET("", api.LogLevel) + logLevel.POST("", api.SetLogLevel) + } + // TODO: remove /hook in favor of /api/hook e.POST("/hook", api.PostHook) e.POST("/api/hook", api.PostHook) diff --git a/woodpecker-go/woodpecker/client.go b/woodpecker-go/woodpecker/client.go index 10e838b8a..49c2c9ca7 100644 --- a/woodpecker-go/woodpecker/client.go +++ b/woodpecker-go/woodpecker/client.go @@ -37,6 +37,7 @@ const ( pathBuildQueue = "%s/api/builds" pathQueue = "%s/api/queue" pathVersion = "%s/version" + pathLogLevel = "%s/api/log-level" ) type client struct { @@ -355,6 +356,7 @@ func (c *client) SecretDelete(owner, name, secret string) error { return c.delete(uri) } +// QueueInfo returns queue info func (c *client) QueueInfo() (*Info, error) { out := new(Info) uri := fmt.Sprintf(pathQueue+"/info", c.addr) @@ -362,6 +364,22 @@ func (c *client) QueueInfo() (*Info, error) { return out, err } +// LogLevel returns the current logging level +func (c *client) LogLevel() (*LogLevel, error) { + out := new(LogLevel) + uri := fmt.Sprintf(pathLogLevel, c.addr) + err := c.get(uri, out) + return out, err +} + +// SetLogLevel sets the logging level of the server +func (c *client) SetLogLevel(in *LogLevel) (*LogLevel, error) { + out := new(LogLevel) + uri := fmt.Sprintf(pathLogLevel, c.addr) + err := c.post(uri, in, out) + return out, err +} + // // http request helper functions // diff --git a/woodpecker-go/woodpecker/client_test.go b/woodpecker-go/woodpecker/client_test.go index fee25290c..79cf97065 100644 --- a/woodpecker-go/woodpecker/client_test.go +++ b/woodpecker-go/woodpecker/client_test.go @@ -1,9 +1,11 @@ package woodpecker import ( + "encoding/json" "fmt" "net/http" "net/http/httptest" + "strings" "testing" ) @@ -45,3 +47,48 @@ func Test_QueueInfo(t *testing.T) { t.Errorf("Unexpected worker count: %v, %v", info, err) } } + +func Test_LogLevel(t *testing.T) { + logLevel := "warn" + fixtureHandler := func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + var ll LogLevel + if err := json.NewDecoder(r.Body).Decode(&ll); err != nil { + t.Logf("could not decode json: %v\n", err) + t.FailNow() + } + logLevel = ll.Level + } + + fmt.Fprintf(w, `{ + "log-level": "%s" + }`, logLevel) + } + + ts := httptest.NewServer(http.HandlerFunc(fixtureHandler)) + defer ts.Close() + + client := NewClient(ts.URL, http.DefaultClient) + + curLvl, err := client.LogLevel() + if err != nil { + t.Logf("could not get current log level: %v", err) + t.FailNow() + } + + if !strings.EqualFold(curLvl.Level, logLevel) { + t.Logf("log level is not correct\n\tExpected: %s\n\t Got: %s\n", logLevel, curLvl.Level) + t.FailNow() + } + + newLvl, err := client.SetLogLevel(&LogLevel{Level: "trace"}) + if err != nil { + t.Logf("could not set log level: %v", err) + t.FailNow() + } + + if !strings.EqualFold(newLvl.Level, logLevel) { + t.Logf("log level is not correct\n\tExpected: %s\n\t Got: %s\n", logLevel, newLvl.Level) + t.FailNow() + } +} diff --git a/woodpecker-go/woodpecker/interface.go b/woodpecker-go/woodpecker/interface.go index 0ac00439a..d37bb3325 100644 --- a/woodpecker-go/woodpecker/interface.go +++ b/woodpecker-go/woodpecker/interface.go @@ -1,6 +1,8 @@ package woodpecker -import "net/http" +import ( + "net/http" +) // Client is used to communicate with a Drone server. type Client interface { @@ -125,4 +127,10 @@ type Client interface { // QueueInfo returns the queue state. QueueInfo() (*Info, error) + + // LogLevel returns the current logging level + LogLevel() (*LogLevel, error) + + // SetLogLevel sets the server's logging level + SetLogLevel(logLevel *LogLevel) (*LogLevel, error) } diff --git a/woodpecker-go/woodpecker/types.go b/woodpecker-go/woodpecker/types.go index deab4ddd0..ff1ab6cfb 100644 --- a/woodpecker-go/woodpecker/types.go +++ b/woodpecker-go/woodpecker/types.go @@ -152,4 +152,9 @@ type ( } `json:"stats"` Paused bool `json:"paused,omitempty"` } + + // LogLevel is for checking/setting logging level + LogLevel struct { + Level string `json:"log-level"` + } )