Add log level API (#444)

* Add log level API

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Update cli/loglevel/loglevel.go

Co-authored-by: Anbraten <anton@ju60.de>

* Move API to api routes

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: Anbraten <anton@ju60.de>
This commit is contained in:
John Olheiser 2021-10-18 18:25:20 -05:00 committed by GitHub
parent 03bb0e69d8
commit 8e658c135d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 174 additions and 4 deletions

45
cli/loglevel/loglevel.go Normal file
View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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
//

View file

@ -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()
}
}

View file

@ -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)
}

View file

@ -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"`
}
)