mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
logger: reduce go routine generation (#992)
* logger: reduce go routine generation * logger: shift most of processing and prep work to the worker pool, add pool for fields because each log we are pushing the struct to the heap, has better segregation now and includes a buffer in scope instead of relying on a pool * logger: shift fmt package calls to worker pool * logger: conform tests to new design * linter: fix issues * Update log/logger_test.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update log/logger_test.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * UN-GLORIOUS: nits * logger: Handle config variable * logger: NITERINOS BY GLORIOUS CODE * logger: revert * glorious: nits * Panic at the disco: fix * Panic at the disco: fix * logger: make sure logger closed and job channel emptied on start up error * fix tests * logger: reduce globals * logger: finished reduces globals, reduce workers to one too keep everything in line. * logger: remove comments * logger/exhchange: linter issues * db/test: fix linter * logger: add tests shift wait before unlock * logger: consolidate worker code; fix linter issue and make sure we can sustain writing for external testing. * logger: fix race and warn for conflict in config * logger: fix name and add to tests * logger: remove zero value field * glorious: panic fix and removal of code * logger: reinstate channels in close * logger: shift reinstate processing to SetupGlobalLogger * logger: segregate config.json from internal log.Config * logger: fix silly mistake that is silly * engine: Add protection for nil issues and implement new constructor in tests * logger: Force singular mutex usage throughout package, throw away funcs that are not used outside of this package, unexport a bunch. Fix tests. * logger: actually set advanced settings * Update log/loggers.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update log/loggers.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update log/loggers.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update log/loggers.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update log/logger_multiwriter.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * logger: test issue when not purging temp file and contents * loggertest: add more protections for the panics * linter: fix * glorious: nits * cleanup * logger: linter fix * linter: fix(?) :/ * linter: revert change * linter: fix Co-authored-by: Scott <gloriousCode@users.noreply.github.com> Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
@@ -124,7 +124,7 @@ go build
|
||||
copy config_example.json %APPDATA%\GoCryptoTrader\config.json
|
||||
```
|
||||
|
||||
+ Make any neccessary changes to the `config.json` file.
|
||||
+ Make any necessary changes to the `config.json` file.
|
||||
+ Run the `gocryptotrader` binary file inside your GOPATH bin folder.
|
||||
|
||||
## Donations
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
backtest "github.com/thrasher-corp/gocryptotrader/backtester/engine"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/plugins/strategies"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
"github.com/thrasher-corp/gocryptotrader/engine"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -110,12 +111,18 @@ func main() {
|
||||
common.PurgeColours()
|
||||
}
|
||||
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.GlobalLogConfig.AdvancedSettings.ShowLogSystemName = &logSubHeader
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Info = common.CMDColours.Info + "[INFO]" + common.CMDColours.Default
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Warn = common.CMDColours.Warn + "[WARN]" + common.CMDColours.Default
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Debug = common.CMDColours.Debug + "[DEBUG]" + common.CMDColours.Default
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Error = common.CMDColours.Error + "[ERROR]" + common.CMDColours.Default
|
||||
defaultLogSettings := log.GenDefaultSettings()
|
||||
defaultLogSettings.AdvancedSettings.ShowLogSystemName = convert.BoolPtr(logSubHeader)
|
||||
defaultLogSettings.AdvancedSettings.Headers.Info = common.CMDColours.Info + "[INFO]" + common.CMDColours.Default
|
||||
defaultLogSettings.AdvancedSettings.Headers.Warn = common.CMDColours.Warn + "[WARN]" + common.CMDColours.Default
|
||||
defaultLogSettings.AdvancedSettings.Headers.Debug = common.CMDColours.Debug + "[DEBUG]" + common.CMDColours.Default
|
||||
defaultLogSettings.AdvancedSettings.Headers.Error = common.CMDColours.Error + "[ERROR]" + common.CMDColours.Default
|
||||
err = log.SetGlobalLogConfig(defaultLogSettings)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = log.SetupGlobalLogger()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
|
||||
@@ -141,7 +141,7 @@ func (d *Data) enhanceCandles() error {
|
||||
Asset: lookup.Asset,
|
||||
Pair: lookup.Pair,
|
||||
Interval: lookup.Interval,
|
||||
Watermark: fmt.Sprintf("%s - %s - %s", strings.Title(lookup.Exchange), lookup.Asset.String(), lookup.Pair.Upper()), //nolint // Title usage
|
||||
Watermark: fmt.Sprintf("%s - %s - %s", strings.Title(lookup.Exchange), lookup.Asset.String(), lookup.Pair.Upper()), //nolint:staticcheck // Ignore Title usage warning
|
||||
}
|
||||
|
||||
statsForCandles :=
|
||||
|
||||
@@ -101,10 +101,13 @@ func main() {
|
||||
flag.BoolVar(&verbose, "verbose", false, "increases logging verbosity for API Update Checker")
|
||||
flag.BoolVar(&create, "create", false, "specifies whether to automatically create trello list, card and checklist in a given board")
|
||||
flag.Parse()
|
||||
var err error
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.RWM.Unlock()
|
||||
|
||||
err := log.SetGlobalLogConfig(log.GenDefaultSettings())
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = log.SetupGlobalLogger()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
gctfile "github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -25,14 +24,12 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setTestVars()
|
||||
testMode = true
|
||||
c := log.GenDefaultSettings()
|
||||
c.Enabled = convert.BoolPtr(true)
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = c
|
||||
log.RWM.Unlock()
|
||||
err := log.SetGlobalLogConfig(log.GenDefaultSettings())
|
||||
if err != nil {
|
||||
log.Error(log.Global, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infoln(log.Global, "set verbose to true for more detailed output")
|
||||
var err error
|
||||
configData, err = readFileData(jsonFile)
|
||||
if err != nil {
|
||||
log.Error(log.Global, err)
|
||||
|
||||
@@ -486,7 +486,7 @@ func GetPackageName(name string, capital bool) string {
|
||||
i = len(newStrings) - 1
|
||||
}
|
||||
if capital {
|
||||
return strings.Replace(strings.Title(newStrings[i]), "_", " ", -1) //nolint // ignore staticcheck strings.Title warning
|
||||
return strings.Replace(strings.Title(newStrings[i]), "_", " ", -1) //nolint:staticcheck // Ignore Title usage warning
|
||||
}
|
||||
return newStrings[i]
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ go build
|
||||
copy config_example.json %APPDATA%\GoCryptoTrader\config.json
|
||||
```
|
||||
|
||||
+ Make any neccessary changes to the `config.json` file.
|
||||
+ Make any necessary changes to the `config.json` file.
|
||||
+ Run the `gocryptotrader` binary file inside your GOPATH bin folder.
|
||||
|
||||
{{template "donations" .}}
|
||||
|
||||
@@ -1188,20 +1188,20 @@ func (c *Config) CheckLoggerConfig() error {
|
||||
log.Warnf(log.ConfigMgr, "Logger rotation size invalid, defaulting to %v", log.DefaultMaxFileSize)
|
||||
c.Logging.LoggerFileConfig.MaxSize = log.DefaultMaxFileSize
|
||||
}
|
||||
log.FileLoggingConfiguredCorrectly = true
|
||||
log.SetFileLoggingState( /*Is correctly configured*/ true)
|
||||
}
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = &c.Logging
|
||||
log.RWM.Unlock()
|
||||
|
||||
logPath := c.GetDataPath("logs")
|
||||
err := common.CreateDir(logPath)
|
||||
err := log.SetGlobalLogConfig(&c.Logging)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.LogPath = logPath
|
||||
|
||||
return nil
|
||||
logPath := c.GetDataPath("logs")
|
||||
err = common.CreateDir(logPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return log.SetLogPath(logPath)
|
||||
}
|
||||
|
||||
func (c *Config) checkGCTScriptConfig() error {
|
||||
|
||||
@@ -100,22 +100,24 @@ func (p Pairs) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.Join())
|
||||
}
|
||||
|
||||
// Upper updates the original pairs and returns the pairs for convenience if
|
||||
// needed.
|
||||
// Upper updates and returns the entire slice of pairs to upper casing
|
||||
// NOTE: Do not duplicate slice reference as this can cause race issues.
|
||||
func (p Pairs) Upper() Pairs {
|
||||
newSlice := make(Pairs, len(p))
|
||||
for i := range p {
|
||||
p[i] = p[i].Upper()
|
||||
newSlice[i] = p[i].Upper()
|
||||
}
|
||||
return p
|
||||
return newSlice
|
||||
}
|
||||
|
||||
// Lower updates the original pairs and returns the pairs for convenience if
|
||||
// needed.
|
||||
// Lower updates and returns the entire slice of pairs to upper casing
|
||||
// NOTE: Do not duplicate slice reference as this can cause race issues.
|
||||
func (p Pairs) Lower() Pairs {
|
||||
newSlice := make(Pairs, len(p))
|
||||
for i := range p {
|
||||
p[i] = p[i].Lower()
|
||||
newSlice[i] = p[i].Lower()
|
||||
}
|
||||
return p
|
||||
return newSlice
|
||||
}
|
||||
|
||||
// Contains checks to see if a specified pair exists inside a currency pair
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestPairsUpper(t *testing.T) {
|
||||
}
|
||||
if expected := "BTC_USD,BTC_AUD,BTC_LTC"; pairs.Upper().Join() != expected {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
expected, pairs.Join())
|
||||
expected, pairs.Upper().Join())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestPairsLower(t *testing.T) {
|
||||
}
|
||||
if expected := "btc_usd,btc_aud,btc_ltc"; pairs.Lower().Join() != expected {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
expected, pairs.Join())
|
||||
expected, pairs.Lower().Join())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
psqlConn "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres"
|
||||
sqliteConn "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite3"
|
||||
"github.com/thrasher-corp/gocryptotrader/database/repository"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/goose"
|
||||
"github.com/thrasher-corp/sqlboiler/boil"
|
||||
)
|
||||
@@ -119,14 +118,7 @@ func migrateDB(db *sql.DB) error {
|
||||
|
||||
// EnableVerboseTestOutput enables debug output for SQL queries
|
||||
func EnableVerboseTestOutput() error {
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.RWM.Unlock()
|
||||
if err := log.SetupGlobalLogger(); err != nil {
|
||||
return err
|
||||
}
|
||||
DBLogger := database.Logger{}
|
||||
boil.DebugMode = true
|
||||
boil.DebugWriter = DBLogger
|
||||
boil.DebugWriter = database.Logger{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ func (m *DataHistoryManager) Start() error {
|
||||
if m == nil {
|
||||
return ErrNilSubsystem
|
||||
}
|
||||
if m.databaseConnectionInstance == nil {
|
||||
return errNilDatabaseConnectionManager
|
||||
}
|
||||
if !atomic.CompareAndSwapInt32(&m.started, 0, 1) {
|
||||
return ErrSubSystemAlreadyStarted
|
||||
}
|
||||
@@ -225,7 +228,7 @@ func (m *DataHistoryManager) run() {
|
||||
case <-m.shutdown:
|
||||
return
|
||||
case <-m.interval.C:
|
||||
if m.databaseConnectionInstance.IsConnected() {
|
||||
if m.databaseConnectionInstance != nil && m.databaseConnectionInstance.IsConnected() {
|
||||
go func() {
|
||||
if err := m.runJobs(); err != nil {
|
||||
log.Error(log.DataHistory, err)
|
||||
|
||||
@@ -979,6 +979,7 @@ func createDHM(t *testing.T) (*DataHistoryManager, *datahistoryjob.DataHistoryJo
|
||||
},
|
||||
}
|
||||
m := &DataHistoryManager{
|
||||
databaseConnectionInstance: &dataBaseConnection{},
|
||||
jobDB: dataHistoryJobService{
|
||||
job: j,
|
||||
},
|
||||
@@ -993,6 +994,20 @@ func createDHM(t *testing.T) (*DataHistoryManager, *datahistoryjob.DataHistoryJo
|
||||
return m, j
|
||||
}
|
||||
|
||||
type dataBaseConnection struct{}
|
||||
|
||||
func (d *dataBaseConnection) IsConnected() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *dataBaseConnection) GetSQL() (*sql.DB, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (d *dataBaseConnection) GetConfig() *database.Config {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProcessCandleData(t *testing.T) {
|
||||
t.Parallel()
|
||||
m, _ := createDHM(t)
|
||||
|
||||
@@ -395,8 +395,11 @@ func (bot *Engine) Start() error {
|
||||
gctlog.Debugf(gctlog.Global, "Bot '%s' started.\n", bot.Config.Name)
|
||||
gctlog.Debugf(gctlog.Global, "Using data dir: %s\n", bot.Settings.DataDir)
|
||||
if *bot.Config.Logging.Enabled && strings.Contains(bot.Config.Logging.Output, "file") {
|
||||
gctlog.Debugf(gctlog.Global, "Using log file: %s\n",
|
||||
filepath.Join(gctlog.LogPath, bot.Config.Logging.LoggerFileConfig.FileName))
|
||||
gctlog.Debugf(gctlog.Global,
|
||||
"Using log file: %s\n",
|
||||
filepath.Join(gctlog.GetLogPath(),
|
||||
bot.Config.Logging.LoggerFileConfig.FileName),
|
||||
)
|
||||
}
|
||||
gctlog.Debugf(gctlog.Global,
|
||||
"Using %d out of %d logical processors for runtime performance\n",
|
||||
@@ -725,6 +728,7 @@ func (bot *Engine) Stop() {
|
||||
|
||||
// Wait for services to gracefully shutdown
|
||||
bot.ServicesWG.Wait()
|
||||
gctlog.Infoln(gctlog.Global, "Exiting.")
|
||||
if err := gctlog.CloseLogger(); err != nil {
|
||||
log.Printf("Failed to close logger. Error: %v\n", err)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ package exchange
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -18,7 +16,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
)
|
||||
|
||||
@@ -27,17 +24,6 @@ const (
|
||||
defaultTestCurrencyPair = "BTC-USD"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.RWM.Unlock()
|
||||
if err := log.SetupGlobalLogger(); err != nil {
|
||||
fmt.Println("Cannot setup global logger. Error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestSupportsRESTTickerBatchUpdates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -29,15 +27,6 @@ var (
|
||||
testScriptRunnerInvalid = filepath.Join("..", "..", "testdata", "gctscript", "invalid_timer.gct")
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := log.GenDefaultSettings()
|
||||
c.Enabled = convert.BoolPtr(false)
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = c
|
||||
log.RWM.Unlock()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestNewVM(t *testing.T) {
|
||||
manager := GctScriptManager{
|
||||
config: configHelper(true, true, maxTestVirtualMachines),
|
||||
|
||||
@@ -34,13 +34,9 @@ const (
|
||||
|
||||
var (
|
||||
settings = engine.Settings{
|
||||
ConfigFile: filepath.Join("..", "..", "..", "..", "testdata", "configtest.json"),
|
||||
EnableDryRun: true,
|
||||
DataDir: filepath.Join("..", "..", "..", "..", "testdata", "gocryptotrader"),
|
||||
Verbose: false,
|
||||
EnableGRPC: false,
|
||||
EnableDeprecatedRPC: false,
|
||||
EnableWebsocketRPC: false,
|
||||
ConfigFile: filepath.Join("..", "..", "..", "..", "testdata", "configtest.json"),
|
||||
EnableDryRun: true,
|
||||
DataDir: filepath.Join("..", "..", "..", "..", "testdata", "gocryptotrader"),
|
||||
}
|
||||
exchangeTest = Exchange{}
|
||||
)
|
||||
|
||||
@@ -3,8 +3,8 @@ package log
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -15,56 +15,32 @@ var (
|
||||
|
||||
func newLogger(c *Config) Logger {
|
||||
return Logger{
|
||||
Timestamp: c.AdvancedSettings.TimeStampFormat,
|
||||
Spacer: c.AdvancedSettings.Spacer,
|
||||
ErrorHeader: c.AdvancedSettings.Headers.Error,
|
||||
InfoHeader: c.AdvancedSettings.Headers.Info,
|
||||
WarnHeader: c.AdvancedSettings.Headers.Warn,
|
||||
DebugHeader: c.AdvancedSettings.Headers.Debug,
|
||||
ShowLogSystemName: *c.AdvancedSettings.ShowLogSystemName,
|
||||
TimestampFormat: c.AdvancedSettings.TimeStampFormat,
|
||||
Spacer: c.AdvancedSettings.Spacer,
|
||||
ErrorHeader: c.AdvancedSettings.Headers.Error,
|
||||
InfoHeader: c.AdvancedSettings.Headers.Info,
|
||||
WarnHeader: c.AdvancedSettings.Headers.Warn,
|
||||
DebugHeader: c.AdvancedSettings.Headers.Debug,
|
||||
ShowLogSystemName: c.AdvancedSettings.ShowLogSystemName != nil && *c.AdvancedSettings.ShowLogSystemName,
|
||||
BypassJobChannelFilledWarning: c.AdvancedSettings.BypassJobChannelFilledWarning,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) newLogEvent(data, header, slName string, w io.Writer) error {
|
||||
if w == nil {
|
||||
return errors.New("io.Writer not set")
|
||||
}
|
||||
|
||||
pool, ok := eventPool.Get().(*[]byte)
|
||||
if !ok {
|
||||
return errors.New("unable to type assert slice of bytes pointer")
|
||||
}
|
||||
|
||||
*pool = append(*pool, header...)
|
||||
if l.ShowLogSystemName {
|
||||
*pool = append(*pool, l.Spacer...)
|
||||
*pool = append(*pool, slName...)
|
||||
}
|
||||
*pool = append(*pool, l.Spacer...)
|
||||
if l.Timestamp != "" {
|
||||
*pool = time.Now().AppendFormat(*pool, l.Timestamp)
|
||||
}
|
||||
*pool = append(*pool, l.Spacer...)
|
||||
*pool = append(*pool, data...)
|
||||
if data == "" || data[len(data)-1] != '\n' {
|
||||
*pool = append(*pool, '\n')
|
||||
}
|
||||
_, err := w.Write(*pool)
|
||||
*pool = (*pool)[:0]
|
||||
eventPool.Put(pool)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CloseLogger is called on shutdown of application
|
||||
func CloseLogger() error {
|
||||
return GlobalLogFile.Close()
|
||||
ch := make(chan struct{})
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
globalLogConfig.Enabled = convert.BoolPtr(false)
|
||||
jobsChannel <- &job{Passback: ch}
|
||||
<-ch
|
||||
return globalLogFile.Close()
|
||||
}
|
||||
|
||||
// Level retries the current sublogger levels
|
||||
// Level retrieves the current sublogger levels
|
||||
func Level(name string) (Levels, error) {
|
||||
RWM.RLock()
|
||||
defer RWM.RUnlock()
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
subLogger, found := SubLoggers[name]
|
||||
if !found {
|
||||
return Levels{}, fmt.Errorf("logger %s not found", name)
|
||||
@@ -74,12 +50,12 @@ func Level(name string) (Levels, error) {
|
||||
|
||||
// SetLevel sets sublogger levels
|
||||
func SetLevel(s, level string) (Levels, error) {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
subLogger, found := SubLoggers[s]
|
||||
if !found {
|
||||
return Levels{}, fmt.Errorf("sub logger %v not found", s)
|
||||
}
|
||||
subLogger.SetLevels(splitLevel(level))
|
||||
subLogger.setLevels(splitLevel(level))
|
||||
return subLogger.levels, nil
|
||||
}
|
||||
|
||||
@@ -4,17 +4,104 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
errWriterNotFound = errors.New("io.Writer not found")
|
||||
errWriterAlreadyLoaded = errors.New("io.Writer already loaded")
|
||||
errJobsChannelIsFull = errors.New("logger jobs channel is filled")
|
||||
errWriterIsNil = errors.New("io writer is nil")
|
||||
)
|
||||
|
||||
// loggerWorker handles all work staged to be written to configured io.Writer(s)
|
||||
// This worker is generated in init() to handle full workload.
|
||||
func loggerWorker() {
|
||||
// Localise a persistent buffer for a worker, this does not need to be
|
||||
// garbage collected.
|
||||
buffer := make([]byte, 0, defaultBufferCapacity)
|
||||
var n int
|
||||
var err error
|
||||
for j := range jobsChannel {
|
||||
if j.Passback != nil {
|
||||
j.Passback <- struct{}{}
|
||||
continue
|
||||
}
|
||||
data := j.fn()
|
||||
buffer = append(buffer, j.Header...)
|
||||
if j.ShowLogSystemName {
|
||||
buffer = append(buffer, j.Spacer...)
|
||||
buffer = append(buffer, j.SlName...)
|
||||
}
|
||||
buffer = append(buffer, j.Spacer...)
|
||||
if j.TimestampFormat != "" {
|
||||
buffer = time.Now().AppendFormat(buffer, j.TimestampFormat)
|
||||
}
|
||||
buffer = append(buffer, j.Spacer...)
|
||||
buffer = append(buffer, data...)
|
||||
if data == "" || data[len(data)-1] != '\n' {
|
||||
buffer = append(buffer, '\n')
|
||||
}
|
||||
|
||||
for x := range j.Writers {
|
||||
n, err = j.Writers[x].Write(buffer)
|
||||
if err != nil {
|
||||
displayError(fmt.Errorf("%T %w", j.Writers[x], err))
|
||||
} else if n != len(buffer) {
|
||||
displayError(fmt.Errorf("%T %w", j.Writers[x], io.ErrShortWrite))
|
||||
}
|
||||
}
|
||||
buffer = buffer[:0] // Clean buffer
|
||||
jobsPool.Put(j)
|
||||
}
|
||||
}
|
||||
|
||||
// deferral defines functionality that will capture data string processing and
|
||||
// defer that to the worker pool if needed.
|
||||
type deferral func() string
|
||||
|
||||
// StageLogEvent stages a new logger event in a jobs channel to be processed by
|
||||
// a worker pool. This segregates the need to process the log string and the
|
||||
// writes to the required io.Writer.
|
||||
func (mw *multiWriterHolder) StageLogEvent(fn deferral, header, slName, spacer, timestampFormat string, showLogSystemName, bypassWarning bool) {
|
||||
newJob := jobsPool.Get().(*job) //nolint:forcetypeassert // Not necessary from a pool
|
||||
newJob.Writers = mw.writers
|
||||
newJob.fn = fn
|
||||
newJob.Header = header
|
||||
newJob.SlName = slName
|
||||
newJob.ShowLogSystemName = showLogSystemName
|
||||
newJob.Spacer = spacer
|
||||
newJob.TimestampFormat = timestampFormat
|
||||
|
||||
select {
|
||||
case jobsChannel <- newJob:
|
||||
default:
|
||||
// This will cause temporary caller impedance, which can have a knock
|
||||
// on effect in processing.
|
||||
if !bypassWarning {
|
||||
log.Printf("Logger warning: %v\n", errJobsChannelIsFull)
|
||||
}
|
||||
jobsChannel <- newJob
|
||||
}
|
||||
}
|
||||
|
||||
// multiWriter make and return a new copy of multiWriterHolder
|
||||
func multiWriter(writers ...io.Writer) (*multiWriterHolder, error) {
|
||||
mw := &multiWriterHolder{}
|
||||
for x := range writers {
|
||||
err := mw.add(writers[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mw, nil
|
||||
}
|
||||
|
||||
// Add appends a new writer to the multiwriter slice
|
||||
func (mw *multiWriterHolder) Add(writer io.Writer) error {
|
||||
mw.mu.Lock()
|
||||
defer mw.mu.Unlock()
|
||||
func (mw *multiWriterHolder) add(writer io.Writer) error {
|
||||
if writer == nil {
|
||||
return errWriterIsNil
|
||||
}
|
||||
for i := range mw.writers {
|
||||
if mw.writers[i] == writer {
|
||||
return errWriterAlreadyLoaded
|
||||
@@ -23,69 +110,3 @@ func (mw *multiWriterHolder) Add(writer io.Writer) error {
|
||||
mw.writers = append(mw.writers, writer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes existing writer from multiwriter slice
|
||||
func (mw *multiWriterHolder) Remove(writer io.Writer) error {
|
||||
mw.mu.Lock()
|
||||
defer mw.mu.Unlock()
|
||||
for i := range mw.writers {
|
||||
if mw.writers[i] != writer {
|
||||
continue
|
||||
}
|
||||
mw.writers[i] = mw.writers[len(mw.writers)-1]
|
||||
mw.writers[len(mw.writers)-1] = nil
|
||||
mw.writers = mw.writers[:len(mw.writers)-1]
|
||||
return nil
|
||||
}
|
||||
return errWriterNotFound
|
||||
}
|
||||
|
||||
// Write concurrent safe Write for each writer
|
||||
func (mw *multiWriterHolder) Write(p []byte) (int, error) {
|
||||
type data struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
results := make(chan data, len(mw.writers))
|
||||
mw.mu.RLock()
|
||||
defer mw.mu.RUnlock()
|
||||
for x := range mw.writers {
|
||||
go func(w io.Writer, p []byte, ch chan<- data) {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
ch <- data{n, fmt.Errorf("%T %w", w, err)}
|
||||
return
|
||||
}
|
||||
if n != len(p) {
|
||||
ch <- data{n, fmt.Errorf("%T %w", w, io.ErrShortWrite)}
|
||||
return
|
||||
}
|
||||
ch <- data{n, nil}
|
||||
}(mw.writers[x], p, results)
|
||||
}
|
||||
|
||||
for range mw.writers {
|
||||
// NOTE: These results do not necessarily reflect the current io.writer
|
||||
// due to the go scheduler and writer finishing at different times, the
|
||||
// response coming from the channel might not match up with the for loop
|
||||
// writer.
|
||||
d := <-results
|
||||
if d.err != nil {
|
||||
return d.n, d.err
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// multiWriter make and return a new copy of multiWriterHolder
|
||||
func multiWriter(writers ...io.Writer) (*multiWriterHolder, error) {
|
||||
mw := &multiWriterHolder{}
|
||||
for x := range writers {
|
||||
err := mw.Add(writers[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mw, nil
|
||||
}
|
||||
|
||||
@@ -17,9 +17,6 @@ var (
|
||||
|
||||
// Write implementation to satisfy io.Writer handles length check and rotation
|
||||
func (r *Rotate) Write(output []byte) (n int, err error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
outputLen := int64(len(output))
|
||||
if outputLen > r.maxSize() {
|
||||
return 0, fmt.Errorf(
|
||||
@@ -49,7 +46,7 @@ func (r *Rotate) Write(output []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (r *Rotate) openOrCreateFile(n int64) error {
|
||||
logFile := filepath.Join(LogPath, r.FileName)
|
||||
logFile := filepath.Join(GetLogPath(), r.FileName)
|
||||
info, err := os.Stat(logFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -79,12 +76,12 @@ func (r *Rotate) openNew() error {
|
||||
if r.FileName == "" {
|
||||
return fmt.Errorf("cannot open new file: %w", errFileNameIsEmpty)
|
||||
}
|
||||
name := filepath.Join(LogPath, r.FileName)
|
||||
name := filepath.Join(GetLogPath(), r.FileName)
|
||||
_, err := os.Stat(name)
|
||||
|
||||
if err == nil {
|
||||
timestamp := time.Now().Format("2006-01-02T15-04-05")
|
||||
newName := filepath.Join(LogPath, timestamp+"-"+r.FileName)
|
||||
newName := filepath.Join(GetLogPath(), timestamp+"-"+r.FileName)
|
||||
|
||||
err = file.Move(name, newName)
|
||||
if err != nil {
|
||||
@@ -113,8 +110,6 @@ func (r *Rotate) close() (err error) {
|
||||
|
||||
// Close handler for open file
|
||||
func (r *Rotate) Close() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.close()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -18,5 +17,4 @@ type Rotate struct {
|
||||
|
||||
size int64
|
||||
output *os.File
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
@@ -11,11 +11,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errSubloggerConfigIsNil = errors.New("sublogger config is nil")
|
||||
errUnhandledOutputWriter = errors.New("unhandled output writer")
|
||||
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")
|
||||
)
|
||||
|
||||
func getWriters(s *SubLoggerConfig) (io.Writer, error) {
|
||||
// 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
|
||||
}
|
||||
@@ -30,12 +35,13 @@ func getWriters(s *SubLoggerConfig) (io.Writer, error) {
|
||||
case "stderr":
|
||||
writer = os.Stderr
|
||||
case "file":
|
||||
if FileLoggingConfiguredCorrectly {
|
||||
writer = GlobalLogFile
|
||||
if !fileLoggingConfiguredCorrectly {
|
||||
return nil, errFileLoggingNotConfiguredCorrectly
|
||||
}
|
||||
writer = globalLogFile
|
||||
default:
|
||||
// Note: Do not want to add a io.Discard here as this adds
|
||||
// additional routines for every write for no reason.
|
||||
// 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)
|
||||
@@ -54,7 +60,6 @@ func GenDefaultSettings() *Config {
|
||||
LoggerFileConfig: &loggerFileConfig{
|
||||
FileName: "log.txt",
|
||||
Rotate: convert.BoolPtr(false),
|
||||
MaxSize: 0,
|
||||
},
|
||||
AdvancedSettings: advancedSettings{
|
||||
ShowLogSystemName: convert.BoolPtr(false),
|
||||
@@ -70,22 +75,65 @@ func GenDefaultSettings() *Config {
|
||||
}
|
||||
}
|
||||
|
||||
func configureSubLogger(subLogger, levels string, output io.Writer) error {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
// 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)
|
||||
}
|
||||
|
||||
logPtr.SetOutput(output)
|
||||
logPtr.SetLevels(splitLevel(levels))
|
||||
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 {
|
||||
@@ -101,33 +149,45 @@ func SetupSubLoggers(s []SubLoggerConfig) error {
|
||||
|
||||
// SetupGlobalLogger setup the global loggers with the default global config values
|
||||
func SetupGlobalLogger() error {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if FileLoggingConfiguredCorrectly {
|
||||
GlobalLogFile = &Rotate{
|
||||
FileName: GlobalLogConfig.LoggerFileConfig.FileName,
|
||||
MaxSize: GlobalLogConfig.LoggerFileConfig.MaxSize,
|
||||
Rotate: GlobalLogConfig.LoggerFileConfig.Rotate,
|
||||
if fileLoggingConfiguredCorrectly {
|
||||
globalLogFile = &Rotate{
|
||||
FileName: globalLogConfig.LoggerFileConfig.FileName,
|
||||
MaxSize: globalLogConfig.LoggerFileConfig.MaxSize,
|
||||
Rotate: globalLogConfig.LoggerFileConfig.Rotate,
|
||||
}
|
||||
}
|
||||
|
||||
for x := range SubLoggers {
|
||||
SubLoggers[x].SetLevels(splitLevel(GlobalLogConfig.Level))
|
||||
writers, err := getWriters(&GlobalLogConfig.SubLoggerConfig)
|
||||
writers, err := getWriters(&globalLogConfig.SubLoggerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, subLogger := range SubLoggers {
|
||||
subLogger.setLevels(splitLevel(globalLogConfig.Level))
|
||||
err = subLogger.setOutput(writers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
SubLoggers[x].SetOutput(writers)
|
||||
}
|
||||
logger = newLogger(GlobalLogConfig)
|
||||
logger = newLogger(globalLogConfig)
|
||||
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 level := enabledLevels[x]; level {
|
||||
switch enabledLevels[x] {
|
||||
case "DEBUG":
|
||||
l.Debug = true
|
||||
case "INFO":
|
||||
@@ -141,20 +201,32 @@ func splitLevel(level string) (l Levels) {
|
||||
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: os.Stdout,
|
||||
output: tempHolder,
|
||||
levels: splitLevel("INFO|WARN|DEBUG|ERROR"),
|
||||
}
|
||||
RWM.Lock()
|
||||
SubLoggers[subLogger] = temp
|
||||
RWM.Unlock()
|
||||
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")
|
||||
@@ -182,4 +254,5 @@ func init() {
|
||||
Trade = registerNewSubLogger("TRADE")
|
||||
Fill = registerNewSubLogger("FILL")
|
||||
Currency = registerNewSubLogger("CURRENCY")
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
err := setupTestLoggers()
|
||||
if err != nil {
|
||||
log.Fatal("cannot set up test loggers", err)
|
||||
}
|
||||
tempDir, err := os.MkdirTemp(os.TempDir(), "")
|
||||
if err != nil {
|
||||
log.Fatal("Cannot create temporary file", err)
|
||||
}
|
||||
log.Println("temp dir created at:", tempDir)
|
||||
LogPath = tempDir
|
||||
r := m.Run()
|
||||
err = os.Remove(tempDir)
|
||||
if err != nil {
|
||||
log.Println("failed to remove temp file:", tempDir)
|
||||
}
|
||||
os.Exit(r)
|
||||
}
|
||||
|
||||
func setupTestLoggers() error {
|
||||
logTest := Config{
|
||||
var (
|
||||
testConfigEnabled = &Config{
|
||||
Enabled: convert.BoolPtr(true),
|
||||
SubLoggerConfig: SubLoggerConfig{
|
||||
Output: "console",
|
||||
@@ -56,34 +37,111 @@ func setupTestLoggers() error {
|
||||
Output: "stdout",
|
||||
}},
|
||||
}
|
||||
RWM.Lock()
|
||||
GlobalLogConfig = &logTest
|
||||
RWM.Unlock()
|
||||
if err := SetupGlobalLogger(); err != nil {
|
||||
testConfigDisabled = &Config{
|
||||
Enabled: convert.BoolPtr(false),
|
||||
SubLoggerConfig: SubLoggerConfig{Output: "console"},
|
||||
}
|
||||
|
||||
tempDir string
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
err := setupTestLoggers()
|
||||
if err != nil {
|
||||
log.Fatal("cannot set up test loggers", err)
|
||||
}
|
||||
tempDir, err = os.MkdirTemp(os.TempDir(), "")
|
||||
if err != nil {
|
||||
log.Fatal("Cannot create temporary file", err)
|
||||
}
|
||||
log.Println("temp dir created at:", tempDir)
|
||||
err = SetLogPath(tempDir)
|
||||
if err != nil {
|
||||
log.Fatal("Cannot set log path", err)
|
||||
}
|
||||
r := m.Run()
|
||||
err = CloseLogger()
|
||||
if err != nil {
|
||||
log.Fatalf("CloseLogger() failed %v", err)
|
||||
}
|
||||
err = os.RemoveAll(tempDir)
|
||||
if err != nil {
|
||||
log.Fatal("failed to remove temp file:", tempDir, err)
|
||||
}
|
||||
os.Exit(r)
|
||||
}
|
||||
|
||||
func setupTestLoggers() error {
|
||||
err := SetGlobalLogConfig(testConfigEnabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SetupSubLoggers(logTest.SubLoggers)
|
||||
err = SetupGlobalLogger()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SetupSubLoggers(testConfigEnabled.SubLoggers)
|
||||
}
|
||||
|
||||
func SetupDisabled() error {
|
||||
logTest := Config{
|
||||
Enabled: convert.BoolPtr(false),
|
||||
}
|
||||
RWM.Lock()
|
||||
GlobalLogConfig = &logTest
|
||||
RWM.Unlock()
|
||||
|
||||
if err := SetupGlobalLogger(); err != nil {
|
||||
err := SetGlobalLogConfig(testConfigDisabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SetupSubLoggers(logTest.SubLoggers)
|
||||
err = SetupGlobalLogger()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SetupSubLoggers(testConfigDisabled.SubLoggers)
|
||||
}
|
||||
|
||||
func BenchmarkInfo(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Info(Global, "Hello this is an info benchmark")
|
||||
func TestSetGlobalLogConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := SetGlobalLogConfig(nil)
|
||||
if !errors.Is(err, errConfigNil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errConfigNil)
|
||||
}
|
||||
err = SetGlobalLogConfig(testConfigEnabled)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLogPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := SetLogPath("")
|
||||
if !errors.Is(err, errLogPathIsEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errLogPathIsEmpty)
|
||||
}
|
||||
|
||||
err = SetLogPath(tempDir)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if path := GetLogPath(); path != tempDir {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", path, tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFileLoggingState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
SetFileLoggingState(true)
|
||||
if !getFileLoggingState() {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
SetFileLoggingState(false)
|
||||
if getFileLoggingState() {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func getFileLoggingState() bool {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return fileLoggingConfiguredCorrectly
|
||||
}
|
||||
|
||||
func TestAddWriter(t *testing.T) {
|
||||
@@ -97,61 +155,29 @@ func TestAddWriter(t *testing.T) {
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
err = mw.Add(io.Discard)
|
||||
err = mw.add(io.Discard)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Add(os.Stdin)
|
||||
err = mw.add(os.Stdin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Add(os.Stdout)
|
||||
err = mw.add(os.Stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = mw.add(nil)
|
||||
if !errors.Is(err, errWriterIsNil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWriterIsNil)
|
||||
}
|
||||
|
||||
if total := len(mw.writers); total != 3 {
|
||||
t.Errorf("expected m.Writers to be 3 %v", total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
mw, err := multiWriter()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Add(io.Discard)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Add(os.Stdin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Add(os.Stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
total := len(mw.writers)
|
||||
err = mw.Remove(os.Stdin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Remove(os.Stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Remove(&bytes.Buffer{})
|
||||
if !errors.Is(err, errWriterNotFound) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWriterNotFound)
|
||||
}
|
||||
|
||||
if len(mw.writers) != total-2 {
|
||||
t.Errorf("expected m.Writers to be %v got %v", total-2, len(mw.writers))
|
||||
}
|
||||
}
|
||||
|
||||
type WriteShorter struct{}
|
||||
|
||||
func (w *WriteShorter) Write(p []byte) (int, error) {
|
||||
@@ -168,60 +194,76 @@ var errWriteError = errors.New("write error")
|
||||
|
||||
func TestMultiWriterWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
mw, err := multiWriter(io.Discard, &bytes.Buffer{})
|
||||
|
||||
fields := &logFields{}
|
||||
buff := newTestBuffer()
|
||||
|
||||
var err error
|
||||
fields.output, err = multiWriter(io.Discard, buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := "woooooooooooooooooooooooooooooooooooow"
|
||||
l, err := mw.Write([]byte(payload))
|
||||
fields.output.StageLogEvent(func() string { return payload }, "", "", "", "", false, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l != len(payload) {
|
||||
t.Fatal("unexpected return")
|
||||
}
|
||||
|
||||
mw, err = multiWriter(&WriteShorter{}, io.Discard)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = mw.Write([]byte(payload))
|
||||
if !errors.Is(err, io.ErrShortWrite) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, io.ErrShortWrite)
|
||||
<-buff.Finished
|
||||
if contents := buff.Read(); !strings.Contains(contents, payload) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, payload)
|
||||
}
|
||||
|
||||
mw, err = multiWriter(&WriteError{}, io.Discard)
|
||||
fields.output, err = multiWriter(&WriteShorter{}, io.Discard)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = mw.Write([]byte(payload))
|
||||
if !errors.Is(err, errWriteError) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWriteError)
|
||||
fields.output.StageLogEvent(func() string { return payload }, "", "", "", "", false, false) // Will display error: Logger write error: *log.WriteShorter short write
|
||||
|
||||
fields.output, err = multiWriter(&WriteError{}, io.Discard)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fields.output.StageLogEvent(func() string { return payload }, "", "", "", "", false, false) // Will display error: Logger write error: *log.WriteError write error
|
||||
}
|
||||
|
||||
func TestGetWriters(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := getWriters(nil)
|
||||
err := getWritersProtected(nil)
|
||||
if !errors.Is(err, errSubloggerConfigIsNil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errSubloggerConfigIsNil)
|
||||
}
|
||||
|
||||
outputWriters := "stDout|stderr|filE"
|
||||
|
||||
mu.Lock()
|
||||
fileLoggingConfiguredCorrectly = false
|
||||
_, err = getWriters(&SubLoggerConfig{Output: outputWriters})
|
||||
if !errors.Is(err, errFileLoggingNotConfiguredCorrectly) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errFileLoggingNotConfiguredCorrectly)
|
||||
}
|
||||
fileLoggingConfiguredCorrectly = true
|
||||
_, err = getWriters(&SubLoggerConfig{Output: outputWriters})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
outputWriters = "stdout|stderr|file|noobs"
|
||||
_, err = getWriters(&SubLoggerConfig{Output: outputWriters})
|
||||
outputWriters = "stdout|stderr|noobs"
|
||||
err = getWritersProtected(&SubLoggerConfig{Output: outputWriters})
|
||||
if !errors.Is(err, errUnhandledOutputWriter) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errUnhandledOutputWriter)
|
||||
}
|
||||
}
|
||||
|
||||
func getWritersProtected(s *SubLoggerConfig) error {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
_, err := getWriters(s)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestGenDefaultSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
if cfg := GenDefaultSettings(); cfg.Enabled == nil {
|
||||
@@ -263,21 +305,16 @@ func TestSetLevel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := CloseLogger(); err != nil {
|
||||
t.Errorf("CloseLogger() failed %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureSubLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := configureSubLogger("LOG", "INFO", os.Stdin)
|
||||
mw := &multiWriterHolder{writers: []io.Writer{newTestBuffer()}}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
err := configureSubLogger("LOG", "INFO", mw)
|
||||
if err != nil {
|
||||
t.Skipf("configureSubLogger() returned unexpected error %v", err)
|
||||
}
|
||||
levels := Global.GetLevels()
|
||||
if (levels != Levels{Info: true, Debug: false}) {
|
||||
if (Global.levels != Levels{Info: true}) {
|
||||
t.Error("configureSubLogger() incorrectly configure subLogger")
|
||||
}
|
||||
if Global.name != "LOG" {
|
||||
@@ -301,266 +338,281 @@ func TestSplitLevel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInfoDisabled(b *testing.B) {
|
||||
if err := SetupDisabled(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Info(Global, "Hello this is an info benchmark")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInfof(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Infof(Global, "Hello this is an infof benchmark %v %v %v\n", n, 1, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInfoln(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Infoln(Global, "Hello this is an infoln benchmark")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLogEvent(t *testing.T) {
|
||||
func TestStageNewLogEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
RWM.Lock()
|
||||
err := logger.newLogEvent("out", "header", "SUBLOGGER", w)
|
||||
RWM.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w := newTestBuffer()
|
||||
mw := &multiWriterHolder{writers: []io.Writer{w}}
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("newLogEvent() failed expected output got empty string")
|
||||
}
|
||||
fields := &logFields{output: mw}
|
||||
fields.output.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", " space ", "", false, false)
|
||||
|
||||
RWM.Lock()
|
||||
err = logger.newLogEvent("out", "header", "SUBLOGGER", nil)
|
||||
RWM.Unlock()
|
||||
if err == nil {
|
||||
t.Error("Error expected with output is set to nil")
|
||||
<-w.Finished
|
||||
if contents := w.Read(); contents != "header space space out\n" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "header space space out\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
w := newTestBuffer()
|
||||
mw := &multiWriterHolder{writers: []io.Writer{w}}
|
||||
|
||||
sl := registerNewSubLogger("TESTYMCTESTALOTINFO")
|
||||
sl.SetLevels(splitLevel("INFO|WARN|DEBUG|ERROR"))
|
||||
sl.SetOutput(w)
|
||||
|
||||
Info(sl, "Hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
sl, err := NewSubLogger("TESTYMCTESTALOTINFO")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Infof(sl, "%s", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Infoln(sl, "hello", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
_, err := SetLevel("TESTYMCTESTALOTINFO", "")
|
||||
sl.setLevelsProtected(splitLevel("INFO"))
|
||||
err = sl.setOutputProtected(mw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Info(sl, "HelloHello")
|
||||
Info(sl, "Hello")
|
||||
<-w.Finished
|
||||
contents := w.Read()
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
if !strings.Contains(contents, "Hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
|
||||
}
|
||||
|
||||
Infof(sl, "%s", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
|
||||
}
|
||||
|
||||
Infoln(sl, "hello", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello hello")
|
||||
}
|
||||
|
||||
_, err = SetLevel("TESTYMCTESTALOTINFO", "")
|
||||
if err != nil {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
// Should not write to buffer at all as it should return if functionality
|
||||
// is not enabled.
|
||||
Info(sl, "HelloHello")
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
|
||||
Infoln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebug(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
w := newTestBuffer()
|
||||
mw := &multiWriterHolder{writers: []io.Writer{w}}
|
||||
|
||||
sl := registerNewSubLogger("TESTYMCTESTALOTDEBUG")
|
||||
sl.SetLevels(splitLevel("INFO|WARN|DEBUG|ERROR"))
|
||||
sl.SetOutput(w)
|
||||
|
||||
Debug(sl, "Hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
sl, err := NewSubLogger("TESTYMCTESTALOTDEBUG")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Debugf(sl, "%s", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Debugln(sl, "hello", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
_, err := SetLevel("TESTYMCTESTALOTDEBUG", "")
|
||||
sl.setLevelsProtected(splitLevel("DEBUG"))
|
||||
err = sl.setOutputProtected(mw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Debug(sl, "HelloHello")
|
||||
Debug(sl, "Hello")
|
||||
<-w.Finished
|
||||
contents := w.Read()
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
if !strings.Contains(contents, "Hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
|
||||
}
|
||||
|
||||
Debugf(sl, "%s", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
|
||||
}
|
||||
|
||||
Debugln(sl, "hello", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello hello")
|
||||
}
|
||||
|
||||
_, err = SetLevel("TESTYMCTESTALOTDEBUG", "")
|
||||
if err != nil {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
// Should not write to buffer at all as it should return if functionality
|
||||
// is not enabled.
|
||||
Debug(sl, "HelloHello")
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
|
||||
Debugln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarn(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
w := newTestBuffer()
|
||||
mw := &multiWriterHolder{writers: []io.Writer{w}}
|
||||
|
||||
sl := registerNewSubLogger("TESTYMCTESTALOTWARN")
|
||||
sl.SetLevels(splitLevel("INFO|WARN|DEBUG|ERROR"))
|
||||
sl.SetOutput(w)
|
||||
|
||||
Warn(sl, "Hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
sl, err := NewSubLogger("TESTYMCTESTALOTWARN")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Warnf(sl, "%s", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Warnln(sl, "hello", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
_, err := SetLevel("TESTYMCTESTALOTWARN", "")
|
||||
sl.setLevelsProtected(splitLevel("WARN"))
|
||||
err = sl.setOutputProtected(mw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Warn(sl, "HelloHello")
|
||||
Warn(sl, "Hello")
|
||||
<-w.Finished
|
||||
contents := w.Read()
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
if !strings.Contains(contents, "Hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
|
||||
}
|
||||
|
||||
Warnf(sl, "%s", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
|
||||
}
|
||||
|
||||
Warnln(sl, "hello", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello hello")
|
||||
}
|
||||
|
||||
_, err = SetLevel("TESTYMCTESTALOTWARN", "")
|
||||
if err != nil {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
// Should not write to buffer at all as it shhould return if functionality
|
||||
// is not enabled.
|
||||
Warn(sl, "HelloHello")
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
|
||||
Warnln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
w := newTestBuffer()
|
||||
mw := &multiWriterHolder{writers: []io.Writer{w}}
|
||||
|
||||
sl := registerNewSubLogger("TESTYMCTESTALOTERROR")
|
||||
sl.SetLevels(splitLevel("INFO|WARN|DEBUG|ERROR"))
|
||||
sl.SetOutput(w)
|
||||
|
||||
Error(sl, "Hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
sl, err := NewSubLogger("TESTYMCTESTALOTERROR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Errorf(sl, "%s", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
sl.setLevelsProtected(splitLevel("ERROR"))
|
||||
err = sl.setOutputProtected(nil)
|
||||
if !errors.Is(err, errMultiWriterHolderIsNil) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", err, errMultiWriterHolderIsNil)
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Errorln(sl, "hello", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
_, err := SetLevel("TESTYMCTESTALOTERROR", "")
|
||||
err = sl.setOutputProtected(mw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Error(sl, "HelloHello")
|
||||
Error(sl, "Hello")
|
||||
<-w.Finished
|
||||
contents := w.Read()
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
if !strings.Contains(contents, "Hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
|
||||
}
|
||||
|
||||
Errorf(sl, "%s", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
|
||||
}
|
||||
|
||||
Errorln(sl, "hello", "hello")
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if !strings.Contains(contents, "hello hello") {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "hello hello")
|
||||
}
|
||||
|
||||
_, err = SetLevel("TESTYMCTESTALOTERROR", "")
|
||||
if err != nil {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
// Should not write to buffer at all as it shhould return if functionality
|
||||
// is not enabled.
|
||||
Error(sl, "HelloHello")
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
|
||||
Errorln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
contents = w.Read()
|
||||
if contents != "" {
|
||||
t.Errorf("received: '%v' but expected: '%v'", contents, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *SubLogger) setLevelsProtected(newLevels Levels) {
|
||||
mu.Lock()
|
||||
sl.setLevels(newLevels)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func (sl *SubLogger) setOutputProtected(o *multiWriterHolder) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return sl.setOutput(o)
|
||||
}
|
||||
|
||||
func TestSubLoggerName(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
registerNewSubLogger("sublogger")
|
||||
RWM.Lock()
|
||||
err := logger.newLogEvent("out", "header", "SUBLOGGER", w)
|
||||
RWM.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(w.String(), "SUBLOGGER") {
|
||||
w := newTestBuffer()
|
||||
mw := &multiWriterHolder{writers: []io.Writer{w}}
|
||||
|
||||
mw.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", "||", time.RFC3339, true, false)
|
||||
<-w.Finished
|
||||
contents := w.Read()
|
||||
if !strings.Contains(contents, "SUBLOGGER") {
|
||||
t.Error("Expected SUBLOGGER in output")
|
||||
}
|
||||
|
||||
RWM.Lock()
|
||||
logger.ShowLogSystemName = false
|
||||
RWM.Unlock()
|
||||
w.Reset()
|
||||
RWM.Lock()
|
||||
err = logger.newLogEvent("out", "header", "SUBLOGGER", w)
|
||||
RWM.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(w.String(), "SUBLOGGER") {
|
||||
mw.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", "||", time.RFC3339, false, false)
|
||||
<-w.Finished
|
||||
contents = w.Read()
|
||||
if strings.Contains(contents, "SUBLOGGER") {
|
||||
t.Error("Unexpected SUBLOGGER in output")
|
||||
}
|
||||
}
|
||||
@@ -585,14 +637,6 @@ func TestNewSubLogger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewLogEvent(b *testing.B) {
|
||||
var bro bytes.Buffer
|
||||
l := Logger{Spacer: " "}
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = l.newLogEvent("somedata", "header", "sublog", &bro)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRotateWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
empty := Rotate{Rotate: convert.BoolPtr(true), FileName: "test.txt"}
|
||||
@@ -622,6 +666,11 @@ func TestRotateWrite(t *testing.T) {
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
err = empty.Close()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenNew(t *testing.T) {
|
||||
@@ -637,4 +686,73 @@ func TestOpenNew(t *testing.T) {
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
err = empty.Close()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type testBuffer struct {
|
||||
value string
|
||||
Finished chan struct{}
|
||||
}
|
||||
|
||||
func (tb *testBuffer) Write(p []byte) (int, error) {
|
||||
tb.value = string(p)
|
||||
tb.Finished <- struct{}{}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (tb *testBuffer) Read() string {
|
||||
defer func() { tb.value = "" }()
|
||||
return tb.value
|
||||
}
|
||||
|
||||
func newTestBuffer() *testBuffer {
|
||||
return &testBuffer{Finished: make(chan struct{}, 1)}
|
||||
}
|
||||
|
||||
// 2140294 770.0 ns/op 0 B/op 0 allocs/op
|
||||
func BenchmarkNewLogEvent(b *testing.B) {
|
||||
mw := &multiWriterHolder{writers: []io.Writer{io.Discard}}
|
||||
for i := 0; i < b.N; i++ {
|
||||
mw.StageLogEvent(func() string { return "somedata" }, "header", "sublog", "||", time.RFC3339, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkInfo-8 1000000 64971 ns/op 47 B/op 1 allocs/op
|
||||
func BenchmarkInfo(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Info(Global, "Hello this is an info benchmark")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkInfoDisabled-8 47124242 24.16 ns/op 0 B/op 0 allocs/op
|
||||
func BenchmarkInfoDisabled(b *testing.B) {
|
||||
if err := SetupDisabled(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Info(Global, "Hello this is an info benchmark")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkInfof-8 1000000 72641 ns/op 178 B/op 4 allocs/op
|
||||
func BenchmarkInfof(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Infof(Global, "Hello this is an infof benchmark %v %v %v\n", n, 1, 2)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkInfoln-8 1000000 68152 ns/op 121 B/op 3 allocs/op
|
||||
func BenchmarkInfoln(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Infoln(Global, "Hello this is an infoln benchmark")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,32 +11,46 @@ const (
|
||||
// DefaultMaxFileSize for logger rotation file
|
||||
DefaultMaxFileSize int64 = 100
|
||||
|
||||
defaultCapacityForSliceOfBytes = 100
|
||||
// defaultBufferCapacity has 200kb of memory per buffer, there has been some
|
||||
// instances where it was 3/4 of this. This size so as to not need a resize.
|
||||
defaultBufferCapacity = 200000
|
||||
defaultJobChannelCapacity = 10000
|
||||
)
|
||||
|
||||
var (
|
||||
logger = Logger{}
|
||||
// FileLoggingConfiguredCorrectly flag set during config check if file logging meets requirements
|
||||
FileLoggingConfiguredCorrectly bool
|
||||
fileLoggingConfiguredCorrectly bool
|
||||
// GlobalLogConfig holds global configuration options for logger
|
||||
GlobalLogConfig = &Config{}
|
||||
globalLogConfig = &Config{}
|
||||
// GlobalLogFile hold global configuration options for file logger
|
||||
GlobalLogFile = &Rotate{}
|
||||
globalLogFile = &Rotate{}
|
||||
|
||||
eventPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
sliceOBytes := make([]byte, 0, defaultCapacityForSliceOfBytes)
|
||||
return &sliceOBytes
|
||||
},
|
||||
}
|
||||
jobsPool = &sync.Pool{New: func() interface{} { return new(job) }}
|
||||
jobsChannel = make(chan *job, defaultJobChannelCapacity)
|
||||
|
||||
// Note: Logger state within logFields will be persistent until it's garbage
|
||||
// collected. This is a little bit more efficient.
|
||||
logFieldsPool = &sync.Pool{New: func() interface{} { return &logFields{logger: logger} }}
|
||||
|
||||
// LogPath system path to store log files in
|
||||
LogPath string
|
||||
logPath string
|
||||
|
||||
// RWM read/write mutex for logger
|
||||
RWM = &sync.RWMutex{}
|
||||
// read/write mutex for logger
|
||||
mu = &sync.RWMutex{}
|
||||
)
|
||||
|
||||
type job struct {
|
||||
Writers []io.Writer
|
||||
fn deferral
|
||||
Header string
|
||||
SlName string
|
||||
Spacer string
|
||||
TimestampFormat string
|
||||
ShowLogSystemName bool
|
||||
Passback chan<- struct{}
|
||||
}
|
||||
|
||||
// Config holds configuration settings loaded from bot config
|
||||
type Config struct {
|
||||
Enabled *bool `json:"enabled"`
|
||||
@@ -47,10 +61,11 @@ type Config struct {
|
||||
}
|
||||
|
||||
type advancedSettings struct {
|
||||
ShowLogSystemName *bool `json:"showLogSystemName"`
|
||||
Spacer string `json:"spacer"`
|
||||
TimeStampFormat string `json:"timeStampFormat"`
|
||||
Headers headers `json:"headers"`
|
||||
ShowLogSystemName *bool `json:"showLogSystemName"`
|
||||
Spacer string `json:"spacer"`
|
||||
TimeStampFormat string `json:"timeStampFormat"`
|
||||
Headers headers `json:"headers"`
|
||||
BypassJobChannelFilledWarning bool `json:"bypassJobChannelFilledWarning"`
|
||||
}
|
||||
|
||||
type headers struct {
|
||||
@@ -76,7 +91,8 @@ type loggerFileConfig struct {
|
||||
// Logger each instance of logger settings
|
||||
type Logger struct {
|
||||
ShowLogSystemName bool
|
||||
Timestamp string
|
||||
BypassJobChannelFilledWarning bool
|
||||
TimestampFormat string
|
||||
InfoHeader, ErrorHeader, DebugHeader, WarnHeader string
|
||||
Spacer string
|
||||
}
|
||||
@@ -88,5 +104,4 @@ type Levels struct {
|
||||
|
||||
type multiWriterHolder struct {
|
||||
writers []io.Writer
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
238
log/loggers.go
238
log/loggers.go
@@ -5,122 +5,244 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// Info takes a pointer subLogger struct and string sends to newLogEvent
|
||||
// Info takes a pointer subLogger struct and string sends to StageLogEvent
|
||||
func Info(sl *SubLogger, data string) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.info {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(data,
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.info {
|
||||
fields.output.StageLogEvent(func() string { return data },
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Infoln takes a pointer subLogger struct and interface sends to newLogEvent
|
||||
// Infoln takes a pointer subLogger struct and interface sends to StageLogEvent
|
||||
func Infoln(sl *SubLogger, v ...interface{}) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.info {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.info {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintln(v...) },
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Infof takes a pointer subLogger struct, string & interface formats and sends to Info()
|
||||
// Infof takes a pointer subLogger struct, string and interface formats sends to StageLogEvent
|
||||
func Infof(sl *SubLogger, data string, v ...interface{}) {
|
||||
Info(sl, fmt.Sprintf(data, v...))
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if fields.info {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintf(data, v...) },
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Debug takes a pointer subLogger struct and string sends to multiwriter
|
||||
// Debug takes a pointer subLogger struct and string sends to StageLogEvent
|
||||
func Debug(sl *SubLogger, data string) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.debug {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
displayError(fields.logger.newLogEvent(data,
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.debug {
|
||||
fields.output.StageLogEvent(func() string { return data },
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Debugln takes a pointer subLogger struct, string and interface sends to newLogEvent
|
||||
// Debugln takes a pointer subLogger struct, string and interface sends to StageLogEvent
|
||||
func Debugln(sl *SubLogger, v ...interface{}) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.debug {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.debug {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintln(v...) },
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Debugf takes a pointer subLogger struct, string & interface formats and sends to Info()
|
||||
// Debugf takes a pointer subLogger struct, string and interface formats sends to StageLogEvent
|
||||
func Debugf(sl *SubLogger, data string, v ...interface{}) {
|
||||
Debug(sl, fmt.Sprintf(data, v...))
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if fields.debug {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintf(data, v...) },
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Warn takes a pointer subLogger struct & string and sends to newLogEvent()
|
||||
// Warn takes a pointer subLogger struct & string and sends to StageLogEvent
|
||||
func Warn(sl *SubLogger, data string) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.warn {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
displayError(fields.logger.newLogEvent(data,
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.warn {
|
||||
fields.output.StageLogEvent(func() string { return data },
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Warnln takes a pointer subLogger struct & interface formats and sends to newLogEvent()
|
||||
// Warnln takes a pointer subLogger struct & interface formats and sends to StageLogEvent
|
||||
func Warnln(sl *SubLogger, v ...interface{}) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.warn {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.warn {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintln(v...) },
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Warnf takes a pointer subLogger struct, string & interface formats and sends to Warn()
|
||||
// Warnf takes a pointer subLogger struct, string and interface formats sends to StageLogEvent
|
||||
func Warnf(sl *SubLogger, data string, v ...interface{}) {
|
||||
Warn(sl, fmt.Sprintf(data, v...))
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if fields.warn {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintf(data, v...) },
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Error takes a pointer subLogger struct & interface formats and sends to newLogEvent()
|
||||
// Error takes a pointer subLogger struct & interface formats and sends to StageLogEvent
|
||||
func Error(sl *SubLogger, data ...interface{}) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.error {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprint(data...),
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.error {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprint(data...) },
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Errorln takes a pointer subLogger struct, string & interface formats and sends to newLogEvent()
|
||||
// Errorln takes a pointer subLogger struct, string & interface formats and sends to StageLogEvent
|
||||
func Errorln(sl *SubLogger, v ...interface{}) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.error {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
if fields.error {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintln(v...) },
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
// Errorf takes a pointer subLogger struct, string & interface formats and sends to Debug()
|
||||
// Errorf takes a pointer subLogger struct, string and interface formats sends to StageLogEvent
|
||||
func Errorf(sl *SubLogger, data string, v ...interface{}) {
|
||||
Error(sl, fmt.Sprintf(data, v...))
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
fields := sl.getFields()
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if fields.error {
|
||||
fields.output.StageLogEvent(func() string { return fmt.Sprintf(data, v...) },
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.logger.Spacer,
|
||||
fields.logger.TimestampFormat,
|
||||
fields.logger.ShowLogSystemName,
|
||||
fields.logger.BypassJobChannelFilledWarning)
|
||||
}
|
||||
logFieldsPool.Put(fields)
|
||||
}
|
||||
|
||||
func displayError(err error) {
|
||||
|
||||
@@ -1,67 +1,54 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errMultiWriterHolderIsNil = errors.New("multiwriter holder is nil")
|
||||
|
||||
// NewSubLogger allows for a new sub logger to be registered.
|
||||
func NewSubLogger(name string) (*SubLogger, error) {
|
||||
if name == "" {
|
||||
return nil, errEmptyLoggerName
|
||||
}
|
||||
name = strings.ToUpper(name)
|
||||
RWM.RLock()
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if _, ok := SubLoggers[name]; ok {
|
||||
RWM.RUnlock()
|
||||
return nil, fmt.Errorf("'%v' %w", name, ErrSubLoggerAlreadyRegistered)
|
||||
}
|
||||
RWM.RUnlock()
|
||||
return registerNewSubLogger(name), nil
|
||||
}
|
||||
|
||||
// SetOutput overrides the default output with a new writer
|
||||
func (sl *SubLogger) SetOutput(o io.Writer) {
|
||||
sl.mtx.Lock()
|
||||
func (sl *SubLogger) setOutput(o *multiWriterHolder) error {
|
||||
if o == nil {
|
||||
return errMultiWriterHolderIsNil
|
||||
}
|
||||
sl.output = o
|
||||
sl.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLevels overrides the default levels with new levels; levelception
|
||||
func (sl *SubLogger) SetLevels(newLevels Levels) {
|
||||
sl.mtx.Lock()
|
||||
func (sl *SubLogger) setLevels(newLevels Levels) {
|
||||
sl.levels = newLevels
|
||||
sl.mtx.Unlock()
|
||||
}
|
||||
|
||||
// GetLevels returns current functional log levels
|
||||
func (sl *SubLogger) GetLevels() Levels {
|
||||
sl.mtx.RLock()
|
||||
defer sl.mtx.RUnlock()
|
||||
return sl.levels
|
||||
}
|
||||
|
||||
// getFields returns sub logger specific fields for the potential log job.
|
||||
// Note: Calling function must have mutex lock in place.
|
||||
func (sl *SubLogger) getFields() *logFields {
|
||||
RWM.RLock()
|
||||
defer RWM.RUnlock()
|
||||
|
||||
if sl == nil ||
|
||||
(GlobalLogConfig != nil &&
|
||||
GlobalLogConfig.Enabled != nil &&
|
||||
!*GlobalLogConfig.Enabled) {
|
||||
if sl == nil || globalLogConfig == nil || globalLogConfig.Enabled == nil || !*globalLogConfig.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
sl.mtx.RLock()
|
||||
defer sl.mtx.RUnlock()
|
||||
return &logFields{
|
||||
info: sl.levels.Info,
|
||||
warn: sl.levels.Warn,
|
||||
debug: sl.levels.Debug,
|
||||
error: sl.levels.Error,
|
||||
name: sl.name,
|
||||
output: sl.output,
|
||||
logger: logger,
|
||||
}
|
||||
fields := logFieldsPool.Get().(*logFields) //nolint:forcetypeassert // Not necessary from a pool
|
||||
fields.info = sl.levels.Info
|
||||
fields.warn = sl.levels.Warn
|
||||
fields.debug = sl.levels.Debug
|
||||
fields.error = sl.levels.Error
|
||||
fields.name = sl.name
|
||||
fields.output = sl.output
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Global vars related to the logger package
|
||||
var (
|
||||
SubLoggers = map[string]*SubLogger{}
|
||||
@@ -42,8 +37,7 @@ var (
|
||||
type SubLogger struct {
|
||||
name string
|
||||
levels Levels
|
||||
output io.Writer
|
||||
mtx sync.RWMutex
|
||||
output *multiWriterHolder
|
||||
}
|
||||
|
||||
// logFields is used to store data in a non-global and thread-safe manner
|
||||
@@ -54,6 +48,6 @@ type logFields struct {
|
||||
debug bool
|
||||
error bool
|
||||
name string
|
||||
output io.Writer
|
||||
output *multiWriterHolder
|
||||
logger Logger
|
||||
}
|
||||
|
||||
17
main.go
17
main.go
@@ -6,7 +6,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
@@ -37,7 +36,7 @@ func main() {
|
||||
flag.BoolVar(&settings.EnableAllPairs, "enableallpairs", false, "enables all pairs for enabled exchanges")
|
||||
flag.BoolVar(&settings.EnablePortfolioManager, "portfoliomanager", true, "enables the portfolio manager")
|
||||
flag.BoolVar(&settings.EnableDataHistoryManager, "datahistorymanager", false, "enables the data history manager")
|
||||
flag.DurationVar(&settings.PortfolioManagerDelay, "portfoliomanagerdelay", time.Duration(0), "sets the portfolio managers sleep delay between updates")
|
||||
flag.DurationVar(&settings.PortfolioManagerDelay, "portfoliomanagerdelay", 0, "sets the portfolio managers sleep delay between updates")
|
||||
flag.BoolVar(&settings.EnableGRPC, "grpc", true, "enables the grpc server")
|
||||
flag.BoolVar(&settings.EnableGRPCProxy, "grpcproxy", false, "enables the grpc proxy server")
|
||||
flag.BoolVar(&settings.EnableGRPCShutdown, "grpcshutdown", false, "enables gRPC bot instance shutdown functionality")
|
||||
@@ -55,7 +54,7 @@ func main() {
|
||||
flag.BoolVar(&settings.EnableConnectivityMonitor, "connectivitymonitor", true, "enables the connectivity monitor")
|
||||
flag.BoolVar(&settings.EnableDatabaseManager, "databasemanager", true, "enables database manager")
|
||||
flag.BoolVar(&settings.EnableGCTScriptManager, "gctscriptmanager", true, "enables gctscript manager")
|
||||
flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking")
|
||||
flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", 0, "sets the event managers sleep delay between event checking")
|
||||
flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift")
|
||||
flag.BoolVar(&settings.EnableDispatcher, "dispatch", true, "enables the dispatch system")
|
||||
flag.BoolVar(&settings.EnableCurrencyStateManager, "currencystatemanager", true, "enables the currency state manager")
|
||||
@@ -91,7 +90,7 @@ func main() {
|
||||
flag.BoolVar(&settings.EnableExchangeHTTPRateLimiter, "ratelimiter", true, "enables the rate limiter for HTTP requests")
|
||||
flag.IntVar(&settings.MaxHTTPRequestJobsLimit, "requestjobslimit", int(request.DefaultMaxRequestJobs), "sets the max amount of jobs the HTTP request package stores")
|
||||
flag.IntVar(&settings.RequestMaxRetryAttempts, "httpmaxretryattempts", request.DefaultMaxRetryAttempts, "sets the number of retry attempts after a retryable HTTP failure")
|
||||
flag.DurationVar(&settings.HTTPTimeout, "httptimeout", time.Duration(0), "sets the HTTP timeout value for HTTP requests")
|
||||
flag.DurationVar(&settings.HTTPTimeout, "httptimeout", 0, "sets the HTTP timeout value for HTTP requests")
|
||||
flag.StringVar(&settings.HTTPUserAgent, "httpuseragent", "", "sets the HTTP user agent")
|
||||
flag.StringVar(&settings.HTTPProxy, "httpproxy", "", "sets the HTTP proxy server")
|
||||
flag.BoolVar(&settings.EnableExchangeHTTPDebugging, "exchangehttpdebugging", false, "sets the exchanges HTTP debugging")
|
||||
@@ -99,7 +98,7 @@ func main() {
|
||||
flag.IntVar(&settings.AlertSystemPreAllocationCommsBuffer, "alertbuffer", alert.PreAllocCommsDefaultBuffer, "sets the size of the pre-allocation communications buffer")
|
||||
|
||||
// Common tuning settings
|
||||
flag.DurationVar(&settings.GlobalHTTPTimeout, "globalhttptimeout", time.Duration(0), "sets common HTTP timeout value for HTTP requests")
|
||||
flag.DurationVar(&settings.GlobalHTTPTimeout, "globalhttptimeout", 0, "sets common HTTP timeout value for HTTP requests")
|
||||
flag.StringVar(&settings.GlobalHTTPUserAgent, "globalhttpuseragent", "", "sets the common HTTP client's user agent")
|
||||
flag.StringVar(&settings.GlobalHTTPProxy, "globalhttpproxy", "", "sets the common HTTP client's proxy server")
|
||||
|
||||
@@ -142,14 +141,16 @@ func main() {
|
||||
|
||||
engine.PrintSettings(&engine.Bot.Settings)
|
||||
if err = engine.Bot.Start(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Unable to start bot engine. Error: %s\n", err)
|
||||
os.Exit(1)
|
||||
errClose := gctlog.CloseLogger()
|
||||
if errClose != nil {
|
||||
log.Printf("Unable to close logger. Error: %s\n", errClose)
|
||||
}
|
||||
log.Fatalf("Unable to start bot engine. Error: %s\n", err)
|
||||
}
|
||||
|
||||
go waitForInterupt(settings.Shutdown)
|
||||
<-settings.Shutdown
|
||||
engine.Bot.Stop()
|
||||
gctlog.Infoln(gctlog.Global, "Exiting.")
|
||||
}
|
||||
|
||||
func waitForInterupt(waiter chan<- struct{}) {
|
||||
|
||||
Reference in New Issue
Block a user