Files
gocryptotrader/log/logger_setup.go
Adrian Gallagher f5faca2eb2 linter: Enable gofumpt and run against codebase (#1848)
* linter: Enable gofumpt and run against codebase

* Address shazbert's nits

* gofumpt: Fix issues after rebase
2025-03-18 10:23:16 +11:00

265 lines
7.1 KiB
Go

package log
import (
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/thrasher-corp/gocryptotrader/common/convert"
)
var (
errSubloggerConfigIsNil = errors.New("sublogger config is nil")
errUnhandledOutputWriter = errors.New("unhandled output writer")
errLogPathIsEmpty = errors.New("log path is empty")
errConfigNil = errors.New("config is nil")
errFileLoggingNotConfiguredCorrectly = errors.New("file logging not configured correctly")
)
// getWriters returns a new multi writer holder from sub logger configuration.
// Note: Calling function must have mutex lock in place.
func getWriters(s *SubLoggerConfig) (*multiWriterHolder, error) {
if s == nil {
return nil, errSubloggerConfigIsNil
}
outputWriters := strings.Split(s.Output, "|")
writers := make([]io.Writer, 0, len(outputWriters))
for x := range outputWriters {
var writer io.Writer
switch strings.ToLower(outputWriters[x]) {
case "stdout", "console":
writer = os.Stdout
case "stderr":
writer = os.Stderr
case "file":
if !fileLoggingConfiguredCorrectly {
return nil, errFileLoggingNotConfiguredCorrectly
}
writer = globalLogFile
default:
// Note: Do not want to add an io.Discard here as this adds
// additional write calls for no reason.
return nil, fmt.Errorf("%w: %s", errUnhandledOutputWriter, outputWriters[x])
}
writers = append(writers, writer)
}
return multiWriter(writers...)
}
// GenDefaultSettings return struct with known sane/working logger settings
func GenDefaultSettings() *Config {
return &Config{
Enabled: convert.BoolPtr(true),
SubLoggerConfig: SubLoggerConfig{
Level: "INFO|DEBUG|WARN|ERROR",
Output: "console",
},
LoggerFileConfig: &loggerFileConfig{
FileName: "log.txt",
Rotate: convert.BoolPtr(false),
},
AdvancedSettings: advancedSettings{
ShowLogSystemName: convert.BoolPtr(false),
Spacer: spacer,
TimeStampFormat: timestampFormat,
Headers: headers{
Info: "[INFO]",
Warn: "[WARN]",
Debug: "[DEBUG]",
Error: "[ERROR]",
},
},
}
}
// SetGlobalLogConfig sets the global config with the supplied config
func SetGlobalLogConfig(incoming *Config) error {
if incoming == nil {
return errConfigNil
}
var fileConf loggerFileConfig
if incoming.LoggerFileConfig != nil {
fileConf = *incoming.LoggerFileConfig
}
subs := make([]SubLoggerConfig, len(incoming.SubLoggers))
copy(subs, incoming.SubLoggers)
mu.Lock()
defer mu.Unlock()
globalLogConfig.SubLoggerConfig = incoming.SubLoggerConfig
globalLogConfig.Enabled = convert.BoolPtr(incoming.Enabled != nil && *incoming.Enabled)
globalLogConfig.LoggerFileConfig = &fileConf
globalLogConfig.AdvancedSettings = incoming.AdvancedSettings
return nil
}
// SetLogPath sets the log path for writing to file
func SetLogPath(newLogPath string) error {
if newLogPath == "" {
return errLogPathIsEmpty
}
mu.Lock()
defer mu.Unlock()
logPath = newLogPath
return nil
}
// GetLogPath returns path of log file
func GetLogPath() string {
mu.RLock()
defer mu.RUnlock()
return logPath
}
// configureSubLogger configures a new sub logger. Note: Calling function must
// have mutex lock in place.
func configureSubLogger(subLogger, levels string, output *multiWriterHolder) error {
logPtr, found := SubLoggers[subLogger]
if !found {
return fmt.Errorf("sub logger %v not found", subLogger)
}
err := logPtr.setOutput(output)
if err != nil {
return err
}
logPtr.setLevels(splitLevel(levels))
SubLoggers[subLogger] = logPtr
return nil
}
// SetupSubLoggers configure all sub loggers with provided configuration values
func SetupSubLoggers(s []SubLoggerConfig) error {
mu.Lock()
defer mu.Unlock()
for x := range s {
output, err := getWriters(&s[x])
if err != nil {
return err
}
err = configureSubLogger(strings.ToUpper(s[x].Name), s[x].Level, output)
if err != nil {
return err
}
}
return nil
}
// SetupGlobalLogger setup the global loggers with the default global config
// values.
func SetupGlobalLogger(botName string, structuredOutput bool) error {
mu.Lock()
defer mu.Unlock()
if fileLoggingConfiguredCorrectly {
globalLogFile = &Rotate{
FileName: globalLogConfig.LoggerFileConfig.FileName,
MaxSize: globalLogConfig.LoggerFileConfig.MaxSize,
Rotate: globalLogConfig.LoggerFileConfig.Rotate,
}
}
writers, err := getWriters(&globalLogConfig.SubLoggerConfig)
if err != nil {
return err
}
for _, subLogger := range SubLoggers {
subLogger.setLevels(splitLevel(globalLogConfig.Level))
subLogger.structuredLogging = structuredOutput
err = subLogger.setOutput(writers)
if err != nil {
return err
}
subLogger.botName = botName
}
logger = newLogger(globalLogConfig, botName)
return nil
}
// SetFileLoggingState can set file logging state if it is correctly configured
// or not. This will bypass the ability to log to file if set as false.
func SetFileLoggingState(correctlyConfigured bool) {
mu.Lock()
fileLoggingConfiguredCorrectly = correctlyConfigured
mu.Unlock()
}
func splitLevel(level string) (l Levels) {
enabledLevels := strings.Split(level, "|")
for x := range enabledLevels {
switch enabledLevels[x] {
case "DEBUG":
l.Debug = true
case "INFO":
l.Info = true
case "WARN":
l.Warn = true
case "ERROR":
l.Error = true
}
}
return
}
// registerNewSubLogger registers a new sub logger. Note: Calling function must
// have mutex lock in place.
func registerNewSubLogger(subLogger string) *SubLogger {
tempHolder, err := getWriters(&SubLoggerConfig{
Name: strings.ToUpper(subLogger),
Level: "INFO|WARN|DEBUG|ERROR",
Output: "stdout",
})
if err != nil {
return nil
}
temp := &SubLogger{
name: strings.ToUpper(subLogger),
output: tempHolder,
levels: splitLevel("INFO|WARN|DEBUG|ERROR"),
botName: logger.botName,
structuredLogging: globalLogConfig != nil && globalLogConfig.AdvancedSettings.StructuredLogging,
}
SubLoggers[subLogger] = temp
return temp
}
// register all loggers at package init()
func init() {
// Start persistent worker to handle logs
go loggerWorker()
mu.Lock()
Global = registerNewSubLogger("LOG")
ConnectionMgr = registerNewSubLogger("CONNECTION")
CommunicationMgr = registerNewSubLogger("COMMS")
APIServerMgr = registerNewSubLogger("API")
ConfigMgr = registerNewSubLogger("CONFIG")
DatabaseMgr = registerNewSubLogger("DATABASE")
DataHistory = registerNewSubLogger("DATAHISTORY")
OrderMgr = registerNewSubLogger("ORDER")
PortfolioMgr = registerNewSubLogger("PORTFOLIO")
SyncMgr = registerNewSubLogger("SYNC")
TimeMgr = registerNewSubLogger("TIMEKEEPER")
GCTScriptMgr = registerNewSubLogger("GCTSCRIPT")
WebsocketMgr = registerNewSubLogger("WEBSOCKET")
EventMgr = registerNewSubLogger("EVENT")
DispatchMgr = registerNewSubLogger("DISPATCH")
RequestSys = registerNewSubLogger("REQUESTER")
ExchangeSys = registerNewSubLogger("EXCHANGE")
GRPCSys = registerNewSubLogger("GRPC")
RESTSys = registerNewSubLogger("REST")
Ticker = registerNewSubLogger("TICKER")
OrderBook = registerNewSubLogger("ORDERBOOK")
Trade = registerNewSubLogger("TRADE")
Fill = registerNewSubLogger("FILL")
Currency = registerNewSubLogger("CURRENCY")
mu.Unlock()
}