From 91baa7d8017414ff5139490dc0699270d1a8ea58 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Tue, 21 Jun 2022 08:53:37 -0400 Subject: [PATCH 01/11] add func to set level for specific logger --- vault/core.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vault/core.go b/vault/core.go index 79dff2ad86ab..01d27853fcf1 100644 --- a/vault/core.go +++ b/vault/core.go @@ -2816,6 +2816,19 @@ func (c *Core) SetLogLevel(level log.Level) { } } +func (c *Core) SetLogLevelByName(name string, level log.Level) error { + c.allLoggersLock.RLock() + defer c.allLoggersLock.RUnlock() + for _, logger := range c.allLoggers { + if logger.Name() == name { + logger.SetLevel(level) + return nil + } + } + + return fmt.Errorf("logger %q does not exist", name) +} + // SetConfig sets core's config object to the newly provided config. func (c *Core) SetConfig(conf *server.Config) { c.rawConfig.Store(conf) From 05c622fc8ef3a106a6fee61568100a477fc49940 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Tue, 21 Jun 2022 09:06:34 -0400 Subject: [PATCH 02/11] add endpoints to modify log level --- vault/logical_system.go | 68 +++++++++++ vault/logical_system_paths.go | 36 ++++++ vault/logical_system_test.go | 211 ++++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+) diff --git a/vault/logical_system.go b/vault/logical_system.go index e4d168be25a9..f014eb4bf56a 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -4436,6 +4436,74 @@ func (b *SystemBackend) handleVersionHistoryList(ctx context.Context, req *logic return logical.ListResponseWithInfo(respKeys, respKeyInfo), nil } +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) + } + + return level, nil +} + +func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + logLevelRaw, ok := d.GetOk("level") + + if !ok { + return logical.ErrorResponse("level is required"), nil + } + + level, err := getLogLevel(logLevelRaw.(string)) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil + } + + b.Core.SetLogLevel(level) + + return nil, 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 + } + + logLevelRaw, logLevelOk := d.GetOk("level") + + if !logLevelOk { + return logical.ErrorResponse("level is required"), nil + } + + logLevel := logLevelRaw.(string) + if logLevel == "" { + return logical.ErrorResponse("level is empty"), nil + } + + level, err := getLogLevel(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 + } + + return nil, nil +} + func sanitizePath(path string) string { if !strings.HasSuffix(path, "/") { path += "/" diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 82129c92b45b..6dc8dfacbf2c 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -288,6 +288,42 @@ func (b *SystemBackend) configPaths() []*framework.Path { }, }, }, + { + Pattern: "loggers$", + Fields: map[string]*framework.FieldSchema{ + "level": { + Type: framework.TypeString, + Description: "Log verbosity level. Supported values (in order of detail) are " + + "\"trace\", \"debug\", \"info\", \"warn\", and \"error\".", + }, + }, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.handleLoggersWrite, + Summary: "Modify the log level for all existing loggers.", + }, + }, + }, + { + Pattern: "loggers/" + framework.MatchAllRegex("name"), + Fields: map[string]*framework.FieldSchema{ + "name": { + Type: framework.TypeString, + Description: "The name of the logger to be modified.", + }, + "level": { + Type: framework.TypeString, + Description: "Log verbosity level. Supported values (in order of detail) are " + + "\"trace\", \"debug\", \"info\", \"warn\", and \"error\".", + }, + }, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.handleLoggersByNameWrite, + Summary: "Modify the log level of a single logger.", + }, + }, + }, } } diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 4f889d7133f9..292f80816cc0 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -4556,3 +4556,214 @@ func TestProcessLimit(t *testing.T) { } } } + +func TestSystemBackend_Loggers(t *testing.T) { + testCases := []struct { + level string + validator func(hclog.Logger) bool + expectError bool + }{ + { + "trace", + func(l hclog.Logger) bool { return l.IsTrace() }, + false, + }, + { + "debug", + func(l hclog.Logger) bool { return l.IsDebug() }, + false, + }, + { + "notice", + func(l hclog.Logger) bool { return l.IsInfo() }, + false, + }, + { + "info", + func(l hclog.Logger) bool { return l.IsInfo() }, + false, + }, + { + "warn", + func(l hclog.Logger) bool { return l.IsWarn() }, + false, + }, + { + "warning", + func(l hclog.Logger) bool { return l.IsWarn() }, + false, + }, + { + "err", + func(l hclog.Logger) bool { return l.IsError() }, + false, + }, + { + "error", + func(l hclog.Logger) bool { return l.IsError() }, + false, + }, + { + "", + func(l hclog.Logger) bool { return true }, + true, + }, + { + "invalid", + func(l hclog.Logger) bool { return true }, + true, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(fmt.Sprintf("all-loggers-%s", tc.level), func(t *testing.T) { + t.Parallel() + + core, b, _ := testCoreSystemBackend(t) + + req := &logical.Request{ + Path: "loggers", + Operation: logical.UpdateOperation, + Data: map[string]interface{}{ + "level": tc.level, + }, + } + + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + respIsError := resp != nil && resp.IsError() + + if err != nil || (!tc.expectError && respIsError) { + t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) + } + + if tc.expectError && !respIsError { + t.Fatalf("expected response error, resp: %#v", resp) + } + + for _, logger := range core.allLoggers { + if !tc.validator(logger) { + t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) + } + } + }) + } +} + +func TestSystemBackend_LoggersByName(t *testing.T) { + testCases := []struct { + logger string + level string + validator func(hclog.Logger) bool + expectError bool + }{ + { + "core", + "trace", + func(l hclog.Logger) bool { return l.IsTrace() }, + false, + }, + { + "token", + "debug", + func(l hclog.Logger) bool { return l.IsDebug() }, + false, + }, + { + "audit", + "notice", + func(l hclog.Logger) bool { return l.IsInfo() }, + false, + }, + { + "expiration", + "info", + func(l hclog.Logger) bool { return l.IsInfo() }, + false, + }, + { + "policy", + "warn", + func(l hclog.Logger) bool { return l.IsWarn() }, + false, + }, + { + "activity", + "warning", + func(l hclog.Logger) bool { return l.IsWarn() }, + false, + }, + { + "identity", + "err", + func(l hclog.Logger) bool { return l.IsError() }, + false, + }, + { + "rollback", + "error", + func(l hclog.Logger) bool { return l.IsError() }, + false, + }, + { + "system", + "", + func(l hclog.Logger) bool { return true }, + true, + }, + { + "quotas", + "invalid", + func(l hclog.Logger) bool { return true }, + true, + }, + { + "", + "info", + func(l hclog.Logger) bool { return true }, + true, + }, + { + "does_not_exist", + "error", + func(l hclog.Logger) bool { return true }, + true, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(fmt.Sprintf("loggers-by-name-%s", tc.logger), func(t *testing.T) { + t.Parallel() + + core, b, _ := testCoreSystemBackend(t) + + req := &logical.Request{ + Path: fmt.Sprintf("loggers/%s", tc.logger), + Operation: logical.UpdateOperation, + Data: map[string]interface{}{ + "level": tc.level, + }, + } + + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + respIsError := resp != nil && resp.IsError() + + if err != nil || (!tc.expectError && respIsError) { + t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) + } + + if tc.expectError && !respIsError { + t.Fatalf("expected response error, resp: %#v", resp) + } + + for _, logger := range core.allLoggers { + if !tc.validator(logger) { + t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) + } + } + }) + } +} From 7476b8d828e0814c67c42f6560acd8c39d3d4cf4 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:55:57 -0400 Subject: [PATCH 03/11] initialize base logger with IndependentLevels --- command/server.go | 17 ++++++++++------- sdk/helper/logging/logging.go | 7 ++++--- vault/testing.go | 8 +++++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/command/server.go b/command/server.go index 4ba9a783e463..9eb0a5a75b3f 100644 --- a/command/server.go +++ b/command/server.go @@ -460,8 +460,9 @@ func (c *ServerCommand) runRecoveryMode() int { } c.logger = hclog.NewInterceptLogger(&hclog.LoggerOptions{ - Output: c.gatedWriter, - Level: level, + Output: c.gatedWriter, + Level: level, + IndependentLevels: true, // Note that if logFormat is either unspecified or standard, then // the resulting logger's format will be standard. JSONFormat: logFormat == logging.JSONFormat, @@ -1116,14 +1117,16 @@ func (c *ServerCommand) Run(args []string) int { if c.flagDevThreeNode || c.flagDevFourCluster { c.logger = hclog.NewInterceptLogger(&hclog.LoggerOptions{ - Mutex: &sync.Mutex{}, - Output: c.gatedWriter, - Level: hclog.Trace, + Mutex: &sync.Mutex{}, + Output: c.gatedWriter, + Level: hclog.Trace, + IndependentLevels: true, }) } else { c.logger = hclog.NewInterceptLogger(&hclog.LoggerOptions{ - Output: c.gatedWriter, - Level: level, + Output: c.gatedWriter, + Level: level, + IndependentLevels: true, // Note that if logFormat is either unspecified or standard, then // the resulting logger's format will be standard. JSONFormat: logFormat == logging.JSONFormat, diff --git a/sdk/helper/logging/logging.go b/sdk/helper/logging/logging.go index a8d30674b1b5..211a545e3360 100644 --- a/sdk/helper/logging/logging.go +++ b/sdk/helper/logging/logging.go @@ -42,9 +42,10 @@ func NewVaultLogger(level log.Level) log.Logger { // writer and a Vault formatter func NewVaultLoggerWithWriter(w io.Writer, level log.Level) log.Logger { opts := &log.LoggerOptions{ - Level: level, - Output: w, - JSONFormat: ParseEnvLogFormat() == JSONFormat, + Level: level, + IndependentLevels: true, + Output: w, + JSONFormat: ParseEnvLogFormat() == JSONFormat, } return log.New(opts) } diff --git a/vault/testing.go b/vault/testing.go index 652861c9b1f2..97a9c76d4342 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -1197,11 +1197,13 @@ func NewTestLogger(t testing.T) *TestLogger { // We send nothing on the regular logger, that way we can later deregister // the sink to stop logging during cluster cleanup. logger := log.NewInterceptLogger(&log.LoggerOptions{ - Output: ioutil.Discard, + Output: ioutil.Discard, + IndependentLevels: true, }) sink := log.NewSinkAdapter(&log.LoggerOptions{ - Output: output, - Level: log.Trace, + Output: output, + Level: log.Trace, + IndependentLevels: true, }) logger.RegisterSink(sink) return &TestLogger{ From 9d3c0678fddaa4d7031a3334835eb2f246f50e92 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:56:51 -0400 Subject: [PATCH 04/11] test to ensure other loggers remain unchanged --- vault/logical_system_test.go | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 292f80816cc0..50409d0133e5 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -4651,83 +4651,87 @@ func TestSystemBackend_Loggers(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_LoggersByName(t *testing.T) { testCases := []struct { logger string level string - validator func(hclog.Logger) bool expectError bool }{ { "core", "trace", - func(l hclog.Logger) bool { return l.IsTrace() }, false, }, { "token", "debug", - func(l hclog.Logger) bool { return l.IsDebug() }, false, }, { "audit", "notice", - func(l hclog.Logger) bool { return l.IsInfo() }, false, }, { "expiration", "info", - func(l hclog.Logger) bool { return l.IsInfo() }, false, }, { "policy", "warn", - func(l hclog.Logger) bool { return l.IsWarn() }, false, }, { "activity", "warning", - func(l hclog.Logger) bool { return l.IsWarn() }, false, }, { "identity", "err", - func(l hclog.Logger) bool { return l.IsError() }, false, }, { "rollback", "error", - func(l hclog.Logger) bool { return l.IsError() }, false, }, { "system", "", - func(l hclog.Logger) bool { return true }, true, }, { "quotas", "invalid", - func(l hclog.Logger) bool { return true }, true, }, { "", "info", - func(l hclog.Logger) bool { return true }, true, }, { "does_not_exist", "error", - func(l hclog.Logger) bool { return true }, true, }, } @@ -4739,11 +4743,13 @@ func TestSystemBackend_LoggersByName(t *testing.T) { t.Parallel() core, b, _ := testCoreSystemBackend(t) + config := core.GetCoreConfigInternal() req := &logical.Request{ Path: fmt.Sprintf("loggers/%s", tc.logger), Operation: logical.UpdateOperation, Data: map[string]interface{}{ + "name": tc.logger, "level": tc.level, }, } @@ -4760,7 +4766,11 @@ func TestSystemBackend_LoggersByName(t *testing.T) { } for _, logger := range core.allLoggers { - if !tc.validator(logger) { + if logger.Name() != tc.logger && !validateLevel(config.LogLevel, logger) { + t.Errorf("expected level of logger %q to be unchanged", logger.Name()) + } + + if !tc.expectError && !validateLevel(tc.level, logger) { t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) } } From 9316e9ea8012e6f5c89fadfb910724c41ae06d6a Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 12:00:22 -0400 Subject: [PATCH 05/11] add DELETE loggers endpoints to revert back to config --- vault/logical_system.go | 41 ++++++++++- vault/logical_system_paths.go | 8 +++ vault/logical_system_test.go | 130 +++++++++++++++++++++++----------- 3 files changed, 137 insertions(+), 42 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index f014eb4bf56a..7f1a64b4539f 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -4444,7 +4444,7 @@ func getLogLevel(logLevel string) (log.Level, error) { level = log.Trace case "debug": level = log.Debug - case "notice", "info": + case "notice", "info", "": level = log.Info case "warn", "warning": level = log.Warn @@ -4464,7 +4464,12 @@ func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Req return logical.ErrorResponse("level is required"), nil } - level, err := getLogLevel(logLevelRaw.(string)) + logLevel := logLevelRaw.(string) + if logLevel == "" { + return logical.ErrorResponse("level is empty"), nil + } + + level, err := getLogLevel(logLevel) if err != nil { return logical.ErrorResponse(fmt.Sprintf("invalid level provided: %s", err.Error())), nil } @@ -4474,6 +4479,18 @@ func (b *SystemBackend) handleLoggersWrite(ctx context.Context, req *logical.Req return nil, nil } +func (b *SystemBackend) handleLoggersDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + config := b.Core.GetCoreConfigInternal() + level, err := getLogLevel(config.LogLevel) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil + } + + b.Core.SetLogLevel(level) + + return nil, nil +} + func (b *SystemBackend) handleLoggersByNameWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { nameRaw, nameOk := d.GetOk("name") if !nameOk { @@ -4504,6 +4521,26 @@ func (b *SystemBackend) handleLoggersByNameWrite(ctx context.Context, req *logic return nil, nil } +func (b *SystemBackend) handleLoggersByNameDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + nameRaw, ok := d.GetOk("name") + if !ok { + return logical.ErrorResponse("name is required"), nil + } + + config := b.Core.GetCoreConfigInternal() + level, err := getLogLevel(config.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 + } + + return nil, nil +} + func sanitizePath(path string) string { if !strings.HasSuffix(path, "/") { path += "/" diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 6dc8dfacbf2c..6b1516217a6e 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -302,6 +302,10 @@ func (b *SystemBackend) configPaths() []*framework.Path { Callback: b.handleLoggersWrite, Summary: "Modify the log level for all existing loggers.", }, + logical.DeleteOperation: &framework.PathOperation{ + Callback: b.handleLoggersDelete, + Summary: "Revert the all loggers to use log level provided in config.", + }, }, }, { @@ -322,6 +326,10 @@ func (b *SystemBackend) configPaths() []*framework.Path { Callback: b.handleLoggersByNameWrite, Summary: "Modify the log level of a single logger.", }, + logical.DeleteOperation: &framework.PathOperation{ + Callback: b.handleLoggersByNameDelete, + Summary: "Revert a single logger to use log level provided in config.", + }, }, }, } diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 50409d0133e5..b23df0d3612d 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -4557,60 +4557,66 @@ 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) { testCases := []struct { level string - validator func(hclog.Logger) bool expectError bool }{ { "trace", - func(l hclog.Logger) bool { return l.IsTrace() }, false, }, { "debug", - func(l hclog.Logger) bool { return l.IsDebug() }, false, }, { "notice", - func(l hclog.Logger) bool { return l.IsInfo() }, false, }, { "info", - func(l hclog.Logger) bool { return l.IsInfo() }, false, }, { "warn", - func(l hclog.Logger) bool { return l.IsWarn() }, false, }, { "warning", - func(l hclog.Logger) bool { return l.IsWarn() }, false, }, { "err", - func(l hclog.Logger) bool { return l.IsError() }, false, }, { "error", - func(l hclog.Logger) bool { return l.IsError() }, false, }, { "", - func(l hclog.Logger) bool { return true }, true, }, { "invalid", - func(l hclog.Logger) bool { return true }, true, }, } @@ -4622,6 +4628,7 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Parallel() core, b, _ := testCoreSystemBackend(t) + config := core.GetCoreConfigInternal() req := &logical.Request{ Path: "loggers", @@ -4642,97 +4649,111 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Fatalf("expected response error, resp: %#v", resp) } + 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{ + Path: fmt.Sprintf("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) + } + for _, logger := range core.allLoggers { - if !tc.validator(logger) { - t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) + if !validateLevel(config.LogLevel, logger) { + t.Errorf("expected level of logger %q to match original config", logger.Name()) } } }) } } -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_LoggersByName(t *testing.T) { testCases := []struct { - logger string - level string - expectError bool + logger string + level string + expectWriteError bool + expectDeleteError bool }{ { "core", "trace", false, + false, }, { "token", "debug", false, + false, }, { "audit", "notice", false, + false, }, { "expiration", "info", false, + false, }, { "policy", "warn", false, + false, }, { "activity", "warning", false, + false, }, { "identity", "err", false, + false, }, { "rollback", "error", false, + false, }, { "system", "", true, + false, }, { "quotas", "invalid", true, + false, }, { "", "info", true, + true, }, { "does_not_exist", "error", true, + true, }, } @@ -4757,21 +4778,50 @@ func TestSystemBackend_LoggersByName(t *testing.T) { resp, err := b.HandleRequest(namespace.RootContext(nil), req) respIsError := resp != nil && resp.IsError() - if err != nil || (!tc.expectError && respIsError) { + if err != nil || (!tc.expectWriteError && respIsError) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } - if tc.expectError && !respIsError { + if tc.expectWriteError && !respIsError { t.Fatalf("expected response error, resp: %#v", resp) } - for _, logger := range core.allLoggers { - if logger.Name() != tc.logger && !validateLevel(config.LogLevel, logger) { - t.Errorf("expected level of logger %q to be unchanged", logger.Name()) + if !tc.expectWriteError { + for _, logger := range core.allLoggers { + if logger.Name() != tc.logger && !validateLevel(config.LogLevel, logger) { + t.Errorf("expected level of logger %q to be unchanged", logger.Name()) + } + + if !validateLevel(tc.level, logger) { + t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) + } } + } + + req = &logical.Request{ + Path: fmt.Sprintf("loggers/%s", tc.logger), + Operation: logical.DeleteOperation, + Data: map[string]interface{}{ + "name": tc.logger, + }, + } + + resp, err = b.HandleRequest(namespace.RootContext(nil), req) + respIsError = resp != nil && resp.IsError() + + if err != nil || (!tc.expectDeleteError && respIsError) { + t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) + } + + if tc.expectDeleteError && !respIsError { + t.Fatalf("expected response error, resp: %#v", resp) + } - if !tc.expectError && !validateLevel(tc.level, logger) { - t.Fatalf("expected logger %q to be %q", logger.Name(), tc.level) + if !tc.expectDeleteError { + for _, logger := range core.allLoggers { + if !validateLevel(config.LogLevel, logger) { + t.Errorf("expected level of logger %q to match original config", logger.Name()) + } } } }) From 92943e8b42d0a81472eed0ed4d6a566491a4e0cd Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:12:48 -0400 Subject: [PATCH 06/11] add API docs page --- website/content/api-docs/system/loggers.mdx | 102 ++++++++++++++++++++ website/data/api-docs-nav-data.json | 4 + 2 files changed, 106 insertions(+) create mode 100644 website/content/api-docs/system/loggers.mdx diff --git a/website/content/api-docs/system/loggers.mdx b/website/content/api-docs/system/loggers.mdx new file mode 100644 index 000000000000..494395d0422a --- /dev/null +++ b/website/content/api-docs/system/loggers.mdx @@ -0,0 +1,102 @@ +--- +layout: api +page_title: /sys/loggers - HTTP API +description: The `/sys/loggers` endpoint is used modify the verbosity level of logging. +--- + +# `/sys/loggers` + +The `/sys/loggers` endpoint is used modify the verbosity level of logging. + +## Modify verbosity level of all loggers + +| Method | Path | +| :------ | :------------- | +| `POST` | `/sys/loggers` | + +### Parameters + +- `level` `(string: )` – Specifies the log verbosity level to be set for all loggers. +Supported values (in order of detail) are `"trace"`, `"debug"`, `"info"`, `"warn"`, and `"error"`. + +### Sample Payload + +```json +{ + "level": "debug", +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "X-Vault-Token: ..." \ + --request POST \ + --data @payload.json \ + http://127.0.0.1:8200/v1/sys/loggers +``` + +## Modify verbosity level of a single logger + +| Method | Path | +| :------ | :------------------- | +| `POST` | `/sys/loggers/:name` | + +### Parameters + +- `name` `(string: )` – Specifies the logger to be modified (e.g. `audit`, `core`, `expiration`). +- `level` `(string: )` – Specifies the log verbosity level to be set for the provided logger. +Supported values (in order of detail) are `"trace"`, `"debug"`, `"info"`, `"warn"`, and `"error"`. + +### Sample Payload + +```json +{ + "level": "debug", +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "X-Vault-Token: ..." \ + --request POST \ + --data @payload.json \ + http://127.0.0.1:8200/v1/sys/loggers/core +``` + +## Revert verbosity of all loggers to configured level + +| Method | Path | +| :-------- | :------------- | +| `DELETE` | `/sys/loggers` | + +### Sample Request + +```shell-session +$ curl \ + --header "X-Vault-Token: ..." \ + --request DELETE \ + http://127.0.0.1:8200/v1/sys/loggers +``` + +## Revert verbosity of a single logger to configured level + +| Method | Path | +| :-------- | :------------------- | +| `DELETE` | `/sys/loggers/:name` | + +### Parameters + +- `name` `(string: )` – Specifies the logger to be modified (e.g. `audit`, `core`, `expiration`). + +### Sample Request + +```shell-session +$ curl \ + --header "X-Vault-Token: ..." \ + --request DELETE \ + http://127.0.0.1:8200/v1/sys/loggers/core +``` diff --git a/website/data/api-docs-nav-data.json b/website/data/api-docs-nav-data.json index 52b3db9bfbfa..2c28ea3881c5 100644 --- a/website/data/api-docs-nav-data.json +++ b/website/data/api-docs-nav-data.json @@ -482,6 +482,10 @@ "title": "/sys/license/status", "path": "system/license" }, + { + "title": "/sys/loggers", + "path": "system/loggers" + }, { "title": "/sys/managed-keys ENT", "path": "system/managed-keys" From 1c491a67f323697b93fda1c40901c65871076b7c Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:17:26 -0400 Subject: [PATCH 07/11] add changelog entry --- changelog/16111.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/16111.txt diff --git a/changelog/16111.txt b/changelog/16111.txt new file mode 100644 index 000000000000..a81e4e6a19d0 --- /dev/null +++ b/changelog/16111.txt @@ -0,0 +1,4 @@ +```release-note:improvement +core: Add `sys/loggers` and `sys/loggers/:name` endpoints to provide ability to modify logging verbosity +``` + From 0d832a0d572c20352adbaf94da9b558884947105 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:28:30 -0400 Subject: [PATCH 08/11] remove extraneous line --- changelog/16111.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog/16111.txt b/changelog/16111.txt index a81e4e6a19d0..59d533076842 100644 --- a/changelog/16111.txt +++ b/changelog/16111.txt @@ -1,4 +1,3 @@ ```release-note:improvement core: Add `sys/loggers` and `sys/loggers/:name` endpoints to provide ability to modify logging verbosity ``` - From 74310bae8518f8e30e6ca69544d2f25f061d875a Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:47:52 -0400 Subject: [PATCH 09/11] add log level field to Core struct --- command/server.go | 1 + vault/core.go | 6 ++++++ vault/logical_system.go | 6 ++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/command/server.go b/command/server.go index 9eb0a5a75b3f..52b84e6160b9 100644 --- a/command/server.go +++ b/command/server.go @@ -593,6 +593,7 @@ func (c *ServerCommand) runRecoveryMode() int { Physical: backend, StorageType: config.Storage.Type, Seal: barrierSeal, + LogLevel: logLevelString, Logger: c.logger, DisableMlock: config.DisableMlock, RecoveryMode: c.flagRecovery, diff --git a/vault/core.go b/vault/core.go index 01d27853fcf1..b7f39349ab22 100644 --- a/vault/core.go +++ b/vault/core.go @@ -408,6 +408,9 @@ type Core struct { baseLogger log.Logger logger log.Logger + // log level provided by config, CLI flag, or env + logLevel string + // Disables the trace display for Sentinel checks sentinelTraceDisabled bool @@ -665,6 +668,8 @@ type CoreConfig struct { SecureRandomReader io.Reader + LogLevel string + Logger log.Logger // Disables the trace display for Sentinel checks @@ -848,6 +853,7 @@ func CreateCore(conf *CoreConfig) (*Core, error) { standbyStopCh: new(atomic.Value), baseLogger: conf.Logger, logger: conf.Logger.Named("core"), + logLevel: conf.LogLevel, defaultLeaseTTL: conf.DefaultLeaseTTL, maxLeaseTTL: conf.MaxLeaseTTL, diff --git a/vault/logical_system.go b/vault/logical_system.go index 7f1a64b4539f..27e11a342742 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -4480,8 +4480,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) { - config := b.Core.GetCoreConfigInternal() - level, err := getLogLevel(config.LogLevel) + level, err := getLogLevel(b.Core.logLevel) if err != nil { return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil } @@ -4527,8 +4526,7 @@ func (b *SystemBackend) handleLoggersByNameDelete(ctx context.Context, req *logi return logical.ErrorResponse("name is required"), nil } - config := b.Core.GetCoreConfigInternal() - level, err := getLogLevel(config.LogLevel) + level, err := getLogLevel(b.Core.logLevel) if err != nil { return logical.ErrorResponse(fmt.Sprintf("log level from config is invalid: %s", err.Error())), nil } From 7ff42d7656f4460dc3727732ff4f6841eab782f0 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Thu, 23 Jun 2022 10:32:58 -0400 Subject: [PATCH 10/11] add godoc for getLogLevel --- vault/logical_system.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vault/logical_system.go b/vault/logical_system.go index 27e11a342742..5f85ec514be4 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -4436,6 +4436,9 @@ 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 From c44d8d706747e6a3cabad3f469216298bbe695e0 Mon Sep 17 00:00:00 2001 From: Chris Capurso <1036769+ccapurso@users.noreply.github.com> Date: Fri, 24 Jun 2022 09:48:51 -0400 Subject: [PATCH 11/11] add some loggers to c.allLoggers --- vault/cluster.go | 9 +++++++-- vault/core.go | 4 ++++ vault/expiration.go | 8 ++++++-- vault/raft.go | 1 + 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/vault/cluster.go b/vault/cluster.go index a1d037907cd2..bc1d352e5337 100644 --- a/vault/cluster.go +++ b/vault/cluster.go @@ -318,14 +318,19 @@ func (c *Core) startClusterListener(ctx context.Context) error { networkLayer := c.clusterNetworkLayer if networkLayer == nil { - networkLayer = cluster.NewTCPLayer(c.clusterListenerAddrs, c.logger.Named("cluster-listener.tcp")) + tcpLogger := c.logger.Named("cluster-listener.tcp") + networkLayer = cluster.NewTCPLayer(c.clusterListenerAddrs, tcpLogger) + c.AddLogger(tcpLogger) } + listenerLogger := c.logger.Named("cluster-listener") c.clusterListener.Store(cluster.NewListener(networkLayer, c.clusterCipherSuites, - c.logger.Named("cluster-listener"), + listenerLogger, 5*c.clusterHeartbeatInterval)) + c.AddLogger(listenerLogger) + err := c.getClusterListener().Run(ctx) if err != nil { return err diff --git a/vault/core.go b/vault/core.go index b7f39349ab22..15c6b5676234 100644 --- a/vault/core.go +++ b/vault/core.go @@ -1040,6 +1040,10 @@ func NewCore(conf *CoreConfig) (*Core, error) { c.loginMFABackend = NewLoginMFABackend(c, conf.Logger) + if c.loginMFABackend.mfaLogger != nil { + c.AddLogger(c.loginMFABackend.mfaLogger) + } + logicalBackends := make(map[string]logical.Factory) for k, f := range conf.LogicalBackends { logicalBackends[k] = f diff --git a/vault/expiration.go b/vault/expiration.go index 827864c380b7..4bda9bded750 100644 --- a/vault/expiration.go +++ b/vault/expiration.go @@ -313,9 +313,12 @@ func getNumExpirationWorkers(c *Core, l log.Logger) int { // NewExpirationManager creates a new ExpirationManager that is backed // using a given view, and uses the provided router for revocation. func NewExpirationManager(c *Core, view *BarrierView, e ExpireLeaseStrategy, logger log.Logger) *ExpirationManager { - jobManager := fairshare.NewJobManager("expire", getNumExpirationWorkers(c, logger), logger.Named("job-manager"), c.metricSink) + managerLogger := logger.Named("job-manager") + jobManager := fairshare.NewJobManager("expire", getNumExpirationWorkers(c, logger), managerLogger, c.metricSink) jobManager.Start() + c.AddLogger(managerLogger) + exp := &ExpirationManager{ core: c, router: c.router, @@ -1268,7 +1271,8 @@ func (m *ExpirationManager) Renew(ctx context.Context, leaseID string, increment // RenewToken is used to renew a token which does not need to // invoke a logical backend. func (m *ExpirationManager) RenewToken(ctx context.Context, req *logical.Request, te *logical.TokenEntry, - increment time.Duration) (*logical.Response, error) { + increment time.Duration, +) (*logical.Response, error) { defer metrics.MeasureSince([]string{"expire", "renew-token"}, time.Now()) tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core) diff --git a/vault/raft.go b/vault/raft.go index bf9f1c0af64a..89397e18db61 100644 --- a/vault/raft.go +++ b/vault/raft.go @@ -220,6 +220,7 @@ func (c *Core) startPeriodicRaftTLSRotate(ctx context.Context) error { c.raftTLSRotationStopCh = make(chan struct{}) logger := c.logger.Named("raft") + c.AddLogger(logger) if c.isRaftHAOnly() { return c.raftTLSRotateDirect(ctx, logger, c.raftTLSRotationStopCh)