VAULT-9427: Add read support to sys/loggers endpoints (#17979)

* 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
This commit is contained in:
Chris Capurso 2022-11-28 11:18:36 -05:00 committed by GitHub
parent a04855c98d
commit 2843cfcdc1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 355 additions and 75 deletions

3
changelog/17979.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:improvement
core: Add read support to `sys/loggers` and `sys/loggers/:name` endpoints
```

View file

@ -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) { func ParseLogLevel(logLevel string) (log.Level, error) {
var result log.Level var result log.Level
logLevel = strings.ToLower(strings.TrimSpace(logLevel)) logLevel = strings.ToLower(strings.TrimSpace(logLevel))
@ -133,3 +135,24 @@ func ParseLogLevel(logLevel string) (log.Level, error) {
return result, nil 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
}

View file

@ -2866,6 +2866,7 @@ func (c *Core) AddLogger(logger log.Logger) {
c.allLoggers = append(c.allLoggers, 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) { func (c *Core) SetLogLevel(level log.Level) {
c.allLoggersLock.RLock() c.allLoggersLock.RLock()
defer c.allLoggersLock.RUnlock() defer c.allLoggersLock.RUnlock()
@ -2874,17 +2875,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() c.allLoggersLock.RLock()
defer c.allLoggersLock.RUnlock() defer c.allLoggersLock.RUnlock()
found := false
for _, logger := range c.allLoggers { for _, logger := range c.allLoggers {
if logger.Name() == name { if logger.Name() == name {
logger.SetLevel(level) 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. // SetConfig sets core's config object to the newly provided config.

View file

@ -20,9 +20,6 @@ import (
"time" "time"
"unicode" "unicode"
"github.com/hashicorp/vault/helper/versions"
"golang.org/x/crypto/sha3"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
@ -32,10 +29,12 @@ import (
semver "github.com/hashicorp/go-version" semver "github.com/hashicorp/go-version"
"github.com/hashicorp/vault/helper/hostutil" "github.com/hashicorp/vault/helper/hostutil"
"github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/logging"
"github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/monitor" "github.com/hashicorp/vault/helper/monitor"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/random" "github.com/hashicorp/vault/helper/random"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/jsonutil"
@ -44,6 +43,7 @@ import (
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/version" "github.com/hashicorp/vault/sdk/version"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"golang.org/x/crypto/sha3"
) )
const ( const (
@ -4833,28 +4833,35 @@ func (b *SystemBackend) handleVersionHistoryList(ctx context.Context, req *logic
return logical.ListResponseWithInfo(respKeys, respKeyInfo), nil return logical.ListResponseWithInfo(respKeys, respKeyInfo), nil
} }
// getLogLevel returns the hclog.Level that corresponds with the provided level string. func (b *SystemBackend) handleLoggersRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
// This differs hclog.LevelFromString in that it supports additional level strings so b.Core.allLoggersLock.RLock()
// that in remains consistent with the handling found in the "vault server" command. defer b.Core.allLoggersLock.RUnlock()
func getLogLevel(logLevel string) (log.Level, error) {
var level log.Level
switch logLevel { loggers := make(map[string]interface{})
case "trace": warnings := make([]string, 0)
level = log.Trace
case "debug": for _, logger := range b.Core.allLoggers {
level = log.Debug loggerName := logger.Name()
case "notice", "info", "":
level = log.Info // ignore base logger
case "warn", "warning": if loggerName == "" {
level = log.Warn continue
case "err", "error":
level = log.Error
default:
return level, fmt.Errorf("unrecognized log level %q", logLevel)
} }
return level, nil 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
}
}
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) { func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
@ -4869,7 +4876,7 @@ func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Req
return logical.ErrorResponse("level is empty"), nil return logical.ErrorResponse("level is empty"), nil
} }
level, err := getLogLevel(logLevel) level, err := logging.ParseLogLevel(logLevel)
if err != nil { if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil
} }
@ -4880,7 +4887,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) { 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 { if err != nil {
return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil
} }
@ -4890,12 +4897,63 @@ func (b *SystemBackend) handleLoggersDelete(ctx context.Context, req *logical.Re
return nil, nil 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) { func (b *SystemBackend) handleLoggersByNameWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
nameRaw, nameOk := d.GetOk("name") nameRaw, nameOk := d.GetOk("name")
if !nameOk { if !nameOk {
return logical.ErrorResponse("name is required"), nil return logical.ErrorResponse("name is required"), nil
} }
name := nameRaw.(string)
if name == "" {
return logical.ErrorResponse("name is empty"), nil
}
logLevelRaw, logLevelOk := d.GetOk("level") logLevelRaw, logLevelOk := d.GetOk("level")
if !logLevelOk { if !logLevelOk {
@ -4907,14 +4965,14 @@ func (b *SystemBackend) handleLoggersByNameWrite(ctx context.Context, req *logic
return logical.ErrorResponse("level is empty"), nil return logical.ErrorResponse("level is empty"), nil
} }
level, err := getLogLevel(logLevel) level, err := logging.ParseLogLevel(logLevel)
if err != nil { if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil
} }
err = b.Core.SetLogLevelByName(nameRaw.(string), level) success := b.Core.SetLogLevelByName(name, level)
if err != nil { if !success {
return logical.ErrorResponse(fmt.Sprintf("invalid params: %s", err.Error())), nil return logical.ErrorResponse(fmt.Sprintf("logger %q not found", name)), nil
} }
return nil, nil return nil, nil
@ -4926,14 +4984,19 @@ func (b *SystemBackend) handleLoggersByNameDelete(ctx context.Context, req *logi
return logical.ErrorResponse("name is required"), nil return logical.ErrorResponse("name is required"), nil
} }
level, err := getLogLevel(b.Core.logLevel) level, err := logging.ParseLogLevel(b.Core.logLevel)
if err != nil { if err != nil {
return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil
} }
err = b.Core.SetLogLevelByName(nameRaw.(string), level) name := nameRaw.(string)
if err != nil { if name == "" {
return logical.ErrorResponse(fmt.Sprintf("invalid params: %s", err.Error())), nil 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 return nil, nil

View file

@ -298,6 +298,10 @@ func (b *SystemBackend) configPaths() []*framework.Path {
}, },
}, },
Operations: map[logical.Operation]framework.OperationHandler{ 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{ logical.UpdateOperation: &framework.PathOperation{
Callback: b.handleLoggersWrite, Callback: b.handleLoggersWrite,
Summary: "Modify the log level for all existing loggers.", Summary: "Modify the log level for all existing loggers.",
@ -322,6 +326,10 @@ func (b *SystemBackend) configPaths() []*framework.Path {
}, },
}, },
Operations: map[logical.Operation]framework.OperationHandler{ 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{ logical.UpdateOperation: &framework.PathOperation{
Callback: b.handleLoggersByNameWrite, Callback: b.handleLoggersByNameWrite,
Summary: "Modify the log level of a single logger.", Summary: "Modify the log level of a single logger.",

View file

@ -4772,66 +4772,60 @@ func TestProcessLimit(t *testing.T) {
} }
} }
func validateLevel(level string, logger hclog.Logger) bool {
switch level {
case "trace":
return logger.IsTrace()
case "debug":
return logger.IsDebug()
case "notice", "info", "":
return logger.IsInfo()
case "warn", "warning":
return logger.IsWarn()
case "err", "error":
return logger.IsError()
}
return false
}
func TestSystemBackend_Loggers(t *testing.T) { func TestSystemBackend_Loggers(t *testing.T) {
testCases := []struct { testCases := []struct {
level string level string
expectedLevel string
expectError bool expectError bool
}{ }{
{ {
"trace",
"trace", "trace",
false, false,
}, },
{ {
"debug",
"debug", "debug",
false, false,
}, },
{ {
"notice", "notice",
"info",
false, false,
}, },
{ {
"info",
"info", "info",
false, false,
}, },
{ {
"warn",
"warn", "warn",
false, false,
}, },
{ {
"warning", "warning",
"warn",
false, false,
}, },
{ {
"err", "err",
"error",
false, false,
}, },
{ {
"error",
"error", "error",
false, false,
}, },
{ {
"", "",
"info",
true, true,
}, },
{ {
"invalid", "invalid",
"",
true, true,
}, },
} }
@ -4844,7 +4838,33 @@ func TestSystemBackend_Loggers(t *testing.T) {
core, b, _ := testCoreSystemBackend(t) core, b, _ := testCoreSystemBackend(t)
// Test core overrides logging level outside of config,
// an initial delete will ensure that we an initial read
// to get expected values is based off of config and not
// the test override that is hidden from this test
req := &logical.Request{ req := &logical.Request{
Path: "loggers",
Operation: logical.DeleteOperation,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
req = &logical.Request{
Path: "loggers",
Operation: logical.ReadOperation,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
initialLoggers := resp.Data
req = &logical.Request{
Path: "loggers", Path: "loggers",
Operation: logical.UpdateOperation, Operation: logical.UpdateOperation,
Data: map[string]interface{}{ Data: map[string]interface{}{
@ -4852,7 +4872,7 @@ func TestSystemBackend_Loggers(t *testing.T) {
}, },
} }
resp, err := b.HandleRequest(namespace.RootContext(nil), req) resp, err = b.HandleRequest(namespace.RootContext(nil), req)
respIsError := resp != nil && resp.IsError() respIsError := resp != nil && resp.IsError()
if err != nil || (!tc.expectError && respIsError) { if err != nil || (!tc.expectError && respIsError) {
@ -4864,16 +4884,9 @@ func TestSystemBackend_Loggers(t *testing.T) {
} }
if !tc.expectError { if !tc.expectError {
for _, logger := range core.allLoggers {
if !validateLevel(tc.level, logger) {
t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level)
}
}
}
req = &logical.Request{ req = &logical.Request{
Path: fmt.Sprintf("loggers"), Path: "loggers",
Operation: logical.DeleteOperation, Operation: logical.ReadOperation,
} }
resp, err = b.HandleRequest(namespace.RootContext(nil), req) resp, err = b.HandleRequest(namespace.RootContext(nil), req)
@ -4882,8 +4895,52 @@ func TestSystemBackend_Loggers(t *testing.T) {
} }
for _, logger := range core.allLoggers { for _, logger := range core.allLoggers {
if !validateLevel(core.logLevel, logger) { loggerName := logger.Name()
t.Errorf("expected level of logger %q to match original config", logger.Name()) levelRaw, ok := resp.Data[loggerName]
if !ok {
t.Errorf("logger %q not found in response", loggerName)
}
if levelStr := levelRaw.(string); levelStr != tc.expectedLevel {
t.Errorf("unexpected level of logger %q, expected: %s, actual: %s", loggerName, tc.expectedLevel, levelStr)
}
}
}
req = &logical.Request{
Path: "loggers",
Operation: logical.DeleteOperation,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
req = &logical.Request{
Path: "loggers",
Operation: logical.ReadOperation,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
for _, logger := range core.allLoggers {
loggerName := logger.Name()
levelRaw, currentOk := resp.Data[loggerName]
initialLevelRaw, initialOk := initialLoggers[loggerName]
if !currentOk || !initialOk {
t.Errorf("logger %q not found", loggerName)
}
levelStr := levelRaw.(string)
initialLevelStr := initialLevelRaw.(string)
if levelStr != initialLevelStr {
t.Errorf("expected level of logger %q to match original config, expected: %s, actual: %s", loggerName, initialLevelStr, levelStr)
} }
} }
}) })
@ -4894,78 +4951,91 @@ func TestSystemBackend_LoggersByName(t *testing.T) {
testCases := []struct { testCases := []struct {
logger string logger string
level string level string
expectedLevel string
expectWriteError bool expectWriteError bool
expectDeleteError bool expectDeleteError bool
}{ }{
{ {
"core", "core",
"trace", "trace",
"trace",
false, false,
false, false,
}, },
{ {
"token", "token",
"debug", "debug",
"debug",
false, false,
false, false,
}, },
{ {
"audit", "audit",
"notice", "notice",
"info",
false, false,
false, false,
}, },
{ {
"expiration", "expiration",
"info", "info",
"info",
false, false,
false, false,
}, },
{ {
"policy", "policy",
"warn", "warn",
"warn",
false, false,
false, false,
}, },
{ {
"activity", "activity",
"warning", "warning",
"warn",
false, false,
false, false,
}, },
{ {
"identity", "identity",
"err", "err",
"error",
false, false,
false, false,
}, },
{ {
"rollback", "rollback",
"error", "error",
"error",
false, false,
false, false,
}, },
{ {
"system", "system",
"", "",
"does-not-matter",
true, true,
false, false,
}, },
{ {
"quotas", "quotas",
"invalid", "invalid",
"does-not-matter",
true, true,
false, false,
}, },
{ {
"", "",
"info", "info",
"does-not-matter",
true, true,
true, true,
}, },
{ {
"does_not_exist", "does_not_exist",
"error", "error",
"does-not-matter",
true, true,
true, true,
}, },
@ -4979,16 +5049,41 @@ func TestSystemBackend_LoggersByName(t *testing.T) {
core, b, _ := testCoreSystemBackend(t) core, b, _ := testCoreSystemBackend(t)
// Test core overrides logging level outside of config,
// an initial delete will ensure that we an initial read
// to get expected values is based off of config and not
// the test override that is hidden from this test
req := &logical.Request{ req := &logical.Request{
Path: "loggers",
Operation: logical.DeleteOperation,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
req = &logical.Request{
Path: "loggers",
Operation: logical.ReadOperation,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
initialLoggers := resp.Data
req = &logical.Request{
Path: fmt.Sprintf("loggers/%s", tc.logger), Path: fmt.Sprintf("loggers/%s", tc.logger),
Operation: logical.UpdateOperation, Operation: logical.UpdateOperation,
Data: map[string]interface{}{ Data: map[string]interface{}{
"name": tc.logger,
"level": tc.level, "level": tc.level,
}, },
} }
resp, err := b.HandleRequest(namespace.RootContext(nil), req) resp, err = b.HandleRequest(namespace.RootContext(nil), req)
respIsError := resp != nil && resp.IsError() respIsError := resp != nil && resp.IsError()
if err != nil || (!tc.expectWriteError && respIsError) { if err != nil || (!tc.expectWriteError && respIsError) {
@ -5000,13 +5095,34 @@ func TestSystemBackend_LoggersByName(t *testing.T) {
} }
if !tc.expectWriteError { if !tc.expectWriteError {
for _, logger := range core.allLoggers { req = &logical.Request{
if logger.Name() != tc.logger && !validateLevel(core.logLevel, logger) { Path: "loggers",
t.Errorf("expected level of logger %q to be unchanged", logger.Name()) Operation: logical.ReadOperation,
} }
if !validateLevel(tc.level, logger) { resp, err = b.HandleRequest(namespace.RootContext(nil), req)
t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
for _, logger := range core.allLoggers {
loggerName := logger.Name()
levelRaw, currentOk := resp.Data[loggerName]
initialLevelRaw, initialOk := initialLoggers[loggerName]
if !currentOk || !initialOk {
t.Errorf("logger %q not found", loggerName)
}
levelStr := levelRaw.(string)
initialLevelStr := initialLevelRaw.(string)
if loggerName == tc.logger && levelStr != tc.expectedLevel {
t.Fatalf("expected logger %q to be %q, actual: %s", loggerName, tc.expectedLevel, levelStr)
}
if loggerName != tc.logger && levelStr != initialLevelStr {
t.Errorf("expected level of logger %q to be unchanged, exepcted: %s, actual: %s", loggerName, initialLevelStr, levelStr)
} }
} }
} }
@ -5014,9 +5130,6 @@ func TestSystemBackend_LoggersByName(t *testing.T) {
req = &logical.Request{ req = &logical.Request{
Path: fmt.Sprintf("loggers/%s", tc.logger), Path: fmt.Sprintf("loggers/%s", tc.logger),
Operation: logical.DeleteOperation, Operation: logical.DeleteOperation,
Data: map[string]interface{}{
"name": tc.logger,
},
} }
resp, err = b.HandleRequest(namespace.RootContext(nil), req) resp, err = b.HandleRequest(namespace.RootContext(nil), req)
@ -5031,10 +5144,28 @@ func TestSystemBackend_LoggersByName(t *testing.T) {
} }
if !tc.expectDeleteError { if !tc.expectDeleteError {
for _, logger := range core.allLoggers { req = &logical.Request{
if !validateLevel(core.logLevel, logger) { Path: fmt.Sprintf("loggers/%s", tc.logger),
t.Errorf("expected level of logger %q to match original config", logger.Name()) Operation: logical.ReadOperation,
} }
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp)
}
currentLevel, ok := resp.Data[tc.logger].(string)
if !ok {
t.Fatalf("expected resp to include %q, resp: %#v", tc.logger, resp)
}
initialLevel, ok := initialLoggers[tc.logger].(string)
if !ok {
t.Fatalf("expected initial loggers to include %q, resp: %#v", tc.logger, initialLoggers)
}
if currentLevel != initialLevel {
t.Errorf("expected level of logger %q to match original config, expected: %s, actual: %s", tc.logger, initialLevel, currentLevel)
} }
} }
}) })

View file

@ -71,6 +71,52 @@ $ curl \
http://127.0.0.1:8200/v1/sys/loggers/core http://127.0.0.1:8200/v1/sys/loggers/core
``` ```
## Read verbosity level of all loggers
| Method | Path |
| :----- | :------------- |
| `GET` | `/sys/loggers` |
### Sample Request
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
https://127.0.0.1:8200/v1/sys/loggers
```
### Sample Response
```json
{
"audit": "trace",
"core": "info",
"policy": "debug"
}
```
## Read verbosity level of a single logger
| Method | Path |
| :----- | :------------------- |
| `GET` | `/sys/loggers/:name` |
### Sample Request
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
https://127.0.0.1:8200/v1/sys/loggers/core
```
### Sample Response
```json
{
"core": "info"
}
```
## Revert verbosity of all loggers to configured level ## Revert verbosity of all loggers to configured level
| Method | Path | | Method | Path |