mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
log: fix bug, expand test coverage and slightly optimize (#847)
* log: fix bugs expand coverage and optimise * log: fix linter issues * log: fix linter issue and pack methods in same file * log: drop defer * logger: move global check inside getfields and remove unused test function * logger: Increase note thanks @gloriouscode * logger: wrap error with writer type * logger: change variable name * logger: change variable names and remove validsublogger func as it doesn't add functionality over a standard map call * logs: error when unsupported output is applied on setup calls * logs: add glorious suggestion * logger: add protection to reduce olympic gold medal races * logger: fix linter issues * log: glorious niterinos
This commit is contained in:
@@ -30,7 +30,11 @@ const (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
testhelpers.PostgresTestDatabase = testhelpers.GetConnectionDetails()
|
||||
|
||||
@@ -64,9 +64,12 @@ func main() {
|
||||
|
||||
var bt *backtest.BackTest
|
||||
var cfg *config.Config
|
||||
logConfig := log.GenDefaultSettings()
|
||||
log.GlobalLogConfig = &logConfig
|
||||
log.SetupGlobalLogger()
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
err = log.SetupGlobalLogger()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg, err = config.ReadConfigFromFile(configPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -101,11 +101,14 @@ func main() {
|
||||
flag.BoolVar(&create, "create", false, "specifies whether to automatically create trello list, card and checklist in a given board")
|
||||
flag.Parse()
|
||||
var err error
|
||||
c := log.GenDefaultSettings()
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = &c
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.RWM.Unlock()
|
||||
log.SetupGlobalLogger()
|
||||
err = log.SetupGlobalLogger()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
configData, err = readFileData(jsonFile)
|
||||
if err != nil {
|
||||
log.Error(log.Global, err)
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestMain(m *testing.M) {
|
||||
c := log.GenDefaultSettings()
|
||||
c.Enabled = convert.BoolPtr(true)
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = &c
|
||||
log.GlobalLogConfig = c
|
||||
log.RWM.Unlock()
|
||||
log.Infoln(log.Global, "set verbose to true for more detailed output")
|
||||
var err error
|
||||
|
||||
@@ -1267,7 +1267,7 @@ func (c *Config) CheckLoggerConfig() error {
|
||||
defer m.Unlock()
|
||||
|
||||
if c.Logging.Enabled == nil || c.Logging.Output == "" {
|
||||
c.Logging = log.GenDefaultSettings()
|
||||
c.Logging = *log.GenDefaultSettings()
|
||||
}
|
||||
|
||||
if c.Logging.AdvancedSettings.ShowLogSystemName == nil {
|
||||
|
||||
@@ -30,7 +30,11 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
@@ -33,7 +33,11 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
testhelpers.PostgresTestDatabase = testhelpers.GetConnectionDetails()
|
||||
|
||||
@@ -30,7 +30,11 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
testhelpers.PostgresTestDatabase = testhelpers.GetConnectionDetails()
|
||||
|
||||
@@ -30,7 +30,11 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
@@ -28,7 +28,11 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err = testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
t := m.Run()
|
||||
|
||||
@@ -33,7 +33,11 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
testhelpers.PostgresTestDatabase = testhelpers.GetConnectionDetails()
|
||||
|
||||
@@ -29,7 +29,11 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
@@ -118,14 +118,15 @@ func migrateDB(db *sql.DB) error {
|
||||
}
|
||||
|
||||
// EnableVerboseTestOutput enables debug output for SQL queries
|
||||
func EnableVerboseTestOutput() {
|
||||
c := log.GenDefaultSettings()
|
||||
func EnableVerboseTestOutput() error {
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = &c
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.RWM.Unlock()
|
||||
log.SetupGlobalLogger()
|
||||
|
||||
if err := log.SetupGlobalLogger(); err != nil {
|
||||
return err
|
||||
}
|
||||
DBLogger := database.Logger{}
|
||||
boil.DebugMode = true
|
||||
boil.DebugWriter = DBLogger
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -84,12 +84,18 @@ func NewFromSettings(settings *Settings, flagSet map[string]bool) (*Engine, erro
|
||||
|
||||
b.Config, err = loadConfigWithSettings(settings, flagSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load config. Err: %s", err)
|
||||
return nil, fmt.Errorf("failed to load config. Err: %w", err)
|
||||
}
|
||||
|
||||
if *b.Config.Logging.Enabled {
|
||||
gctlog.SetupGlobalLogger()
|
||||
gctlog.SetupSubLoggers(b.Config.Logging.SubLoggers)
|
||||
err = gctlog.SetupGlobalLogger()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to setup global logger. %w", err)
|
||||
}
|
||||
err = gctlog.SetupSubLoggers(b.Config.Logging.SubLoggers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to setup sub loggers. %w", err)
|
||||
}
|
||||
gctlog.Infoln(gctlog.Global, "Logger initialised.")
|
||||
}
|
||||
|
||||
@@ -99,12 +105,12 @@ func NewFromSettings(settings *Settings, flagSet map[string]bool) (*Engine, erro
|
||||
|
||||
err = utils.AdjustGoMaxProcs(settings.GoMaxProcs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to adjust runtime GOMAXPROCS value. Err: %s", err)
|
||||
return nil, fmt.Errorf("unable to adjust runtime GOMAXPROCS value. Err: %w", err)
|
||||
}
|
||||
|
||||
b.gctScriptManager, err = gctscript.NewManager(&b.Config.GCTScript)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create script manager. Err: %s", err)
|
||||
return nil, fmt.Errorf("failed to create script manager. Err: %w", err)
|
||||
}
|
||||
|
||||
b.ExchangeManager = SetupExchangeManager()
|
||||
|
||||
@@ -3,6 +3,7 @@ package exchange
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -29,11 +30,13 @@ const (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := log.GenDefaultSettings()
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = &c
|
||||
log.GlobalLogConfig = log.GenDefaultSettings()
|
||||
log.RWM.Unlock()
|
||||
log.SetupGlobalLogger()
|
||||
if err := log.SetupGlobalLogger(); err != nil {
|
||||
fmt.Println("Cannot setup global logger. Error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package kline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
@@ -511,7 +512,11 @@ func TestItem_SortCandlesByTimestamp(t *testing.T) {
|
||||
func setupTest(t *testing.T) {
|
||||
t.Helper()
|
||||
if verbose {
|
||||
testhelpers.EnableVerboseTestOutput()
|
||||
err := testhelpers.EnableVerboseTestOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to enable verbose test output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestMain(m *testing.M) {
|
||||
c := log.GenDefaultSettings()
|
||||
c.Enabled = convert.BoolPtr(false)
|
||||
log.RWM.Lock()
|
||||
log.GlobalLogConfig = &c
|
||||
log.GlobalLogConfig = c
|
||||
log.RWM.Unlock()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -13,28 +12,8 @@ var (
|
||||
errSubLoggerAlreadyregistered = errors.New("sub logger already registered")
|
||||
)
|
||||
|
||||
// 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)
|
||||
if _, ok := SubLoggers[name]; ok {
|
||||
return nil, errSubLoggerAlreadyregistered
|
||||
}
|
||||
return registerNewSubLogger(name), nil
|
||||
}
|
||||
|
||||
// SetOutput overrides the default output with a new writer
|
||||
func (s *SubLogger) SetOutput(o io.Writer) {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
|
||||
s.output = o
|
||||
}
|
||||
|
||||
func newLogger(c *Config) *Logger {
|
||||
return &Logger{
|
||||
func newLogger(c *Config) Logger {
|
||||
return Logger{
|
||||
Timestamp: c.AdvancedSettings.TimeStampFormat,
|
||||
Spacer: c.AdvancedSettings.Spacer,
|
||||
ErrorHeader: c.AdvancedSettings.Headers.Error,
|
||||
@@ -50,29 +29,28 @@ func (l *Logger) newLogEvent(data, header, slName string, w io.Writer) error {
|
||||
return errors.New("io.Writer not set")
|
||||
}
|
||||
|
||||
e, ok := eventPool.Get().(*Event)
|
||||
pool, ok := eventPool.Get().(*[]byte)
|
||||
if !ok {
|
||||
return errors.New("unable to type asset event")
|
||||
return errors.New("unable to type assert slice of bytes pointer")
|
||||
}
|
||||
e.output = w
|
||||
e.data = append(e.data, []byte(header)...)
|
||||
if l.ShowLogSystemName {
|
||||
e.data = append(e.data, l.Spacer...)
|
||||
e.data = append(e.data, slName...)
|
||||
}
|
||||
e.data = append(e.data, l.Spacer...)
|
||||
if l.Timestamp != "" {
|
||||
e.data = time.Now().AppendFormat(e.data, l.Timestamp)
|
||||
}
|
||||
e.data = append(e.data, l.Spacer...)
|
||||
e.data = append(e.data, []byte(data)...)
|
||||
if data == "" || data[len(data)-1] != '\n' {
|
||||
e.data = append(e.data, '\n')
|
||||
}
|
||||
_, err := e.output.Write(e.data)
|
||||
|
||||
e.data = e.data[:0]
|
||||
eventPool.Put(e)
|
||||
*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
|
||||
}
|
||||
@@ -82,30 +60,25 @@ func CloseLogger() error {
|
||||
return GlobalLogFile.Close()
|
||||
}
|
||||
|
||||
func validSubLogger(s string) (bool, *SubLogger) {
|
||||
if v, found := SubLoggers[s]; found {
|
||||
return true, v
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Level retries the current sublogger levels
|
||||
func Level(s string) (*Levels, error) {
|
||||
found, logger := validSubLogger(s)
|
||||
func Level(name string) (Levels, error) {
|
||||
RWM.RLock()
|
||||
defer RWM.RUnlock()
|
||||
subLogger, found := SubLoggers[name]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("logger %v not found", s)
|
||||
return Levels{}, fmt.Errorf("logger %s not found", name)
|
||||
}
|
||||
|
||||
return &logger.Levels, nil
|
||||
return subLogger.levels, nil
|
||||
}
|
||||
|
||||
// SetLevel sets sublogger levels
|
||||
func SetLevel(s, level string) (*Levels, error) {
|
||||
found, logger := validSubLogger(s)
|
||||
func SetLevel(s, level string) (Levels, error) {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
subLogger, found := SubLoggers[s]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("logger %v not found", s)
|
||||
return Levels{}, fmt.Errorf("sub logger %v not found", s)
|
||||
}
|
||||
logger.Levels = splitLevel(level)
|
||||
|
||||
return &logger.Levels, nil
|
||||
subLogger.SetLevels(splitLevel(level))
|
||||
return subLogger.levels, nil
|
||||
}
|
||||
|
||||
@@ -1,63 +1,75 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
errWriterAlreadyLoaded = errors.New("io.Writer already loaded")
|
||||
errWriterNotFound = errors.New("io.Writer not found")
|
||||
)
|
||||
|
||||
// Add appends a new writer to the multiwriter slice
|
||||
func (mw *multiWriter) Add(writer io.Writer) {
|
||||
func (mw *multiWriter) Add(writer io.Writer) error {
|
||||
mw.mu.Lock()
|
||||
defer mw.mu.Unlock()
|
||||
for i := range mw.writers {
|
||||
if mw.writers[i] == writer {
|
||||
return errWriterAlreadyLoaded
|
||||
}
|
||||
}
|
||||
mw.writers = append(mw.writers, writer)
|
||||
mw.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes existing writer from multiwriter slice
|
||||
func (mw *multiWriter) Remove(writer io.Writer) {
|
||||
func (mw *multiWriter) Remove(writer io.Writer) error {
|
||||
mw.mu.Lock()
|
||||
|
||||
var removeIDs []int
|
||||
defer mw.mu.Unlock()
|
||||
for i := range mw.writers {
|
||||
if mw.writers[i] == writer {
|
||||
removeIDs = append(removeIDs, i)
|
||||
if mw.writers[i] != writer {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for x := range removeIDs {
|
||||
mw.writers[x] = mw.writers[len(mw.writers)-1]
|
||||
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
|
||||
}
|
||||
|
||||
mw.mu.Unlock()
|
||||
return errWriterNotFound
|
||||
}
|
||||
|
||||
// Write concurrent safe Write for each writer
|
||||
func (mw *multiWriter) Write(p []byte) (n int, err error) {
|
||||
func (mw *multiWriter) Write(p []byte) (int, error) {
|
||||
type data struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
mw.mu.RLock()
|
||||
defer mw.mu.RUnlock()
|
||||
|
||||
results := make(chan data, len(mw.writers))
|
||||
|
||||
for _, wr := range mw.writers {
|
||||
go func(w io.Writer, p []byte, ch chan data) {
|
||||
n, err = w.Write(p)
|
||||
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, err}
|
||||
ch <- data{n, fmt.Errorf("%T %w", w, err)}
|
||||
return
|
||||
}
|
||||
if n != len(p) {
|
||||
ch <- data{n, io.ErrShortWrite}
|
||||
ch <- data{n, fmt.Errorf("%T %w", w, io.ErrShortWrite)}
|
||||
return
|
||||
}
|
||||
ch <- data{n, nil}
|
||||
}(wr, p, results)
|
||||
}(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
|
||||
@@ -67,8 +79,13 @@ func (mw *multiWriter) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// MultiWriter make and return a new copy of multiWriter
|
||||
func MultiWriter(writers ...io.Writer) io.Writer {
|
||||
w := make([]io.Writer, len(writers))
|
||||
copy(w, writers)
|
||||
return &multiWriter{writers: w}
|
||||
func MultiWriter(writers ...io.Writer) (*multiWriter, error) {
|
||||
mw := &multiWriter{}
|
||||
for x := range writers {
|
||||
err := mw.Add(writers[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mw, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -9,16 +10,20 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
)
|
||||
|
||||
var (
|
||||
errExceedsMaxFileSize = errors.New("exceeds max file size")
|
||||
errFileNameIsEmpty = errors.New("filename is empty")
|
||||
)
|
||||
|
||||
// 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(
|
||||
"write length %v exceeds max file size %v", outputLen, r.maxSize(),
|
||||
"write length %v %w %v", outputLen, errExceedsMaxFileSize, r.maxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,13 +45,11 @@ func (r *Rotate) Write(output []byte) (n int, err error) {
|
||||
|
||||
n, err = r.output.Write(output)
|
||||
r.size += int64(n)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *Rotate) openOrCreateFile(n int64) error {
|
||||
logFile := filepath.Join(LogPath, r.FileName)
|
||||
|
||||
info, err := os.Stat(logFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -73,6 +76,9 @@ func (r *Rotate) openOrCreateFile(n int64) error {
|
||||
}
|
||||
|
||||
func (r *Rotate) openNew() error {
|
||||
if r.FileName == "" {
|
||||
return fmt.Errorf("cannot open new file: %w", errFileNameIsEmpty)
|
||||
}
|
||||
name := filepath.Join(LogPath, r.FileName)
|
||||
_, err := os.Stat(name)
|
||||
|
||||
@@ -93,7 +99,6 @@ func (r *Rotate) openNew() error {
|
||||
|
||||
r.output = file
|
||||
r.size = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,17 +123,12 @@ func (r *Rotate) rotateFile() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = r.openNew()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
return r.openNew()
|
||||
}
|
||||
|
||||
func (r *Rotate) maxSize() int64 {
|
||||
if r.MaxSize == 0 {
|
||||
return int64(defaultMaxSize * megabyte)
|
||||
return defaultMaxSize * megabyte
|
||||
}
|
||||
return r.MaxSize * int64(megabyte)
|
||||
return r.MaxSize * megabyte
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMaxSize = 250
|
||||
megabyte = 1024 * 1024
|
||||
defaultMaxSize int64 = 250
|
||||
megabyte int64 = 1024 * 1024
|
||||
)
|
||||
|
||||
// Rotate struct for each instance of Rotate
|
||||
|
||||
@@ -1,40 +1,50 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
)
|
||||
|
||||
func getWriters(s *SubLoggerConfig) io.Writer {
|
||||
mw := MultiWriter()
|
||||
m := mw.(*multiWriter) // nolint // type assert not required
|
||||
var (
|
||||
errSubloggerConfigIsNil = errors.New("sublogger config is nil")
|
||||
errUnhandledOutputWriter = errors.New("unhandled output writer")
|
||||
)
|
||||
|
||||
func getWriters(s *SubLoggerConfig) (io.Writer, error) {
|
||||
if s == nil {
|
||||
return nil, errSubloggerConfigIsNil
|
||||
}
|
||||
var writers []io.Writer
|
||||
outputWriters := strings.Split(s.Output, "|")
|
||||
for x := range outputWriters {
|
||||
switch outputWriters[x] {
|
||||
var writer io.Writer
|
||||
switch strings.ToLower(outputWriters[x]) {
|
||||
case "stdout", "console":
|
||||
m.Add(os.Stdout)
|
||||
writer = os.Stdout
|
||||
case "stderr":
|
||||
m.Add(os.Stderr)
|
||||
writer = os.Stderr
|
||||
case "file":
|
||||
if FileLoggingConfiguredCorrectly {
|
||||
m.Add(GlobalLogFile)
|
||||
writer = GlobalLogFile
|
||||
}
|
||||
default:
|
||||
m.Add(ioutil.Discard)
|
||||
// Note: Do not want to add a ioutil.discard here as this adds
|
||||
// additional routines for every write for no reason.
|
||||
return nil, fmt.Errorf("%w: %s", errUnhandledOutputWriter, outputWriters[x])
|
||||
}
|
||||
writers = append(writers, writer)
|
||||
}
|
||||
return m
|
||||
return MultiWriter(writers...)
|
||||
}
|
||||
|
||||
// GenDefaultSettings return struct with known sane/working logger settings
|
||||
func GenDefaultSettings() Config {
|
||||
return Config{
|
||||
func GenDefaultSettings() *Config {
|
||||
return &Config{
|
||||
Enabled: convert.BoolPtr(true),
|
||||
SubLoggerConfig: SubLoggerConfig{
|
||||
Level: "INFO|DEBUG|WARN|ERROR",
|
||||
@@ -59,34 +69,40 @@ func GenDefaultSettings() Config {
|
||||
}
|
||||
}
|
||||
|
||||
func configureSubLogger(logger, levels string, output io.Writer) error {
|
||||
found, logPtr := validSubLogger(logger)
|
||||
func configureSubLogger(subLogger, levels string, output io.Writer) error {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
logPtr, found := SubLoggers[subLogger]
|
||||
if !found {
|
||||
return fmt.Errorf("logger %v not found", logger)
|
||||
return fmt.Errorf("sub logger %v not found", subLogger)
|
||||
}
|
||||
|
||||
logPtr.output = output
|
||||
|
||||
logPtr.Levels = splitLevel(levels)
|
||||
SubLoggers[logger] = logPtr
|
||||
|
||||
logPtr.SetOutput(output)
|
||||
logPtr.SetLevels(splitLevel(levels))
|
||||
SubLoggers[subLogger] = logPtr
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupSubLoggers configure all sub loggers with provided configuration values
|
||||
func SetupSubLoggers(s []SubLoggerConfig) {
|
||||
func SetupSubLoggers(s []SubLoggerConfig) error {
|
||||
for x := range s {
|
||||
output := getWriters(&s[x])
|
||||
err := configureSubLogger(strings.ToUpper(s[x].Name), s[x].Level, output)
|
||||
output, err := getWriters(&s[x])
|
||||
if err != nil {
|
||||
continue
|
||||
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() {
|
||||
func SetupGlobalLogger() error {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
|
||||
if FileLoggingConfiguredCorrectly {
|
||||
GlobalLogFile = &Rotate{
|
||||
FileName: GlobalLogConfig.LoggerFileConfig.FileName,
|
||||
@@ -96,12 +112,15 @@ func SetupGlobalLogger() {
|
||||
}
|
||||
|
||||
for x := range SubLoggers {
|
||||
SubLoggers[x].Levels = splitLevel(GlobalLogConfig.Level)
|
||||
SubLoggers[x].output = getWriters(&GlobalLogConfig.SubLoggerConfig)
|
||||
SubLoggers[x].SetLevels(splitLevel(GlobalLogConfig.Level))
|
||||
writers, err := getWriters(&GlobalLogConfig.SubLoggerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
SubLoggers[x].SetOutput(writers)
|
||||
}
|
||||
|
||||
logger = newLogger(GlobalLogConfig)
|
||||
RWM.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitLevel(level string) (l Levels) {
|
||||
@@ -121,16 +140,16 @@ func splitLevel(level string) (l Levels) {
|
||||
return
|
||||
}
|
||||
|
||||
func registerNewSubLogger(logger string) *SubLogger {
|
||||
temp := SubLogger{
|
||||
name: strings.ToUpper(logger),
|
||||
func registerNewSubLogger(subLogger string) *SubLogger {
|
||||
temp := &SubLogger{
|
||||
name: strings.ToUpper(subLogger),
|
||||
output: os.Stdout,
|
||||
levels: splitLevel("INFO|WARN|DEBUG|ERROR"),
|
||||
}
|
||||
|
||||
temp.Levels = splitLevel("INFO|WARN|DEBUG|ERROR")
|
||||
SubLoggers[logger] = &temp
|
||||
|
||||
return &temp
|
||||
RWM.Lock()
|
||||
SubLoggers[subLogger] = temp
|
||||
RWM.Unlock()
|
||||
return temp
|
||||
}
|
||||
|
||||
// register all loggers at package init()
|
||||
|
||||
@@ -3,7 +3,9 @@ package log
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -12,11 +14,25 @@ import (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setupTestLoggers()
|
||||
os.Exit(m.Run())
|
||||
err := setupTestLoggers()
|
||||
if err != nil {
|
||||
log.Fatal("cannot set up test loggers", err)
|
||||
}
|
||||
tempDir, err := ioutil.TempDir(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() {
|
||||
func setupTestLoggers() error {
|
||||
logTest := Config{
|
||||
Enabled: convert.BoolPtr(true),
|
||||
SubLoggerConfig: SubLoggerConfig{
|
||||
@@ -36,7 +52,7 @@ func setupTestLoggers() {
|
||||
},
|
||||
SubLoggers: []SubLoggerConfig{
|
||||
{
|
||||
Name: "TEST",
|
||||
Name: "lOg",
|
||||
Level: "INFO|DEBUG|WARN|ERROR",
|
||||
Output: "stdout",
|
||||
}},
|
||||
@@ -44,11 +60,13 @@ func setupTestLoggers() {
|
||||
RWM.Lock()
|
||||
GlobalLogConfig = &logTest
|
||||
RWM.Unlock()
|
||||
SetupGlobalLogger()
|
||||
SetupSubLoggers(logTest.SubLoggers)
|
||||
if err := SetupGlobalLogger(); err != nil {
|
||||
return err
|
||||
}
|
||||
return SetupSubLoggers(logTest.SubLoggers)
|
||||
}
|
||||
|
||||
func SetupDisabled() {
|
||||
func SetupDisabled() error {
|
||||
logTest := Config{
|
||||
Enabled: convert.BoolPtr(false),
|
||||
}
|
||||
@@ -56,8 +74,10 @@ func SetupDisabled() {
|
||||
GlobalLogConfig = &logTest
|
||||
RWM.Unlock()
|
||||
|
||||
SetupGlobalLogger()
|
||||
SetupSubLoggers(logTest.SubLoggers)
|
||||
if err := SetupGlobalLogger(); err != nil {
|
||||
return err
|
||||
}
|
||||
return SetupSubLoggers(logTest.SubLoggers)
|
||||
}
|
||||
|
||||
func BenchmarkInfo(b *testing.B) {
|
||||
@@ -67,43 +87,151 @@ func BenchmarkInfo(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func SetupTestDisabled(t *testing.T) {
|
||||
t.Helper()
|
||||
SetupDisabled()
|
||||
}
|
||||
|
||||
func TestAddWriter(t *testing.T) {
|
||||
mw := MultiWriter()
|
||||
m := mw.(*multiWriter) // nolint // type assert not required
|
||||
t.Parallel()
|
||||
_, err := MultiWriter(ioutil.Discard, ioutil.Discard)
|
||||
if !errors.Is(err, errWriterAlreadyLoaded) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWriterAlreadyLoaded)
|
||||
}
|
||||
|
||||
m.Add(ioutil.Discard)
|
||||
m.Add(os.Stdin)
|
||||
m.Add(os.Stdout)
|
||||
mw, err := MultiWriter()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
err = mw.Add(ioutil.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)
|
||||
}
|
||||
|
||||
if total := len(m.writers); total != 3 {
|
||||
if total := len(mw.writers); total != 3 {
|
||||
t.Errorf("expected m.Writers to be 3 %v", total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveWriter(t *testing.T) {
|
||||
mw := MultiWriter()
|
||||
m := mw.(*multiWriter) // nolint // type assert not required
|
||||
t.Parallel()
|
||||
mw, err := MultiWriter()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = mw.Add(ioutil.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)
|
||||
}
|
||||
|
||||
m.Add(ioutil.Discard)
|
||||
m.Add(os.Stdin)
|
||||
m.Add(os.Stdout)
|
||||
if len(mw.writers) != total-2 {
|
||||
t.Errorf("expected m.Writers to be %v got %v", total-2, len(mw.writers))
|
||||
}
|
||||
}
|
||||
|
||||
total := len(m.writers)
|
||||
type WriteShorter struct{}
|
||||
|
||||
m.Remove(os.Stdin)
|
||||
m.Remove(os.Stdout)
|
||||
func (w *WriteShorter) Write(p []byte) (int, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
if len(m.writers) != total-2 {
|
||||
t.Errorf("expected m.Writers to be %v got %v", total-2, len(m.writers))
|
||||
type WriteError struct{}
|
||||
|
||||
func (w *WriteError) Write(p []byte) (int, error) {
|
||||
return 0, errWriteError
|
||||
}
|
||||
|
||||
var errWriteError = errors.New("write error")
|
||||
|
||||
func TestMultiWriterWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
mw, err := MultiWriter(ioutil.Discard, &bytes.Buffer{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := "woooooooooooooooooooooooooooooooooooow"
|
||||
l, err := mw.Write([]byte(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l != len(payload) {
|
||||
t.Fatal("unexpected return")
|
||||
}
|
||||
|
||||
mw, err = MultiWriter(&WriteShorter{}, ioutil.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)
|
||||
}
|
||||
|
||||
mw, err = MultiWriter(&WriteError{}, ioutil.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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWriters(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := getWriters(nil)
|
||||
if !errors.Is(err, errSubloggerConfigIsNil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errSubloggerConfigIsNil)
|
||||
}
|
||||
|
||||
outputWriters := "stDout|stderr|filE"
|
||||
|
||||
_, err = getWriters(&SubLoggerConfig{Output: outputWriters})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
outputWriters = "stdout|stderr|file|noobs"
|
||||
_, err = getWriters(&SubLoggerConfig{Output: outputWriters})
|
||||
if !errors.Is(err, errUnhandledOutputWriter) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errUnhandledOutputWriter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenDefaultSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
if cfg := GenDefaultSettings(); cfg.Enabled == nil {
|
||||
t.Fatal("unexpected items in struct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevel(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := Level("LOG")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get log %s levels skipping", err)
|
||||
@@ -116,6 +244,7 @@ func TestLevel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetLevel(t *testing.T) {
|
||||
t.Parallel()
|
||||
newLevel, err := SetLevel("LOG", "ERROR")
|
||||
if err != nil {
|
||||
t.Skipf("Failed to get log %s levels skipping", err)
|
||||
@@ -135,32 +264,21 @@ func TestSetLevel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidSubLogger(t *testing.T) {
|
||||
b, logPtr := validSubLogger("LOG")
|
||||
|
||||
if !b {
|
||||
t.Skip("validSubLogger() should return found, pointer if valid logger found")
|
||||
}
|
||||
if logPtr == nil {
|
||||
t.Error("validSubLogger() should return a pointer and not nil")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
t.Skipf("configureSubLogger() returned unexpected error %v", err)
|
||||
}
|
||||
if (Global.Levels != Levels{
|
||||
Info: true,
|
||||
Debug: false,
|
||||
}) {
|
||||
levels := Global.GetLevels()
|
||||
if (levels != Levels{Info: true, Debug: false}) {
|
||||
t.Error("configureSubLogger() incorrectly configure subLogger")
|
||||
}
|
||||
if Global.name != "LOG" {
|
||||
@@ -169,6 +287,7 @@ func TestConfigureSubLogger(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSplitLevel(t *testing.T) {
|
||||
t.Parallel()
|
||||
levelsInfoDebug := splitLevel("INFO|DEBUG")
|
||||
|
||||
expected := Levels{
|
||||
@@ -184,7 +303,9 @@ func TestSplitLevel(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkInfoDisabled(b *testing.B) {
|
||||
SetupDisabled()
|
||||
if err := SetupDisabled(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
@@ -207,8 +328,11 @@ func BenchmarkInfoln(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestNewLogEvent(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)
|
||||
}
|
||||
@@ -217,43 +341,209 @@ func TestNewLogEvent(t *testing.T) {
|
||||
t.Error("newLogEvent() failed expected output got empty string")
|
||||
}
|
||||
|
||||
RWM.Lock()
|
||||
err = logger.newLogEvent("out", "header", "SUBLOGGER", nil)
|
||||
RWM.Unlock()
|
||||
if err == nil {
|
||||
t.Error("Error expected with output is set to nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
|
||||
sl := registerNewSubLogger("TESTYMCTESTALOT")
|
||||
sl.Levels = splitLevel("INFO|WARN|DEBUG|ERROR")
|
||||
sl.output = 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.output = nil
|
||||
w.Reset()
|
||||
|
||||
_, err := SetLevel("TESTYMCTESTALOT", "INFO")
|
||||
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", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Info(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
|
||||
Infoln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebug(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
|
||||
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")
|
||||
}
|
||||
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", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Debug(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but Debug wrote to output")
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
|
||||
Debugln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarn(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
|
||||
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")
|
||||
}
|
||||
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", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Warn(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
|
||||
Warnln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := &bytes.Buffer{}
|
||||
|
||||
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")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Errorf(sl, "%s", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
Errorln(sl, "hello", "hello")
|
||||
|
||||
if w.String() == "" {
|
||||
t.Error("expected Info() to write output to buffer")
|
||||
}
|
||||
w.Reset()
|
||||
|
||||
_, err := SetLevel("TESTYMCTESTALOTERROR", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Error(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
|
||||
Errorln(sl, "HelloHello")
|
||||
|
||||
if w.String() != "" {
|
||||
t.Error("Expected output buffer to be empty but wrote to output", w.String())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -261,9 +551,13 @@ func TestSubLoggerName(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
@@ -273,6 +567,7 @@ func TestSubLoggerName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewSubLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := NewSubLogger("")
|
||||
if !errors.Is(err, errEmptyLoggerName) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errEmptyLoggerName)
|
||||
@@ -290,3 +585,57 @@ func TestNewSubLogger(t *testing.T) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errSubLoggerAlreadyregistered)
|
||||
}
|
||||
}
|
||||
|
||||
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"}
|
||||
payload := make([]byte, defaultMaxSize*megabyte+1)
|
||||
_, err := empty.Write(payload)
|
||||
if !errors.Is(err, errExceedsMaxFileSize) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errExceedsMaxFileSize)
|
||||
}
|
||||
|
||||
empty.MaxSize = 1
|
||||
payload = make([]byte, 1*megabyte+1)
|
||||
_, err = empty.Write(payload)
|
||||
if !errors.Is(err, errExceedsMaxFileSize) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errExceedsMaxFileSize)
|
||||
}
|
||||
|
||||
// test write
|
||||
payload = make([]byte, 1*megabyte-1)
|
||||
_, err = empty.Write(payload)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
// test rotate
|
||||
payload = make([]byte, 1*megabyte)
|
||||
_, err = empty.Write(payload)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
empty := Rotate{}
|
||||
err := empty.openNew()
|
||||
if !errors.Is(err, errFileNameIsEmpty) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errFileNameIsEmpty)
|
||||
}
|
||||
|
||||
empty.FileName = "wow.txt"
|
||||
err = empty.openNew()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ const (
|
||||
spacer = " | "
|
||||
// DefaultMaxFileSize for logger rotation file
|
||||
DefaultMaxFileSize int64 = 100
|
||||
|
||||
defaultCapacityForSliceOfBytes = 100
|
||||
)
|
||||
|
||||
var (
|
||||
logger = &Logger{}
|
||||
logger = Logger{}
|
||||
// FileLoggingConfiguredCorrectly flag set during config check if file logging meets requirements
|
||||
FileLoggingConfiguredCorrectly bool
|
||||
// GlobalLogConfig holds global configuration options for logger
|
||||
@@ -23,9 +25,8 @@ var (
|
||||
|
||||
eventPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Event{
|
||||
data: make([]byte, 0, 80),
|
||||
}
|
||||
sliceOBytes := make([]byte, 0, defaultCapacityForSliceOfBytes)
|
||||
return &sliceOBytes
|
||||
},
|
||||
}
|
||||
|
||||
@@ -85,20 +86,6 @@ type Levels struct {
|
||||
Info, Debug, Warn, Error bool
|
||||
}
|
||||
|
||||
// SubLogger defines a sub logger can be used externally for packages wanted to
|
||||
// leverage GCT library logger features.
|
||||
type SubLogger struct {
|
||||
name string
|
||||
Levels
|
||||
output io.Writer
|
||||
}
|
||||
|
||||
// Event holds the data sent to the log and which multiwriter to send to
|
||||
type Event struct {
|
||||
data []byte
|
||||
output io.Writer
|
||||
}
|
||||
|
||||
type multiWriter struct {
|
||||
writers []io.Writer
|
||||
mu sync.RWMutex
|
||||
|
||||
134
log/loggers.go
134
log/loggers.go
@@ -7,28 +7,27 @@ import (
|
||||
|
||||
// Info takes a pointer subLogger struct and string sends to newLogEvent
|
||||
func Info(sl *SubLogger, data string) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if !fields.info {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.info {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(data, fields.logger.InfoHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(data,
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Infoln takes a pointer subLogger struct and interface sends to newLogEvent
|
||||
func Infoln(sl *SubLogger, v ...interface{}) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.info {
|
||||
return
|
||||
}
|
||||
if !fields.info {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...), fields.logger.InfoHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.InfoHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Infof takes a pointer subLogger struct, string & interface formats and sends to Info()
|
||||
@@ -38,28 +37,27 @@ func Infof(sl *SubLogger, data string, v ...interface{}) {
|
||||
|
||||
// Debug takes a pointer subLogger struct and string sends to multiwriter
|
||||
func Debug(sl *SubLogger, data string) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.debug {
|
||||
return
|
||||
}
|
||||
if !fields.debug {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(data, fields.logger.DebugHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(data,
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Debugln takes a pointer subLogger struct, string and interface sends to newLogEvent
|
||||
func Debugln(sl *SubLogger, v ...interface{}) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if !fields.debug {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.debug {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...), fields.logger.DebugHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.DebugHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Debugf takes a pointer subLogger struct, string & interface formats and sends to Info()
|
||||
@@ -69,28 +67,26 @@ func Debugf(sl *SubLogger, data string, v ...interface{}) {
|
||||
|
||||
// Warn takes a pointer subLogger struct & string and sends to newLogEvent()
|
||||
func Warn(sl *SubLogger, data string) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.warn {
|
||||
return
|
||||
}
|
||||
if !fields.warn {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(data, fields.logger.WarnHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(data,
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Warnln takes a pointer subLogger struct & interface formats and sends to newLogEvent()
|
||||
func Warnln(sl *SubLogger, v ...interface{}) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.warn {
|
||||
return
|
||||
}
|
||||
if !fields.warn {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...), fields.logger.WarnHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.WarnHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Warnf takes a pointer subLogger struct, string & interface formats and sends to Warn()
|
||||
@@ -100,28 +96,26 @@ func Warnf(sl *SubLogger, data string, v ...interface{}) {
|
||||
|
||||
// Error takes a pointer subLogger struct & interface formats and sends to newLogEvent()
|
||||
func Error(sl *SubLogger, data ...interface{}) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.error {
|
||||
return
|
||||
}
|
||||
if !fields.error {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprint(data...), fields.logger.ErrorHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprint(data...),
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Errorln takes a pointer subLogger struct, string & interface formats and sends to newLogEvent()
|
||||
func Errorln(sl *SubLogger, v ...interface{}) {
|
||||
fields := getFields(sl)
|
||||
if fields == nil {
|
||||
fields := sl.getFields()
|
||||
if fields == nil || !fields.error {
|
||||
return
|
||||
}
|
||||
if !fields.error {
|
||||
return
|
||||
}
|
||||
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...), fields.logger.ErrorHeader, fields.name, fields.output))
|
||||
displayError(fields.logger.newLogEvent(fmt.Sprintln(v...),
|
||||
fields.logger.ErrorHeader,
|
||||
fields.name,
|
||||
fields.output))
|
||||
}
|
||||
|
||||
// Errorf takes a pointer subLogger struct, string & interface formats and sends to Debug()
|
||||
@@ -134,35 +128,3 @@ func displayError(err error) {
|
||||
log.Printf("Logger write error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func enabled() bool {
|
||||
RWM.Lock()
|
||||
defer RWM.Unlock()
|
||||
if GlobalLogConfig == nil || GlobalLogConfig.Enabled == nil {
|
||||
return false
|
||||
}
|
||||
if *GlobalLogConfig.Enabled {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getFields(sl *SubLogger) *logFields {
|
||||
if !enabled() {
|
||||
return nil
|
||||
}
|
||||
if sl == nil {
|
||||
return nil
|
||||
}
|
||||
RWM.RLock()
|
||||
defer RWM.RUnlock()
|
||||
return &logFields{
|
||||
info: sl.Info,
|
||||
warn: sl.Warn,
|
||||
debug: sl.Debug,
|
||||
error: sl.Error,
|
||||
name: sl.name,
|
||||
output: sl.output,
|
||||
logger: *logger,
|
||||
}
|
||||
}
|
||||
|
||||
66
log/sublogger.go
Normal file
66
log/sublogger.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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()
|
||||
if _, ok := SubLoggers[name]; ok {
|
||||
RWM.RUnlock()
|
||||
return nil, 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()
|
||||
sl.output = o
|
||||
sl.mtx.Unlock()
|
||||
}
|
||||
|
||||
// SetLevels overrides the default levels with new levels; levelception
|
||||
func (sl *SubLogger) SetLevels(newLevels Levels) {
|
||||
sl.mtx.Lock()
|
||||
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
|
||||
}
|
||||
|
||||
func (sl *SubLogger) getFields() *logFields {
|
||||
RWM.RLock()
|
||||
defer RWM.RUnlock()
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package log
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Global vars related to the logger package
|
||||
var (
|
||||
@@ -34,6 +37,15 @@ var (
|
||||
Fill *SubLogger
|
||||
)
|
||||
|
||||
// SubLogger defines a sub logger can be used externally for packages wanted to
|
||||
// leverage GCT library logger features.
|
||||
type SubLogger struct {
|
||||
name string
|
||||
levels Levels
|
||||
output io.Writer
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
// logFields is used to store data in a non-global and thread-safe manner
|
||||
// so logs cannot be modified mid-log causing a data-race issue
|
||||
type logFields struct {
|
||||
|
||||
Reference in New Issue
Block a user