From 4b975c5b9b9a1a9218d77df16f287c2662fbf1b1 Mon Sep 17 00:00:00 2001 From: Roman Khomenko Date: Sun, 9 Jun 2024 21:31:21 +0300 Subject: [PATCH 1/3] log: add log file --- command/app.go | 8 ++++- log/log.go | 81 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/command/app.go b/command/app.go index f10d890e5..b4e4b2018 100644 --- a/command/app.go +++ b/command/app.go @@ -58,6 +58,11 @@ var app = &cli.App{ }, Usage: "log level: (trace, debug, info, error)", }, + &cli.StringFlag{ + Name: "log-file", + Value: "none", + Usage: "sets file for logging", + }, &cli.BoolFlag{ Name: "install-completion", Usage: "get completion installation instructions for your shell (only available for bash, pwsh, and zsh)", @@ -96,10 +101,11 @@ var app = &cli.App{ workerCount := c.Int("numworkers") printJSON := c.Bool("json") logLevel := c.String("log") + logFile := c.String("log-file") isStat := c.Bool("stat") endpointURL := c.String("endpoint-url") - log.Init(logLevel, printJSON) + log.Init(logLevel, printJSON, log.LogFile(logFile)) parallel.Init(workerCount) if retryCount < 0 { diff --git a/log/log.go b/log/log.go index 5ec149ac9..55782d115 100644 --- a/log/log.go +++ b/log/log.go @@ -17,59 +17,83 @@ var outputCh = make(chan output, 10000) var global *Logger +type LoggerOptions struct { + logFile string +} + +type LoggerOption func(*LoggerOptions) + +func LogFile(logFile string) LoggerOption { + return func(args *LoggerOptions) { + args.logFile = logFile + } +} + // Init inits global logger. -func Init(level string, json bool) { - global = New(level, json) +func Init(level string, json bool, options ...LoggerOption) { + global = New(level, json, options...) } // Trace prints message in trace mode. func Trace(msg Message) { - global.printf(LevelTrace, msg, os.Stdout) + global.printf(LevelTrace, msg, global.logFiles.stdout) } // Debug prints message in debug mode. func Debug(msg Message) { - global.printf(LevelDebug, msg, os.Stdout) + global.printf(LevelDebug, msg, global.logFiles.stdout) } // Info prints message in info mode. func Info(msg Message) { - global.printf(LevelInfo, msg, os.Stdout) + global.printf(LevelInfo, msg, global.logFiles.stdout) } // Stat prints stat message regardless of the log level with info print formatting. // It uses printfHelper instead of printf to ignore the log level condition. func Stat(msg Message) { - global.printfHelper(LevelInfo, msg, os.Stdout) + global.printfHelper(LevelInfo, msg, global.logFiles.stdout) } // Error prints message in error mode. func Error(msg Message) { - global.printf(LevelError, msg, os.Stderr) + global.printf(LevelError, msg, global.logFiles.stderr) } // Close closes logger and its channel. func Close() { if global != nil { close(outputCh) + global.logFiles.Close() + <-global.donech } } // Logger is a structure for logging messages. type Logger struct { - donech chan struct{} - json bool - level LogLevel + donech chan struct{} + json bool + level LogLevel + logFiles *LogFiles } // New creates new logger. -func New(level string, json bool) *Logger { +func New(level string, json bool, options ...LoggerOption) *Logger { + args := &LoggerOptions{ + logFile: "none", + } + + for _, setter := range options { + setter(args) + } + logLevel := LevelFromString(level) logger := &Logger{ - donech: make(chan struct{}), - json: json, - level: logLevel, + donech: make(chan struct{}), + json: json, + level: logLevel, + logFiles: GetLogFiles(args.logFile), } go logger.out() return logger @@ -152,3 +176,32 @@ func LevelFromString(s string) LogLevel { return LevelInfo } } + +type LogFiles struct { + stdout *os.File + stderr *os.File +} + +func (files *LogFiles) Close() { + if files.stdout != os.Stdout { + files.stdout.Close() + } +} + +func GetLogFiles(logFile string) *LogFiles { + if logFile == "none" { + return &LogFiles{ + stdout: os.Stdout, + stderr: os.Stderr, + } + } + + file, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + panic(err) + } + return &LogFiles{ + stdout: file, + stderr: file, + } +} From 0b24d639ba653069708ed813bd8465341c3df70b Mon Sep 17 00:00:00 2001 From: Roman Khomenko Date: Mon, 10 Jun 2024 00:00:25 +0300 Subject: [PATCH 2/3] log: error handling and comments --- command/app.go | 13 ++++++++++++- log/log.go | 51 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/command/app.go b/command/app.go index b4e4b2018..37a01df33 100644 --- a/command/app.go +++ b/command/app.go @@ -60,7 +60,7 @@ var app = &cli.App{ }, &cli.StringFlag{ Name: "log-file", - Value: "none", + Value: log.NoLogFile, Usage: "sets file for logging", }, &cli.BoolFlag{ @@ -105,9 +105,20 @@ var app = &cli.App{ isStat := c.Bool("stat") endpointURL := c.String("endpoint-url") + // If validation fails, initialize the logger without a log file. Then we log the error + err := log.IsValidLogFile(logFile) + if err != nil { + logFile = log.NoLogFile + } + log.Init(logLevel, printJSON, log.LogFile(logFile)) parallel.Init(workerCount) + if err != nil { + printError(commandFromContext(c), c.Command.Name, err) + return err + } + if retryCount < 0 { err := fmt.Errorf("retry count cannot be a negative value") printError(commandFromContext(c), c.Command.Name, err) diff --git a/log/log.go b/log/log.go index 55782d115..db3804182 100644 --- a/log/log.go +++ b/log/log.go @@ -17,12 +17,15 @@ var outputCh = make(chan output, 10000) var global *Logger +// LoggerOptions stores parameters that are used to create a logger type LoggerOptions struct { logFile string } +// Type for setter functions that specify LoggerOptions parameters type LoggerOption func(*LoggerOptions) +// Setter for LoggerOptions.logFile parameter func LogFile(logFile string) LoggerOption { return func(args *LoggerOptions) { args.logFile = logFile @@ -70,6 +73,12 @@ func Close() { } } +// LogFiles stores pointers to stdout and stderr files +type LogFiles struct { + stdout *os.File + stderr *os.File +} + // Logger is a structure for logging messages. type Logger struct { donech chan struct{} @@ -78,22 +87,29 @@ type Logger struct { logFiles *LogFiles } +// Вefault value that disables the use of the log file +const NoLogFile string = "" + // New creates new logger. func New(level string, json bool, options ...LoggerOption) *Logger { + // Default logger options args := &LoggerOptions{ - logFile: "none", + logFile: NoLogFile, } + // Apply setters to LoggerOptions struct for _, setter := range options { setter(args) } + logFiles := GetLogFiles(args.logFile) + logLevel := LevelFromString(level) logger := &Logger{ donech: make(chan struct{}), json: json, level: logLevel, - logFiles: GetLogFiles(args.logFile), + logFiles: logFiles, } go logger.out() return logger @@ -177,29 +193,40 @@ func LevelFromString(s string) LogLevel { } } -type LogFiles struct { - stdout *os.File - stderr *os.File -} - +// Closes stdout and stderr if file pointers are not os.Stdout or os.Stderr func (files *LogFiles) Close() { if files.stdout != os.Stdout { files.stdout.Close() } } +// Сhecks the path to the file is correct +func IsValidLogFile(logFile string) error { + if logFile == NoLogFile { + return nil + } + + file, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + + file.Close() + return nil +} + +// Creates and/or opens a file for logging if the path to the file was specified. +// Otherwise uses os.Stdout and os.Stderr for logging func GetLogFiles(logFile string) *LogFiles { - if logFile == "none" { + if logFile == NoLogFile { return &LogFiles{ stdout: os.Stdout, stderr: os.Stderr, } } - file, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - panic(err) - } + file, _ := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + return &LogFiles{ stdout: file, stderr: file, From 876e360791cf47e75c409d84ffc508f2659f447f Mon Sep 17 00:00:00 2001 From: Roman Khomenko Date: Mon, 10 Jun 2024 10:29:47 +0300 Subject: [PATCH 3/3] log: linter fixed --- log/log.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/log/log.go b/log/log.go index db3804182..823160322 100644 --- a/log/log.go +++ b/log/log.go @@ -22,10 +22,10 @@ type LoggerOptions struct { logFile string } -// Type for setter functions that specify LoggerOptions parameters +// LoggerOption is type for setter functions that specify LoggerOptions parameters type LoggerOption func(*LoggerOptions) -// Setter for LoggerOptions.logFile parameter +// LogFile is setter for LoggerOptions.logFile parameter func LogFile(logFile string) LoggerOption { return func(args *LoggerOptions) { args.logFile = logFile @@ -87,7 +87,7 @@ type Logger struct { logFiles *LogFiles } -// Вefault value that disables the use of the log file +// NoLogFile is default value that disables the use of the log file const NoLogFile string = "" // New creates new logger. @@ -193,14 +193,14 @@ func LevelFromString(s string) LogLevel { } } -// Closes stdout and stderr if file pointers are not os.Stdout or os.Stderr +// Close closes stdout and stderr if file pointers are not os.Stdout or os.Stderr func (files *LogFiles) Close() { if files.stdout != os.Stdout { files.stdout.Close() } } -// Сhecks the path to the file is correct +// IsValidLogFile checks the path to the file is correct func IsValidLogFile(logFile string) error { if logFile == NoLogFile { return nil @@ -215,7 +215,7 @@ func IsValidLogFile(logFile string) error { return nil } -// Creates and/or opens a file for logging if the path to the file was specified. +// GetLogFiles creates and/or opens a file for logging if the path to the file was specified. // Otherwise uses os.Stdout and os.Stderr for logging func GetLogFiles(logFile string) *LogFiles { if logFile == NoLogFile {