Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport: Add read support to sys/loggers endpoints #18161

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/17979.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
core: Add read support to `sys/loggers` and `sys/loggers/:name` endpoints
```
56 changes: 56 additions & 0 deletions helper/logging/logfile.go
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
}
158 changes: 158 additions & 0 deletions helper/logging/logger.go
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
}
12 changes: 9 additions & 3 deletions vault/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -2860,6 +2860,7 @@ func (c *Core) AddLogger(logger log.Logger) {
c.allLoggers = append(c.allLoggers, logger)
}

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

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

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

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

// SetConfig sets core's config object to the newly provided config.
Expand Down
Loading