You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	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:
		
							
								
								
									
										45
									
								
								cli/loglevel/loglevel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								cli/loglevel/loglevel.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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") | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| // | ||||
|   | ||||
| @@ -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() | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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"` | ||||
| 	} | ||||
| ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user