-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Backport: Add read support to sys/loggers endpoints (#18161)
* add initial logging helper package * 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 * add logfile * remove doc changes
- Loading branch information
Showing
7 changed files
with
499 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
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) | ||
} | ||
} | ||
|
||
// 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)) | ||
|
||
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 | ||
} | ||
|
||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.