Skip to content

Commit

Permalink
VAULT-9427: Add read support to sys/loggers endpoints (#17979)
Browse files Browse the repository at this point in the history
* add logger->log-level str func

* ensure SetLogLevelByName accounts for duplicates

* add read handlers for sys/loggers endpoints

* add changelog entry

* update docs

* ignore base logger

* fix docs formatting issue

* add ReadOperation support to TestSystemBackend_Loggers

* add more robust checks to TestSystemBackend_Loggers

* add more robust checks to TestSystemBackend_LoggersByName

* check for empty name in delete handler
  • Loading branch information
ccapurso committed Nov 30, 2022
1 parent 114b284 commit b9c0dc8
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 76 deletions.
3 changes: 3 additions & 0 deletions changelog/17979.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
core: Add read support to `sys/loggers` and `sys/loggers/:name` endpoints
```
23 changes: 23 additions & 0 deletions helper/logging/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ func ParseLogFormat(format string) (LogFormat, error) {
}
}

// ParseLogLevel returns the hclog.Level that corresponds with the provided level string.
// This differs hclog.LevelFromString in that it supports additional level strings.
func ParseLogLevel(logLevel string) (log.Level, error) {
var result log.Level
logLevel = strings.ToLower(strings.TrimSpace(logLevel))
Expand All @@ -133,3 +135,24 @@ func ParseLogLevel(logLevel string) (log.Level, error) {

return result, nil
}

// TranslateLoggerLevel returns the string that corresponds with logging level of the hclog.Logger.
func TranslateLoggerLevel(logger log.Logger) (string, error) {
var result string

if logger.IsTrace() {
result = "trace"
} else if logger.IsDebug() {
result = "debug"
} else if logger.IsInfo() {
result = "info"
} else if logger.IsWarn() {
result = "warn"
} else if logger.IsError() {
result = "error"
} else {
return "", fmt.Errorf("unknown log level")
}

return result, nil
}
12 changes: 9 additions & 3 deletions vault/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -2860,6 +2860,7 @@ func (c *Core) AddLogger(logger log.Logger) {
c.allLoggers = append(c.allLoggers, logger)
}

// SetLogLevel sets logging level for all tracked loggers to the level provided
func (c *Core) SetLogLevel(level log.Level) {
c.allLoggersLock.RLock()
defer c.allLoggersLock.RUnlock()
Expand All @@ -2868,17 +2869,22 @@ func (c *Core) SetLogLevel(level log.Level) {
}
}

func (c *Core) SetLogLevelByName(name string, level log.Level) error {
// SetLogLevelByName sets the logging level of named logger to level provided
// if it exists. Core.allLoggers is a slice and as such it is entirely possible
// that multiple entries exist for the same name. Each instance will be modified.
func (c *Core) SetLogLevelByName(name string, level log.Level) bool {
c.allLoggersLock.RLock()
defer c.allLoggersLock.RUnlock()

found := false
for _, logger := range c.allLoggers {
if logger.Name() == name {
logger.SetLevel(level)
return nil
found = true
}
}

return fmt.Errorf("logger %q does not exist", name)
return found
}

// SetConfig sets core's config object to the newly provided config.
Expand Down
129 changes: 96 additions & 33 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import (
"time"
"unicode"

"github.com/hashicorp/vault/helper/versions"
"golang.org/x/crypto/sha3"

"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-memdb"
Expand All @@ -32,10 +29,12 @@ import (
semver "github.com/hashicorp/go-version"
"github.com/hashicorp/vault/helper/hostutil"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/logging"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/monitor"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/random"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
Expand All @@ -44,6 +43,7 @@ import (
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/version"
"github.com/mitchellh/mapstructure"
"golang.org/x/crypto/sha3"
)

const (
Expand Down Expand Up @@ -4682,28 +4682,35 @@ func (b *SystemBackend) handleVersionHistoryList(ctx context.Context, req *logic
return logical.ListResponseWithInfo(respKeys, respKeyInfo), nil
}

// getLogLevel returns the hclog.Level that corresponds with the provided level string.
// This differs hclog.LevelFromString in that it supports additional level strings so
// that in remains consistent with the handling found in the "vault server" command.
func getLogLevel(logLevel string) (log.Level, error) {
var level log.Level

switch logLevel {
case "trace":
level = log.Trace
case "debug":
level = log.Debug
case "notice", "info", "":
level = log.Info
case "warn", "warning":
level = log.Warn
case "err", "error":
level = log.Error
default:
return level, fmt.Errorf("unrecognized log level %q", logLevel)
func (b *SystemBackend) handleLoggersRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
b.Core.allLoggersLock.RLock()
defer b.Core.allLoggersLock.RUnlock()

loggers := make(map[string]interface{})
warnings := make([]string, 0)

for _, logger := range b.Core.allLoggers {
loggerName := logger.Name()

// ignore base logger
if loggerName == "" {
continue
}

logLevel, err := logging.TranslateLoggerLevel(logger)
if err != nil {
warnings = append(warnings, fmt.Sprintf("cannot translate level for %q: %s", loggerName, err.Error()))
} else {
loggers[loggerName] = logLevel
}
}

return level, nil
resp := &logical.Response{
Data: loggers,
Warnings: warnings,
}

return resp, nil
}

func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
Expand All @@ -4718,7 +4725,7 @@ func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Req
return logical.ErrorResponse("level is empty"), nil
}

level, err := getLogLevel(logLevel)
level, err := logging.ParseLogLevel(logLevel)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil
}
Expand All @@ -4729,7 +4736,7 @@ func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Req
}

func (b *SystemBackend) handleLoggersDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
level, err := getLogLevel(b.Core.logLevel)
level, err := logging.ParseLogLevel(b.Core.logLevel)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil
}
Expand All @@ -4739,12 +4746,63 @@ func (b *SystemBackend) handleLoggersDelete(ctx context.Context, req *logical.Re
return nil, nil
}

func (b *SystemBackend) handleLoggersByNameRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
nameRaw, nameOk := d.GetOk("name")
if !nameOk {
return logical.ErrorResponse("name is required"), nil
}

name := nameRaw.(string)
if name == "" {
return logical.ErrorResponse("name is empty"), nil
}

b.Core.allLoggersLock.RLock()
defer b.Core.allLoggersLock.RUnlock()

loggers := make(map[string]interface{})
warnings := make([]string, 0)

for _, logger := range b.Core.allLoggers {
loggerName := logger.Name()

// ignore base logger
if loggerName == "" {
continue
}

if loggerName == name {
logLevel, err := logging.TranslateLoggerLevel(logger)

if err != nil {
warnings = append(warnings, fmt.Sprintf("cannot translate level for %q: %s", loggerName, err.Error()))
} else {
loggers[loggerName] = logLevel
}

break
}
}

resp := &logical.Response{
Data: loggers,
Warnings: warnings,
}

return resp, nil
}

func (b *SystemBackend) handleLoggersByNameWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
nameRaw, nameOk := d.GetOk("name")
if !nameOk {
return logical.ErrorResponse("name is required"), nil
}

name := nameRaw.(string)
if name == "" {
return logical.ErrorResponse("name is empty"), nil
}

logLevelRaw, logLevelOk := d.GetOk("level")

if !logLevelOk {
Expand All @@ -4756,14 +4814,14 @@ func (b *SystemBackend) handleLoggersByNameWrite(ctx context.Context, req *logic
return logical.ErrorResponse("level is empty"), nil
}

level, err := getLogLevel(logLevel)
level, err := logging.ParseLogLevel(logLevel)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil
}

err = b.Core.SetLogLevelByName(nameRaw.(string), level)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid params: %s", err.Error())), nil
success := b.Core.SetLogLevelByName(name, level)
if !success {
return logical.ErrorResponse(fmt.Sprintf("logger %q not found", name)), nil
}

return nil, nil
Expand All @@ -4775,14 +4833,19 @@ func (b *SystemBackend) handleLoggersByNameDelete(ctx context.Context, req *logi
return logical.ErrorResponse("name is required"), nil
}

level, err := getLogLevel(b.Core.logLevel)
level, err := logging.ParseLogLevel(b.Core.logLevel)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil
}

err = b.Core.SetLogLevelByName(nameRaw.(string), level)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid params: %s", err.Error())), nil
name := nameRaw.(string)
if name == "" {
return logical.ErrorResponse("name is empty"), nil
}

success := b.Core.SetLogLevelByName(name, level)
if !success {
return logical.ErrorResponse(fmt.Sprintf("logger %q not found", name)), nil
}

return nil, nil
Expand Down
8 changes: 8 additions & 0 deletions vault/logical_system_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ func (b *SystemBackend) configPaths() []*framework.Path {
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.handleLoggersRead,
Summary: "Read the log level for all existing loggers.",
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.handleLoggersWrite,
Summary: "Modify the log level for all existing loggers.",
Expand All @@ -322,6 +326,10 @@ func (b *SystemBackend) configPaths() []*framework.Path {
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.handleLoggersByNameRead,
Summary: "Read the log level for a single logger.",
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.handleLoggersByNameWrite,
Summary: "Modify the log level of a single logger.",
Expand Down
Loading

0 comments on commit b9c0dc8

Please sign in to comment.