Skip to content

Commit

Permalink
feat: Configure log levels (#1188)
Browse files Browse the repository at this point in the history
Add rest endpoint to configure log levels

Closes #1186

Signed-off-by: Sandra Vrtikapa <[email protected]>
  • Loading branch information
sandrask authored Mar 10, 2023
1 parent 6071536 commit 99a2ea2
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 6 deletions.
5 changes: 4 additions & 1 deletion cmd/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ const (
// LogLevelFlagShorthand is the shorthand flag name used for setting the default log level.
LogLevelFlagShorthand = "l"
// LogLevelPrefixFlagUsage is the usage text for the log level flag.
LogLevelPrefixFlagUsage = "Logging level to set. Supported options: CRITICAL, ERROR, WARNING, INFO, DEBUG." +
LogLevelPrefixFlagUsage = "Sets logging levels for individual modules as well as the default level. `+" +
"`The format of the string is as follows: module1=level1:module2=level2:defaultLevel. `+" +
"`Supported levels are: CRITICAL, ERROR, WARNING, INFO, DEBUG." +
"`Example: oidc4vp=INFO:oidc4vp-service=WARNING:INFO. `+" +
`Defaults to info if not set. Setting to debug may adversely impact performance. Alternatively, this can be ` +
"set with the following environment variable: " + LogLevelEnvKey
)
Expand Down
4 changes: 2 additions & 2 deletions cmd/vc-rest/startcmd/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ type startupParameters struct {
kmsParameters *kmsParameters
token string
requestTokens map[string]string
logLevel string
logLevels string
contextProviderURLs []string
contextEnableRemote bool
tlsParameters *tlsParameters
Expand Down Expand Up @@ -547,7 +547,7 @@ func getStartupParameters(cmd *cobra.Command) (*startupParameters, error) {
tlsParameters: tlsParameters,
token: token,
requestTokens: requestTokens,
logLevel: loggingLevel,
logLevels: loggingLevel,
contextProviderURLs: contextProviderURLs,
contextEnableRemote: contextEnableRemote,
devMode: devMode,
Expand Down
15 changes: 12 additions & 3 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import (
"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

"github.com/trustbloc/vcs/cmd/common"
"github.com/trustbloc/vcs/component/otp"
"github.com/trustbloc/vcs/pkg/doc/vc/statustype"
"github.com/trustbloc/vcs/pkg/ld"
Expand Down Expand Up @@ -66,6 +65,7 @@ import (
"github.com/trustbloc/vcs/pkg/restapi/resterr"
"github.com/trustbloc/vcs/pkg/restapi/v1/devapi"
issuerv1 "github.com/trustbloc/vcs/pkg/restapi/v1/issuer"
"github.com/trustbloc/vcs/pkg/restapi/v1/logapi"
"github.com/trustbloc/vcs/pkg/restapi/v1/mw"
oidc4civ1 "github.com/trustbloc/vcs/pkg/restapi/v1/oidc4ci"
oidc4vpv1 "github.com/trustbloc/vcs/pkg/restapi/v1/oidc4vp"
Expand Down Expand Up @@ -97,6 +97,7 @@ const (
cslSize = 10000
devApiRequestObjectEndpoint = "/request-object/:uuid"
devApiDidConfigEndpoint = "/:profileType/profiles/:profileID/well-known/did-config"
logLevelsEndpoint = "/loglevels"
versionEndpoint = "/version/system"
versionSystemEndpoint = "/version"
)
Expand Down Expand Up @@ -174,8 +175,10 @@ func createStartCmd(opts ...StartOpts) *cobra.Command {
return fmt.Errorf("failed to get startup parameters: %w", err)
}

if params.logLevel != "" {
common.SetDefaultLogLevel(logger, params.logLevel)
if params.logLevels != "" {
if e := log.SetSpec(params.logLevels); e != nil {
logger.Warn("Error setting logging spec.", log.WithError(e))
}
}

conf, err := prepareConfiguration(params)
Expand Down Expand Up @@ -303,6 +306,10 @@ func buildEchoHandler(
return true
}

if c.Path() == logLevelsEndpoint {
return true
}

return echomw.DefaultSkipper(c)
},
}))
Expand Down Expand Up @@ -612,6 +619,8 @@ func buildEchoHandler(
}, e)
}

_ = logapi.NewController(e)

metricsProvider, err := NewMetricsProvider(conf.StartupParameters, internalEchoServer)
if err != nil {
return nil, err
Expand Down
59 changes: 59 additions & 0 deletions pkg/restapi/v1/logapi/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package logapi

import (
"fmt"
"io"
"net/http"

"github.com/labstack/echo/v4"
"github.com/trustbloc/logutil-go/pkg/log"
)

//go:generate mockgen -destination controller_mocks_test.go -package logapi_test -source=controller.go

var logger = log.New("logapi")

type Controller struct {
}

type router interface {
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
}

func NewController(
router router,
) *Controller {
c := &Controller{}

router.POST("/loglevels", func(ctx echo.Context) error {
return c.PostLogLevels(ctx)
})

return c
}

// PostLogLevels updates log levels.
// (POST /loglevels).
func (c *Controller) PostLogLevels(ctx echo.Context) error {
req := ctx.Request()

logLevelBytes, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("failed to read body: %w", err)
}

logLevels := string(logLevelBytes)

if err := log.SetSpec(logLevels); err != nil {
return fmt.Errorf("failed to set log spec: %w", err)
}

logger.Info(fmt.Sprintf("log levels modified to: %s", logLevels))
return ctx.NoContent(http.StatusOK)
}
103 changes: 103 additions & 0 deletions pkg/restapi/v1/logapi/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package logapi_test

import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/golang/mock/gomock"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"

"github.com/trustbloc/vcs/pkg/restapi/v1/logapi"
)

func TestController(t *testing.T) {
mr := NewMockrouter(gomock.NewController(t))

mr.EXPECT().POST("/loglevels", gomock.Any()).Return(nil)
assert.NotNil(t, logapi.NewController(mr))
}

func TestPostLogLevels(t *testing.T) {
t.Run("changed log level", func(t *testing.T) {
mr := NewMockrouter(gomock.NewController(t))
mr.EXPECT().POST(gomock.Any(), gomock.Any()).AnyTimes()

c := logapi.NewController(mr)

body := newMockReader([]byte("DEBUG"))

assert.NoError(t, c.PostLogLevels(echoContext(body)))
})

t.Run("invalid log level", func(t *testing.T) {
mr := NewMockrouter(gomock.NewController(t))
mr.EXPECT().POST(gomock.Any(), gomock.Any()).AnyTimes()

c := logapi.NewController(mr)

body := newMockReader([]byte("INVALID"))

err := c.PostLogLevels(echoContext(body))
assert.Error(t, err)
assert.Contains(t, err.Error(), "logger: invalid log level")
})

t.Run("failed to read request", func(t *testing.T) {
mr := NewMockrouter(gomock.NewController(t))
mr.EXPECT().POST(gomock.Any(), gomock.Any()).AnyTimes()

c := logapi.NewController(mr)

err := c.PostLogLevels(echoContext(newMockReader([]byte("")).withError(fmt.Errorf("reader error"))))
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to read body: reader error")
})
}

func echoContext(body io.Reader) echo.Context {
e := echo.New()

req := httptest.NewRequest(http.MethodPost, "/", body)
req.Header.Set(echo.HeaderContentType, echo.MIMETextPlain)

rec := httptest.NewRecorder()
return e.NewContext(req, rec)
}

type mockReader struct {
io.Reader
err error
}

func newMockReader(value []byte) *mockReader {
return &mockReader{Reader: bytes.NewBuffer(value)}
}

func (r *mockReader) withError(err error) *mockReader {
r.err = err

return r
}

func (r *mockReader) Read(p []byte) (int, error) {
if r.err != nil {
return 0, r.err
}

return r.Reader.Read(p)
}

func (r *mockReader) Close() error {
return nil
}
5 changes: 5 additions & 0 deletions pkg/restapi/v1/mw/api_key_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
const (
header = "X-API-Key"
healthCheckPath = "/healthcheck"
logLevelsPath = "/loglevels"
statusCheckPath = "/credentials/status/"
requestObjectPath = "/request-object/"
checkAuthorizationResponse = "/verifier/interactions/authorization-response"
Expand Down Expand Up @@ -61,6 +62,10 @@ func APIKeyAuth(apiKey string) echo.MiddlewareFunc { //nolint:gocognit
return next(c)
}

if currentPath == logLevelsPath {
return next(c)
}

if strings.HasPrefix(currentPath, oidcAuthorize) ||
strings.HasPrefix(currentPath, oidcRedirect) ||
strings.HasPrefix(currentPath, oidcPresent) ||
Expand Down
20 changes: 20 additions & 0 deletions pkg/restapi/v1/mw/api_key_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ func TestApiKeyAuth(t *testing.T) {
require.True(t, handlerCalled)
})

t.Run("skip log levels endpoint", func(t *testing.T) {
handlerCalled := false
handler := func(c echo.Context) error {
handlerCalled = true
return c.String(http.StatusOK, "test")
}

middlewareChain := mw.APIKeyAuth("test-api-key")(handler)

e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/loglevels", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

err := middlewareChain(c)

require.NoError(t, err)
require.True(t, handlerCalled)
})

t.Run("skip version endpoint", func(t *testing.T) {
handlerCalled := false
handler := func(c echo.Context) error {
Expand Down
13 changes: 13 additions & 0 deletions test/bdd/features/vc_log_api.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# Copyright SecureKey Technologies Inc. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#

@all
@log_api
Feature: Change log levels
Scenario Outline: Modify log levels
When an HTTP POST is sent to "http://localhost:8075/loglevels" with content "DEBUG" of type "text/plain"
When an HTTP POST is sent to "http://localhost:8075/loglevels" with content "INVALID" of type "text/plain" and the returned status code is 500

Loading

0 comments on commit 99a2ea2

Please sign in to comment.