From 5f9041ff5f3f1c928c03c066414d927b0192e484 Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Wed, 2 Sep 2026 14:19:27 -0700 Subject: [PATCH] feat: support admin/access logs on standard streams The access-log and admin-access-log options now accept stdout, stderr, or - for stdout in addition to file paths. Standard stream destinations are kept open during shutdown and SIGHUP handling, while file destinations continue to support normal reopen behavior for log rotation. CLI help, embedded config comments, and the example configuration describe the new destination values. Fixes #2245 --- cmd/versitygw/main.go | 4 ++-- cmd/vgwrdma/main.go | 4 ++-- embedgw/embedgw.go | 6 ++++-- extra/example.conf | 16 ++++++++-------- s3log/file.go | 34 ++++++++++++++++++++++++++++------ s3log/file_admin.go | 4 ++-- 6 files changed, 46 insertions(+), 22 deletions(-) diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 458da43c..0798b91e 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -442,13 +442,13 @@ func initFlags() []cli.Flag { }, &cli.StringFlag{ Name: "access-log", - Usage: "enable server access logging to specified file", + Usage: "enable server access logging to specified file, stdout, stderr, or - for stdout", EnvVars: []string{"LOGFILE", "VGW_ACCESS_LOG"}, Destination: &accessLog, }, &cli.StringFlag{ Name: "admin-access-log", - Usage: "enable admin server access logging to specified file", + Usage: "enable admin server access logging to specified file, stdout, stderr, or - for stdout", EnvVars: []string{"LOGFILE", "VGW_ADMIN_ACCESS_LOG"}, Destination: &adminLogFile, }, diff --git a/cmd/vgwrdma/main.go b/cmd/vgwrdma/main.go index 2185934f..88795087 100644 --- a/cmd/vgwrdma/main.go +++ b/cmd/vgwrdma/main.go @@ -483,13 +483,13 @@ func initFlags() []cli.Flag { }, &cli.StringFlag{ Name: "access-log", - Usage: "enable server access logging to specified file", + Usage: "enable server access logging to specified file, stdout, stderr, or - for stdout", EnvVars: []string{"LOGFILE", "VGW_ACCESS_LOG"}, Destination: &accessLog, }, &cli.StringFlag{ Name: "admin-access-log", - Usage: "enable admin server access logging to specified file", + Usage: "enable admin server access logging to specified file, stdout, stderr, or - for stdout", EnvVars: []string{"LOGFILE", "VGW_ADMIN_ACCESS_LOG"}, Destination: &adminLogFile, }, diff --git a/embedgw/embedgw.go b/embedgw/embedgw.go index 6065581b..cd227560 100644 --- a/embedgw/embedgw.go +++ b/embedgw/embedgw.go @@ -347,14 +347,16 @@ type Config struct { // are independent and can be enabled simultaneously in any combination. // All are optional; omit or leave empty to disable that output. - // AccessLog is the file path for S3 request access logs in the AWS S3 + // AccessLog is the file path or stream name (stdout, stderr, or - for + // stdout) for S3 request access logs in the AWS S3 // access log format. Use absolute paths; relative paths may break if the // server changes its working directory. Empty disables file logging. AccessLog string // LogWebhookURL is an HTTP(S) URL that receives S3 access log entries as // JSON-encoded POST requests. Can be set alongside AccessLog. LogWebhookURL string - // AdminLogFile is the file path for admin API request logs. + // AdminLogFile is the file path or stream name (stdout, stderr, or - for + // stdout) for admin API request logs. AdminLogFile string // Metrics diff --git a/extra/example.conf b/extra/example.conf index ace350c7..91d10a27 100644 --- a/extra/example.conf +++ b/extra/example.conf @@ -196,18 +196,18 @@ ROOT_SECRET_ACCESS_KEY= # Access Logs # ############### -# The VGW_ACCESS_LOG option when set will specify the file to log all S3 -# server requests to. This option is optional, and defaults to not logging. -# It is suggested to use absolute paths for the server log file because the -# server may chdir into the backend root directory and change locations for -# relative paths. +# The VGW_ACCESS_LOG option when set will specify where to log all S3 +# server requests. Set it to a file path, stdout, stderr, or - for stdout. +# This option is optional, and defaults to not logging. When using a file, +# it is suggested to use an absolute path because the server may chdir into +# the backend root directory and change locations for relative paths. # The log file format follows the AWS S3 access log format documented in # https://docs.aws.amazon.com/AmazonS3/latest/userguide/LogFormat.html. #VGW_ACCESS_LOG= -# The VGW_ADMIN_ACCESS_LOG option when set will specify the file to log all -# admin server requests to. When not set, admin requests are logged to the -# same file as the S3 server (VGW_ACCESS_LOG) if that is configured. +# The VGW_ADMIN_ACCESS_LOG option when set will specify where to log all +# admin server requests. Set it to a file path, stdout, stderr, or - for +# stdout. This option is optional, and defaults to not logging admin requests. #VGW_ADMIN_ACCESS_LOG= # The VGW_LOG_WEBHOOK_URL option when set will specify the URL to send the diff --git a/s3log/file.go b/s3log/file.go index 55b76ac5..1a2bda92 100644 --- a/s3log/file.go +++ b/s3log/file.go @@ -35,24 +35,37 @@ const ( // FileLogger is a local file audit log type FileLogger struct { - logfile string - f *os.File - gotErr bool - mu sync.Mutex + logfile string + f *os.File + standardStream bool + gotErr bool + mu sync.Mutex } var _ AuditLogger = &FileLogger{} // InitFileLogger initializes audit logs to local file func InitFileLogger(logname string) (AuditLogger, error) { - f, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, logFileMode) + f, standardStream, err := openLogDestination(logname) if err != nil { return nil, fmt.Errorf("open log: %w", err) } fmt.Fprintf(f, "log starts %v\n", time.Now()) - return &FileLogger{logfile: logname, f: f}, nil + return &FileLogger{logfile: logname, f: f, standardStream: standardStream}, nil +} + +func openLogDestination(logname string) (*os.File, bool, error) { + switch strings.ToLower(logname) { + case "stdout", "-": + return os.Stdout, true, nil + case "stderr": + return os.Stderr, true, nil + default: + f, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, logFileMode) + return f, false, err + } } // Log sends log message to file logger @@ -222,6 +235,11 @@ func (f *FileLogger) writeLog(lf LogFields) { // HangUp closes current logfile handle and opens a new one // typically needed for log rotations func (f *FileLogger) HangUp() error { + if f.standardStream { + f.f.WriteString(fmt.Sprintf("log starts %v\n", time.Now())) + return nil + } + err := f.f.Close() if err != nil { return fmt.Errorf("close log: %w", err) @@ -239,5 +257,9 @@ func (f *FileLogger) HangUp() error { // Shutdown closes logfile handle func (f *FileLogger) Shutdown() error { + if f.standardStream { + return nil + } + return f.f.Close() } diff --git a/s3log/file_admin.go b/s3log/file_admin.go index b13ea085..a3789b8c 100644 --- a/s3log/file_admin.go +++ b/s3log/file_admin.go @@ -34,14 +34,14 @@ var _ AuditLogger = &AdminFileLogger{} // InitFileLogger initializes audit logs to local file func InitAdminFileLogger(logname string) (AuditLogger, error) { - f, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + f, standardStream, err := openLogDestination(logname) if err != nil { return nil, fmt.Errorf("open log: %w", err) } f.WriteString(fmt.Sprintf("log starts %v\n", time.Now())) - return &AdminFileLogger{FileLogger: FileLogger{logfile: logname, f: f}}, nil + return &AdminFileLogger{FileLogger: FileLogger{logfile: logname, f: f, standardStream: standardStream}}, nil } // Log sends log message to file logger