VAULT-8732: Add `log-file` to Vault Agent (#17841)

* Started work on adding log-file support to Agent
* Allow log file to be picked up and appended
* Use NewLogFile everywhere
* Tried to pull out the config aggregation from Agent.Run

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>
This commit is contained in:
Peter Wilson 2022-11-11 10:59:16 +00:00 committed by GitHub
parent 6cc6875d6d
commit 0fad0c3864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 634 additions and 89 deletions

1
.gitignore vendored
View File

@ -119,3 +119,4 @@ website/components/node_modules
.buildcache/
.releaser/
*.log

3
changelog/17841.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
logging: Vault Agent supports logging to a specified file path via environment variable, CLI or config
```

View File

@ -41,11 +41,11 @@ import (
"github.com/hashicorp/vault/command/agent/sink/inmem"
"github.com/hashicorp/vault/command/agent/template"
"github.com/hashicorp/vault/command/agent/winsvc"
"github.com/hashicorp/vault/helper/logging"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/internalshared/listenerutil"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/helper/useragent"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/version"
@ -61,6 +61,12 @@ var (
_ cli.CommandAutocomplete = (*AgentCommand)(nil)
)
const (
// flagNameAgentExitAfterAuth is used as an Agent specific flag to indicate
// that agent should exit after a single successful auth
flagNameAgentExitAfterAuth = "exit-after-auth"
)
type AgentCommand struct {
*BaseCommand
@ -80,6 +86,7 @@ type AgentCommand struct {
flagConfigs []string
flagLogLevel string
flagLogFile string
flagExitAfterAuth bool
flagTestVerifyOnly bool
@ -124,17 +131,24 @@ func (c *AgentCommand) Flags() *FlagSets {
})
f.StringVar(&StringVar{
Name: "log-level",
Name: flagNameLogLevel,
Target: &c.flagLogLevel,
Default: "info",
EnvVar: "VAULT_LOG_LEVEL",
EnvVar: EnvVaultLogLevel,
Completion: complete.PredictSet("trace", "debug", "info", "warn", "error"),
Usage: "Log verbosity level. Supported values (in order of detail) are " +
"\"trace\", \"debug\", \"info\", \"warn\", and \"error\".",
})
f.StringVar(&StringVar{
Name: flagNameLogFile,
Target: &c.flagLogFile,
EnvVar: EnvVaultLogFile,
Usage: "Path to the log file that Vault should use for logging",
})
f.BoolVar(&BoolVar{
Name: "exit-after-auth",
Name: flagNameAgentExitAfterAuth,
Target: &c.flagExitAfterAuth,
Default: false,
Usage: "If set to true, the agent will exit with code 0 after a single " +
@ -193,27 +207,6 @@ func (c *AgentCommand) Run(args []string) int {
if c.flagCombineLogs {
c.logWriter = os.Stdout
}
var level log.Level
c.flagLogLevel = strings.ToLower(strings.TrimSpace(c.flagLogLevel))
switch c.flagLogLevel {
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:
c.UI.Error(fmt.Sprintf("Unknown log level: %s", c.flagLogLevel))
return 1
}
if c.logger == nil {
c.logger = logging.NewVaultLoggerWithWriter(c.logWriter, level)
}
// Validation
if len(c.flagConfigs) != 1 {
@ -221,7 +214,7 @@ func (c *AgentCommand) Run(args []string) int {
return 1
}
// Load the configuration
// Load the configuration file
config, err := agentConfig.LoadConfig(c.flagConfigs[0])
if err != nil {
c.UI.Error(fmt.Sprintf("Error loading configuration from %s: %s", c.flagConfigs[0], err))
@ -235,6 +228,7 @@ func (c *AgentCommand) Run(args []string) int {
"-config flag."))
return 1
}
if config.AutoAuth == nil && config.Cache == nil {
c.UI.Error("No auto_auth or cache block found in config file")
return 1
@ -243,62 +237,29 @@ func (c *AgentCommand) Run(args []string) int {
c.UI.Info("No auto_auth block found in config file, not starting automatic authentication feature")
}
exitAfterAuth := config.ExitAfterAuth
f.Visit(func(fl *flag.Flag) {
if fl.Name == "exit-after-auth" {
exitAfterAuth = c.flagExitAfterAuth
}
})
config = c.aggregateConfig(f, config)
c.setStringFlag(f, config.Vault.Address, &StringVar{
Name: flagNameAddress,
Target: &c.flagAddress,
Default: "https://127.0.0.1:8200",
EnvVar: api.EnvVaultAddress,
})
config.Vault.Address = c.flagAddress
c.setStringFlag(f, config.Vault.CACert, &StringVar{
Name: flagNameCACert,
Target: &c.flagCACert,
Default: "",
EnvVar: api.EnvVaultCACert,
})
config.Vault.CACert = c.flagCACert
c.setStringFlag(f, config.Vault.CAPath, &StringVar{
Name: flagNameCAPath,
Target: &c.flagCAPath,
Default: "",
EnvVar: api.EnvVaultCAPath,
})
config.Vault.CAPath = c.flagCAPath
c.setStringFlag(f, config.Vault.ClientCert, &StringVar{
Name: flagNameClientCert,
Target: &c.flagClientCert,
Default: "",
EnvVar: api.EnvVaultClientCert,
})
config.Vault.ClientCert = c.flagClientCert
c.setStringFlag(f, config.Vault.ClientKey, &StringVar{
Name: flagNameClientKey,
Target: &c.flagClientKey,
Default: "",
EnvVar: api.EnvVaultClientKey,
})
config.Vault.ClientKey = c.flagClientKey
c.setBoolFlag(f, config.Vault.TLSSkipVerify, &BoolVar{
Name: flagNameTLSSkipVerify,
Target: &c.flagTLSSkipVerify,
Default: false,
EnvVar: api.EnvVaultSkipVerify,
})
config.Vault.TLSSkipVerify = c.flagTLSSkipVerify
c.setStringFlag(f, config.Vault.TLSServerName, &StringVar{
Name: flagTLSServerName,
Target: &c.flagTLSServerName,
Default: "",
EnvVar: api.EnvVaultTLSServerName,
})
config.Vault.TLSServerName = c.flagTLSServerName
// Build the logger using level, format and path
logLevel, err := logging.ParseLogLevel(config.LogLevel)
if err != nil {
c.UI.Error(err.Error())
return 1
}
logFormat, err := logging.ParseLogFormat(config.LogFormat)
if err != nil {
c.UI.Error(err.Error())
return 1
}
logCfg := logging.NewLogConfig("agent", logLevel, logFormat, config.LogFile)
l, err := logging.Setup(logCfg, c.logWriter)
if err != nil {
c.UI.Error(err.Error())
return 1
}
c.logger = l
infoKeys := make([]string, 0, 10)
info := make(map[string]string)
@ -855,16 +816,16 @@ func (c *AgentCommand) Run(args []string) int {
ss := sink.NewSinkServer(&sink.SinkServerConfig{
Logger: c.logger.Named("sink.server"),
Client: ahClient,
ExitAfterAuth: exitAfterAuth,
ExitAfterAuth: config.ExitAfterAuth,
})
ts := template.NewServer(&template.ServerConfig{
Logger: c.logger.Named("template.server"),
LogLevel: level,
LogLevel: logLevel,
LogWriter: c.logWriter,
AgentConfig: config,
Namespace: templateNamespace,
ExitAfterAuth: exitAfterAuth,
ExitAfterAuth: config.ExitAfterAuth,
})
g.Add(func() error {
@ -963,6 +924,84 @@ func (c *AgentCommand) Run(args []string) int {
return 0
}
// aggregateConfig ensures that the config object accurately reflects the desired
// settings as configured by the user. It applies the relevant config setting based
// on the precedence (env var overrides file config, cli overrides env var).
// It mutates the config object supplied and returns the updated object.
func (c *AgentCommand) aggregateConfig(f *FlagSets, config *agentConfig.Config) *agentConfig.Config {
f.Visit(func(fl *flag.Flag) {
if fl.Name == flagNameAgentExitAfterAuth {
config.ExitAfterAuth = c.flagExitAfterAuth
}
})
c.setStringFlag(f, config.LogFile, &StringVar{
Name: flagNameLogFile,
EnvVar: EnvVaultLogFile,
Target: &c.flagLogFile,
})
config.LogFile = c.flagLogFile
c.setStringFlag(f, config.LogLevel, &StringVar{
Name: flagNameLogLevel,
EnvVar: EnvVaultLogLevel,
Target: &c.flagLogLevel,
})
config.LogLevel = c.flagLogLevel
c.setStringFlag(f, config.Vault.Address, &StringVar{
Name: flagNameAddress,
Target: &c.flagAddress,
Default: "https://127.0.0.1:8200",
EnvVar: api.EnvVaultAddress,
})
config.Vault.Address = c.flagAddress
c.setStringFlag(f, config.Vault.CACert, &StringVar{
Name: flagNameCACert,
Target: &c.flagCACert,
Default: "",
EnvVar: api.EnvVaultCACert,
})
config.Vault.CACert = c.flagCACert
c.setStringFlag(f, config.Vault.CAPath, &StringVar{
Name: flagNameCAPath,
Target: &c.flagCAPath,
Default: "",
EnvVar: api.EnvVaultCAPath,
})
config.Vault.CAPath = c.flagCAPath
c.setStringFlag(f, config.Vault.ClientCert, &StringVar{
Name: flagNameClientCert,
Target: &c.flagClientCert,
Default: "",
EnvVar: api.EnvVaultClientCert,
})
config.Vault.ClientCert = c.flagClientCert
c.setStringFlag(f, config.Vault.ClientKey, &StringVar{
Name: flagNameClientKey,
Target: &c.flagClientKey,
Default: "",
EnvVar: api.EnvVaultClientKey,
})
config.Vault.ClientKey = c.flagClientKey
c.setBoolFlag(f, config.Vault.TLSSkipVerify, &BoolVar{
Name: flagNameTLSSkipVerify,
Target: &c.flagTLSSkipVerify,
Default: false,
EnvVar: api.EnvVaultSkipVerify,
})
config.Vault.TLSSkipVerify = c.flagTLSSkipVerify
c.setStringFlag(f, config.Vault.TLSServerName, &StringVar{
Name: flagTLSServerName,
Target: &c.flagTLSServerName,
Default: "",
EnvVar: api.EnvVaultTLSServerName,
})
config.Vault.TLSServerName = c.flagTLSServerName
return config
}
// verifyRequestHeader wraps an http.Handler inside a Handler that checks for
// the request header that is used for SSRF protection.
func verifyRequestHeader(handler http.Handler) http.Handler {

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"strings"
@ -38,6 +37,7 @@ type Config struct {
DisableKeepAlivesCaching bool `hcl:"-"`
DisableKeepAlivesTemplating bool `hcl:"-"`
DisableKeepAlivesAutoAuth bool `hcl:"-"`
LogFile string `hcl:"log_file"`
}
const (
@ -173,7 +173,7 @@ func LoadConfig(path string) (*Config, error) {
}
// Read the file
d, err := ioutil.ReadFile(path)
d, err := os.ReadFile(path)
if err != nil {
return nil, err
}

View File

@ -230,6 +230,7 @@ func TestLoadConfigFile(t *testing.T) {
NumRetries: 12,
},
},
LogFile: "/var/log/vault/vault-agent.log",
}
config.Prune()

View File

@ -1,4 +1,5 @@
pid_file = "./pidfile"
log_file = "/var/log/vault/vault-agent.log"
auto_auth {
method "aws" {

View File

@ -1,4 +1,5 @@
pid_file = "./pidfile"
log_file = "/var/log/vault/vault-agent.log"
auto_auth {
method {

View File

@ -20,6 +20,7 @@ import (
"github.com/hashicorp/vault/api"
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
"github.com/hashicorp/vault/command/agent"
agentConfig "github.com/hashicorp/vault/command/agent/config"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/logging"
@ -27,9 +28,26 @@ import (
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
BasicHclConfig = `
log_file = "/foo/bar/juan.log"
vault {
address = "http://127.0.0.1:8200"
retry {
num_retries = 5
}
}
listener "tcp" {
address = "127.0.0.1:8100"
tls_disable = true
}`
)
func testAgentCommand(tb testing.TB, logger hclog.Logger) (*cli.MockUi, *AgentCommand) {
tb.Helper()
@ -1245,6 +1263,27 @@ func makeTempFile(t *testing.T, name, contents string) string {
return path
}
func populateTempFile(t *testing.T, name, contents string) *os.File {
t.Helper()
file, err := os.CreateTemp(t.TempDir(), name)
if err != nil {
t.Fatal(err)
}
_, err = file.WriteString(contents)
if err != nil {
t.Fatal(err)
}
err = file.Close()
if err != nil {
t.Fatal(err)
}
return file
}
// handler makes 500 errors happen for reads on /v1/secret.
// Definitely not thread-safe, do not use t.Parallel with this.
type handler struct {
@ -2211,6 +2250,105 @@ cache {}
wg.Wait()
}
func TestAgent_LogFile_EnvVarOverridesConfig(t *testing.T) {
// Create basic config
configFile := populateTempFile(t, "agent-config.hcl", BasicHclConfig)
cfg, err := agentConfig.LoadConfig(configFile.Name())
if err != nil {
t.Fatal("Cannot load config to test update/merge", err)
}
// Sanity check that the config value is the current value
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile)
// Make sure the env var is configured
oldEnvVarLogFile := os.Getenv(EnvVaultLogFile)
os.Setenv(EnvVaultLogFile, "/squiggle/logs.txt")
if oldEnvVarLogFile == "" {
defer os.Unsetenv(EnvVaultLogFile)
} else {
defer os.Setenv(EnvVaultLogFile, oldEnvVarLogFile)
}
// Initialize the command and parse any flags
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
f := cmd.Flags()
err = f.Parse([]string{})
if err != nil {
t.Fatal(err)
}
// Update the config based on the inputs.
cfg = cmd.aggregateConfig(f, cfg)
assert.NotEqual(t, "/foo/bar/juan.log", cfg.LogFile)
assert.Equal(t, "/squiggle/logs.txt", cfg.LogFile)
}
func TestAgent_LogFile_CliOverridesEnvVar(t *testing.T) {
// Create basic config
configFile := populateTempFile(t, "agent-config.hcl", BasicHclConfig)
cfg, err := agentConfig.LoadConfig(configFile.Name())
if err != nil {
t.Fatal("Cannot load config to test update/merge", err)
}
// Sanity check that the config value is the current value
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile)
// Make sure the env var is configured
oldEnvVarLogFile := os.Getenv(EnvVaultLogFile)
os.Setenv(EnvVaultLogFile, "/squiggle/logs.txt")
if oldEnvVarLogFile == "" {
defer os.Unsetenv(EnvVaultLogFile)
} else {
defer os.Setenv(EnvVaultLogFile, oldEnvVarLogFile)
}
// Initialize the command and parse any flags
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
f := cmd.Flags()
// Simulate the flag being specified
err = f.Parse([]string{"-log-file=/foo/bar/test.log"})
if err != nil {
t.Fatal(err)
}
// Update the config based on the inputs.
cfg = cmd.aggregateConfig(f, cfg)
assert.NotEqual(t, "/foo/bar/juan.log", cfg.LogFile)
assert.NotEqual(t, "/squiggle/logs.txt", cfg.LogFile)
assert.Equal(t, "/foo/bar/test.log", cfg.LogFile)
}
func TestAgent_LogFile_Config(t *testing.T) {
// Sanity check, remove any env var
os.Unsetenv(EnvVaultLogFile)
configFile := populateTempFile(t, "agent-config.hcl", BasicHclConfig)
cfg, err := agentConfig.LoadConfig(configFile.Name())
if err != nil {
t.Fatal("Cannot load config to test update/merge", err)
}
// Sanity check that the config value is the current value
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile, "sanity check on log config failed")
// Parse the cli flags (but we pass in an empty slice)
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
f := cmd.Flags()
err = f.Parse([]string{})
if err != nil {
t.Fatal(err)
}
cfg = cmd.aggregateConfig(f, cfg)
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile, "actual config check")
}
// Get a randomly assigned port and then free it again before returning it.
// There is still a race when trying to use it, but should work better
// than a static port.

View File

@ -82,6 +82,11 @@ const (
EnvVaultLicensePath = "VAULT_LICENSE_PATH"
// EnvVaultDetailed is to output detailed information (e.g., ListResponseWithInfo).
EnvVaultDetailed = `VAULT_DETAILED`
// EnvVaultLogFile is used to specify the path to the log file that Vault should use for logging
EnvVaultLogFile = "VAULT_LOG_FILE"
// EnvVaultLogLevel is used to specify the log level applied to logging
// Supported log levels: Trace, Debug, Error, Warn, Info
EnvVaultLogLevel = "VAULT_LOG_LEVEL"
// DisableSSCTokens is an env var used to disable index bearing
// token functionality
@ -136,6 +141,11 @@ const (
flagNameUserLockoutDisable = "user-lockout-disable"
// flagNameDisableRedirects is used to prevent the client from honoring a single redirect as a response to a request
flagNameDisableRedirects = "disable-redirects"
// flagNameLogFile is used to specify the path to the log file that Vault should use for logging
flagNameLogFile = "log-file"
// flagNameLogLevel is used to specify the log level applied to logging
// Supported log levels: Trace, Debug, Error, Warn, Info
flagNameLogLevel = "log-level"
)
var (

56
helper/logging/logfile.go Normal file
View File

@ -0,0 +1,56 @@
package logging
import (
"os"
"path/filepath"
"strings"
"sync"
)
type LogFile struct {
// Name of the log file
fileName string
// Path to the log file
logPath string
// fileInfo is the pointer to the current file being written to
fileInfo *os.File
// acquire is the mutex utilized to ensure we have no concurrency issues
acquire sync.Mutex
}
func NewLogFile(logPath string, fileName string) *LogFile {
return &LogFile{
fileName: strings.TrimSpace(fileName),
logPath: strings.TrimSpace(logPath),
}
}
// Write is used to implement io.Writer
func (l *LogFile) Write(b []byte) (n int, err error) {
l.acquire.Lock()
defer l.acquire.Unlock()
// Create a new file if we have no file to write to
if l.fileInfo == nil {
if err := l.openNew(); err != nil {
return 0, err
}
}
return l.fileInfo.Write(b)
}
func (l *LogFile) openNew() error {
newFilePath := filepath.Join(l.logPath, l.fileName)
// Try to open an existing file or create a new one if it doesn't exist.
filePointer, err := os.OpenFile(newFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o640)
if err != nil {
return err
}
l.fileInfo = filePointer
return nil
}

View File

@ -0,0 +1,22 @@
package logging
import (
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestLogFile_openNew(t *testing.T) {
logFile := NewLogFile(t.TempDir(), "vault-agent.log")
err := logFile.openNew()
require.NoError(t, err)
msg := "[INFO] Something"
_, err = logFile.Write([]byte(msg))
require.NoError(t, err)
content, err := os.ReadFile(logFile.fileInfo.Name())
require.NoError(t, err)
require.Contains(t, string(content), msg)
}

135
helper/logging/logger.go Normal file
View File

@ -0,0 +1,135 @@
package logging
import (
"errors"
"fmt"
"io"
"path/filepath"
"strings"
log "github.com/hashicorp/go-hclog"
)
const (
UnspecifiedFormat LogFormat = iota
StandardFormat
JSONFormat
)
type LogFormat int
// LogConfig should be used to supply configuration when creating a new Vault logger
type LogConfig struct {
name string
logLevel log.Level
logFormat LogFormat
logFilePath string
}
func NewLogConfig(name string, logLevel log.Level, logFormat LogFormat, logFilePath string) LogConfig {
return LogConfig{
name: name,
logLevel: logLevel,
logFormat: logFormat,
logFilePath: strings.TrimSpace(logFilePath),
}
}
func (c LogConfig) IsFormatJson() bool {
return c.logFormat == JSONFormat
}
// Stringer implementation
func (lf LogFormat) String() string {
switch lf {
case UnspecifiedFormat:
return "unspecified"
case StandardFormat:
return "standard"
case JSONFormat:
return "json"
}
// unreachable
return "unknown"
}
// noErrorWriter is a wrapper to suppress errors when writing to w.
type noErrorWriter struct {
w io.Writer
}
func (w noErrorWriter) Write(p []byte) (n int, err error) {
_, _ = w.w.Write(p)
// We purposely return n == len(p) as if write was successful
return len(p), nil
}
// Setup creates a new logger with the specified configuration and writer
func Setup(config LogConfig, w io.Writer) (log.InterceptLogger, error) {
// Validate the log level
if config.logLevel.String() == "unknown" {
return nil, fmt.Errorf("invalid log level: %v", config.logLevel)
}
// If out is os.Stdout and Vault is being run as a Windows Service, writes will
// fail silently, which may inadvertently prevent writes to other writers.
// noErrorWriter is used as a wrapper to suppress any errors when writing to out.
writers := []io.Writer{noErrorWriter{w: w}}
if config.logFilePath != "" {
dir, fileName := filepath.Split(config.logFilePath)
if fileName == "" {
fileName = "vault-agent.log"
}
logFile := NewLogFile(dir, fileName)
if err := logFile.openNew(); err != nil {
return nil, fmt.Errorf("failed to set up file logging: %w", err)
}
writers = append(writers, logFile)
}
logger := log.NewInterceptLogger(&log.LoggerOptions{
Name: config.name,
Level: config.logLevel,
Output: io.MultiWriter(writers...),
JSONFormat: config.IsFormatJson(),
})
return logger, nil
}
// ParseLogFormat parses the log format from the provided string.
func ParseLogFormat(format string) (LogFormat, error) {
switch strings.ToLower(strings.TrimSpace(format)) {
case "":
return UnspecifiedFormat, nil
case "standard":
return StandardFormat, nil
case "json":
return JSONFormat, nil
default:
return UnspecifiedFormat, fmt.Errorf("unknown log format: %s", format)
}
}
func ParseLogLevel(logLevel string) (log.Level, error) {
var result log.Level
logLevel = strings.ToLower(strings.TrimSpace(logLevel))
switch logLevel {
case "trace":
result = log.Trace
case "debug":
result = log.Debug
case "notice", "info", "":
result = log.Info
case "warn", "warning":
result = log.Warn
case "err", "error":
result = log.Error
default:
return -1, errors.New(fmt.Sprintf("unknown log level: %s", logLevel))
}
return result, nil
}

View File

@ -0,0 +1,128 @@
package logging
import (
"bytes"
"encoding/json"
"errors"
"os"
"testing"
log "github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLogger_SetupBasic(t *testing.T) {
cfg := NewLogConfig("test-system", log.Info, StandardFormat, t.TempDir()+"test.log")
logger, err := Setup(cfg, nil)
require.NoError(t, err)
require.NotNil(t, logger)
}
func TestLogger_SetupInvalidLogLevel(t *testing.T) {
cfg := NewLogConfig("test-system", 999, StandardFormat, t.TempDir()+"test.log")
_, err := Setup(cfg, nil)
assert.Containsf(t, err.Error(), "invalid log level", "expected error %s", err)
}
func TestLogger_SetupLoggerErrorLevel(t *testing.T) {
cfg := NewLogConfig("test-system", log.Error, StandardFormat, t.TempDir()+"test.log")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.NoError(t, err)
require.NotNil(t, logger)
logger.Error("test error msg")
logger.Info("test info msg")
output := buf.String()
require.Contains(t, output, "[ERROR] test-system: test error msg")
require.NotContains(t, output, "[INFO] test-system: test info msg")
}
func TestLogger_SetupLoggerDebugLevel(t *testing.T) {
cfg := NewLogConfig("test-system", log.Debug, StandardFormat, t.TempDir()+"test.log")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.NoError(t, err)
require.NotNil(t, logger)
logger.Info("test info msg")
logger.Debug("test debug msg")
output := buf.String()
require.Contains(t, output, "[INFO] test-system: test info msg")
require.Contains(t, output, "[DEBUG] test-system: test debug msg")
}
func TestLogger_SetupLoggerWithName(t *testing.T) {
cfg := NewLogConfig("test-system", log.Debug, StandardFormat, t.TempDir()+"test.log")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.NoError(t, err)
require.NotNil(t, logger)
logger.Warn("test warn msg")
require.Contains(t, buf.String(), "[WARN] test-system: test warn msg")
}
func TestLogger_SetupLoggerWithJSON(t *testing.T) {
cfg := NewLogConfig("test-system", log.Debug, JSONFormat, t.TempDir()+"test.log")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.NoError(t, err)
require.NotNil(t, logger)
logger.Warn("test warn msg")
var jsonOutput map[string]string
err = json.Unmarshal(buf.Bytes(), &jsonOutput)
require.NoError(t, err)
require.Contains(t, jsonOutput, "@level")
require.Equal(t, jsonOutput["@level"], "warn")
require.Contains(t, jsonOutput, "@message")
require.Equal(t, jsonOutput["@message"], "test warn msg")
}
func TestLogger_SetupLoggerWithValidLogPath(t *testing.T) {
tmpDir := t.TempDir()
cfg := NewLogConfig("test-system", log.Info, StandardFormat, tmpDir+"/")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.NoError(t, err)
require.NotNil(t, logger)
}
func TestLogger_SetupLoggerWithInValidLogPath(t *testing.T) {
cfg := NewLogConfig("test-system", log.Info, StandardFormat, "nonexistentdir/")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.Error(t, err)
require.True(t, errors.Is(err, os.ErrNotExist))
require.Nil(t, logger)
}
func TestLogger_SetupLoggerWithInValidLogPathPermission(t *testing.T) {
tmpDir := "/tmp/" + t.Name()
os.Mkdir(tmpDir, 0o000)
defer os.RemoveAll(tmpDir)
cfg := NewLogConfig("test-system", log.Info, StandardFormat, tmpDir+"/")
var buf bytes.Buffer
logger, err := Setup(cfg, &buf)
require.Error(t, err)
require.True(t, errors.Is(err, os.ErrPermission))
require.Nil(t, logger)
}

View File

@ -127,6 +127,12 @@ See the [caching](/docs/agent/caching#api) page for details on the cache API.
## Configuration
### Command Options
- `-log-file` `(string: "")` - If specified, should contain the full file path to use for outputting log files from Vault.
### Configuration File Options
These are the currently-available general configuration option:
- `vault` <code>([vault][vault]: <optional\>)</code> - Specifies the remote Vault server the Agent connects to.
@ -373,4 +379,4 @@ template {
[listener]: /docs/agent#listener-stanza
[listener_main]: /docs/configuration/listener/tcp
[winsvc]: /docs/agent/winsvc
[telemetry]: /docs/configuration/telemetry
[telemetry]: /docs/configuration/telemetry

View File

@ -35,7 +35,7 @@ of Vault Agent as a service, using "Vault Agent" as the display name, and starti
The `binPath` argument should include the fully qualified path to the Vault executable, as well as any arguments required.
```shell-session
PS C:\Windows\system32> sc.exe create VaultAgent binPath= "C:\vault\vault.exe agent -config=C:\vault\agent-config.hcl" displayName= "Vault Agent" start= auto
PS C:\Windows\system32> sc.exe create VaultAgent binPath="C:\vault\vault.exe agent -config=C:\vault\agent-config.hcl" displayName="Vault Agent" start=auto
[SC] CreateService SUCCESS
```

View File

@ -331,6 +331,10 @@ precedence over [#VAULT_LICENSE_PATH](#vault_license_path) and
[Enterprise, Server only] Specify a path to a license on disk to use for this node.
This takes precedence over [license_path in config](/docs/configuration#license_path).
### `VAULT_LOG_FILE`
(Agent only) If provided, specifies the full path to a log file Vault should use to write its logs.
### `VAULT_MAX_RETRIES`
Maximum number of retries when certain error codes are encountered. The default
@ -434,4 +438,4 @@ list of available flags, run:
```shell-session
$ vault <subcommand> -h
```
```