log: Add structured logging (#1171)

* basic implementation

* log: deprecate duplicate function, add tests and refine calls.

* linter: fixes

* linter: update struct

* linter and new type

* log tests: update to not lint issue

* linter: stop complaining please

* glorious: nits

* log: rm comment code

* glorious: nits

* glorious: nits

* glorious: nits

* glorious: nits missed

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2023-05-10 17:52:53 +10:00
committed by GitHub
parent 6e1cbfc31e
commit db8735ec99
44 changed files with 679 additions and 476 deletions

View File

@@ -221,22 +221,22 @@ func (c *Config) validateCurrencySettings() error {
// PrintSetting prints relevant settings to the console for easy reading
func (c *Config) PrintSetting() {
log.Info(common.Config, common.CMDColours.H1+"------------------Backtester Settings------------------------"+common.CMDColours.Default)
log.Info(common.Config, common.CMDColours.H2+"------------------Strategy Settings--------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H1+"------------------Backtester Settings------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------Strategy Settings--------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Strategy: %s", c.StrategySettings.Name)
if len(c.StrategySettings.CustomSettings) > 0 {
log.Info(common.Config, "Custom strategy variables:")
log.Infoln(common.Config, "Custom strategy variables:")
for k, v := range c.StrategySettings.CustomSettings {
log.Infof(common.Config, "%s: %v", k, v)
}
} else {
log.Info(common.Config, "Custom strategy variables: unset")
log.Infoln(common.Config, "Custom strategy variables: unset")
}
log.Infof(common.Config, "Simultaneous Signal Processing: %v", c.StrategySettings.SimultaneousSignalProcessing)
log.Infof(common.Config, "USD value tracking: %v", !c.StrategySettings.DisableUSDTracking)
if c.FundingSettings.UseExchangeLevelFunding && c.StrategySettings.SimultaneousSignalProcessing {
log.Info(common.Config, common.CMDColours.H2+"------------------Funding Settings---------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------Funding Settings---------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Use Exchange Level Funding: %v", c.FundingSettings.UseExchangeLevelFunding)
if c.DataSettings.LiveData != nil && c.DataSettings.LiveData.RealOrders {
log.Infof(common.Config, "Funding levels will be set by the exchange")
@@ -297,12 +297,12 @@ func (c *Config) PrintSetting() {
log.Infof(common.Config, "Can use exchange defined order execution limits: %+v", c.CurrencySettings[i].CanUseExchangeLimits)
}
log.Info(common.Config, common.CMDColours.H2+"------------------Portfolio Settings-------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------Portfolio Settings-------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Buy rules: %+v", c.PortfolioSettings.BuySide)
log.Infof(common.Config, "Sell rules: %+v", c.PortfolioSettings.SellSide)
log.Infof(common.Config, "Leverage rules: %+v", c.PortfolioSettings.Leverage)
if c.DataSettings.LiveData != nil {
log.Info(common.Config, common.CMDColours.H2+"------------------Live Settings------------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------Live Settings------------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
log.Infof(common.Config, "Using real orders: %v", c.DataSettings.LiveData.RealOrders)
@@ -313,20 +313,20 @@ func (c *Config) PrintSetting() {
}
}
if c.DataSettings.APIData != nil {
log.Info(common.Config, common.CMDColours.H2+"------------------API Settings-------------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------API Settings-------------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
log.Infof(common.Config, "Start date: %v", c.DataSettings.APIData.StartDate.Format(gctcommon.SimpleTimeFormat))
log.Infof(common.Config, "End date: %v", c.DataSettings.APIData.EndDate.Format(gctcommon.SimpleTimeFormat))
}
if c.DataSettings.CSVData != nil {
log.Info(common.Config, common.CMDColours.H2+"------------------CSV Settings-------------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------CSV Settings-------------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
log.Infof(common.Config, "CSV file: %v", c.DataSettings.CSVData.FullPath)
}
if c.DataSettings.DatabaseData != nil {
log.Info(common.Config, common.CMDColours.H2+"------------------Database Settings--------------------------"+common.CMDColours.Default)
log.Infoln(common.Config, common.CMDColours.H2+"------------------Database Settings--------------------------"+common.CMDColours.Default)
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
log.Infof(common.Config, "Start date: %v", c.DataSettings.DatabaseData.StartDate.Format(gctcommon.SimpleTimeFormat))

View File

@@ -95,7 +95,7 @@ func (bt *BackTest) RunLive() error {
go func() {
err = bt.liveCheck()
if err != nil {
log.Error(common.LiveStrategy, err)
log.Errorln(common.LiveStrategy, err)
}
bt.wg.Done()
}()
@@ -153,7 +153,7 @@ func (bt *BackTest) ExecuteStrategy(waitForOfflineCompletion bool) error {
case waitForOfflineCompletion && !liveTesting:
err = bt.Run()
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
return bt.Stop()
case !waitForOfflineCompletion && liveTesting:
@@ -162,11 +162,11 @@ func (bt *BackTest) ExecuteStrategy(waitForOfflineCompletion bool) error {
go func() {
err = bt.Run()
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
err = bt.Stop()
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
}()
}
@@ -188,7 +188,7 @@ func (bt *BackTest) Run() error {
}
if doubleNil {
if bt.verbose {
log.Info(common.Backtester, "No new data on second check")
log.Infoln(common.Backtester, "No new data on second check")
}
return nil
}
@@ -237,7 +237,7 @@ func (bt *BackTest) Run() error {
doubleNil = false
err := bt.handleEvent(ev)
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
if !bt.hasProcessedAnEvent {
bt.hasProcessedAnEvent = true
@@ -283,7 +283,7 @@ func (bt *BackTest) handleEvent(ev common.Event) error {
if err != nil {
return err
}
log.Info(common.LiveStrategy, result)
log.Infoln(common.LiveStrategy, result)
}
default:
err = fmt.Errorf("handleEvent %w %T received, could not process",
@@ -359,7 +359,7 @@ func (bt *BackTest) processSimultaneousDataEvents() error {
case errors.Is(err, gctorder.ErrPositionLiquidated):
return nil
default:
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
}
dataEvents = append(dataEvents, dataHolders[i])
@@ -524,7 +524,7 @@ func (bt *BackTest) processFillEvent(ev fill.Event, funds funding.IFundReleaser)
}
holding, err := bt.Portfolio.ViewHoldingAtTimePeriod(ev)
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
err = bt.Statistic.AddHoldingsForTime(holding)
if err != nil {

View File

@@ -86,7 +86,7 @@ func StartRPCServer(server *GRPCServer) error {
go func() {
if err = s.Serve(lis); err != nil {
log.Error(log.GRPCSys, err)
log.Errorln(log.GRPCSys, err)
return
}
}()
@@ -133,7 +133,7 @@ func (s *GRPCServer) StartRPCRESTProxy() error {
}
}()
log.Debug(log.GRPCSys, "GRPC proxy server started!")
log.Debugln(log.GRPCSys, "GRPC proxy server started!")
return nil
}

View File

@@ -82,7 +82,7 @@ func (d *dataChecker) Start() error {
if err != nil {
stopErr := d.SignalStopFromError(err)
if stopErr != nil {
log.Error(common.LiveStrategy, stopErr)
log.Errorln(common.LiveStrategy, stopErr)
}
}
}()
@@ -118,7 +118,7 @@ func (d *dataChecker) SignalStopFromError(err error) error {
if !atomic.CompareAndSwapUint32(&d.started, 1, 0) {
return engine.ErrSubSystemNotStarted
}
log.Error(common.LiveStrategy, err)
log.Errorln(common.LiveStrategy, err)
d.shutdownErr <- true
return nil
}
@@ -399,7 +399,7 @@ func (d *dataChecker) FetchLatestData() (bool, error) {
err = d.UpdateFunding(false)
if err != nil {
if err != nil {
log.Error(common.LiveStrategy, err)
log.Errorln(common.LiveStrategy, err)
}
}
}

View File

@@ -783,7 +783,7 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange,
defer func() {
stopErr := bt.databaseManager.Stop()
if stopErr != nil {
log.Error(common.Setup, stopErr)
log.Errorln(common.Setup, stopErr)
}
}()
resp, err = loadDatabaseData(cfg, exch.GetName(), fPair, a, dataType, isUSDTrackingPair)

View File

@@ -169,7 +169,7 @@ func CalculateBiggestValueAtTimeDrawdown(closePrices []ValueAtTime, interval gct
}
intervals, err := gctkline.CalculateCandleDateRanges(highestTime, lowestTime, interval, 0)
if err != nil {
log.Error(common.CurrencyStatistics, err)
log.Errorln(common.CurrencyStatistics, err)
}
drawdownPercent := decimal.Zero
if highestPrice.GreaterThan(decimal.Zero) {

View File

@@ -36,13 +36,13 @@ func addReason(reason, msg string) string {
// PrintTotalResults outputs all results to the CMD
func (s *Statistic) PrintTotalResults() {
log.Info(common.Statistics, common.CMDColours.H1+"------------------Strategy-----------------------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.H1+"------------------Strategy-----------------------------------"+common.CMDColours.Default)
log.Infof(common.Statistics, "Strategy Name: %v", s.StrategyName)
log.Infof(common.Statistics, "Strategy Nickname: %v", s.StrategyNickname)
log.Infof(common.Statistics, "Strategy Goal: %v\n\n", s.StrategyGoal)
log.Info(common.Statistics, common.CMDColours.H2+"------------------Total Results------------------------------"+common.CMDColours.Default)
log.Info(common.Statistics, common.CMDColours.H3+"------------------Orders-------------------------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.H2+"------------------Total Results------------------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.H3+"------------------Orders-------------------------------------"+common.CMDColours.Default)
log.Infof(common.Statistics, "Total buy orders: %v", convert.IntToHumanFriendlyString(s.TotalBuyOrders, ","))
log.Infof(common.Statistics, "Total sell orders: %v", convert.IntToHumanFriendlyString(s.TotalSellOrders, ","))
log.Infof(common.Statistics, "Total long orders: %v", convert.IntToHumanFriendlyString(s.TotalLongOrders, ","))
@@ -50,7 +50,7 @@ func (s *Statistic) PrintTotalResults() {
log.Infof(common.Statistics, "Total orders: %v\n\n", convert.IntToHumanFriendlyString(s.TotalOrders, ","))
if s.BiggestDrawdown != nil {
log.Info(common.Statistics, common.CMDColours.H3+"------------------Biggest Drawdown-----------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.H3+"------------------Biggest Drawdown-----------------------"+common.CMDColours.Default)
log.Infof(common.Statistics, "Exchange: %v Asset: %v Currency: %v", s.BiggestDrawdown.Exchange, s.BiggestDrawdown.Asset, s.BiggestDrawdown.Pair)
log.Infof(common.Statistics, "Highest Price: %s", convert.DecimalToHumanFriendlyString(s.BiggestDrawdown.MaxDrawdown.Highest.Value, 8, ".", ","))
log.Infof(common.Statistics, "Highest Price Time: %v", s.BiggestDrawdown.MaxDrawdown.Highest.Time)
@@ -61,7 +61,7 @@ func (s *Statistic) PrintTotalResults() {
log.Infof(common.Statistics, "Drawdown length: %v candles\n\n", convert.IntToHumanFriendlyString(s.BiggestDrawdown.MaxDrawdown.IntervalDuration, ","))
}
if s.BestMarketMovement != nil && s.BestStrategyResults != nil {
log.Info(common.Statistics, common.CMDColours.H4+"------------------Orders----------------------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.H4+"------------------Orders----------------------------------"+common.CMDColours.Default)
log.Infof(common.Statistics, "Best performing market movement: %v %v %v %v%%", s.BestMarketMovement.Exchange, s.BestMarketMovement.Asset, s.BestMarketMovement.Pair, convert.DecimalToHumanFriendlyString(s.BestMarketMovement.MarketMovement, 2, ".", ","))
log.Infof(common.Statistics, "Best performing strategy movement: %v %v %v %v%%\n\n", s.BestStrategyResults.Exchange, s.BestStrategyResults.Asset, s.BestStrategyResults.Pair, convert.DecimalToHumanFriendlyString(s.BestStrategyResults.StrategyMovement, 2, ".", ","))
}
@@ -71,7 +71,7 @@ func (s *Statistic) PrintTotalResults() {
// rather than separated by exchange, asset and currency pair, it's
// grouped by time to allow a clearer picture of events
func (s *Statistic) PrintAllEventsChronologically() {
log.Info(common.Statistics, common.CMDColours.H1+"------------------Events-------------------------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.H1+"------------------Events-------------------------------------"+common.CMDColours.Default)
var errs error
var results []eventOutputHolder
for _, exchangeMap := range s.ExchangeAssetPairStatistics {
@@ -119,13 +119,13 @@ func (s *Statistic) PrintAllEventsChronologically() {
})
for i := range results {
for j := range results[i].Events {
log.Info(common.Statistics, results[i].Events[j])
log.Infoln(common.Statistics, results[i].Events[j])
}
}
if errs != nil {
log.Info(common.Statistics, common.CMDColours.Error+"------------------Errors-------------------------------------"+common.CMDColours.Default)
log.Infoln(common.Statistics, common.CMDColours.Error+"------------------Errors-------------------------------------"+common.CMDColours.Default)
for err := errors.Unwrap(errs); err != nil; err = errors.Unwrap(errs) {
log.Error(common.Statistics, err.Error())
log.Errorln(common.Statistics, err.Error())
}
}
}
@@ -239,17 +239,17 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
log.Infof(common.CurrencyStatistics, "%s Total orders: %s", sep, convert.IntToHumanFriendlyString(c.TotalOrders, ","))
log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Max Drawdown-------------------------------"+common.CMDColours.Default)
log.Infoln(common.CurrencyStatistics, common.CMDColours.H2+"------------------Max Drawdown-------------------------------"+common.CMDColours.Default)
log.Infof(common.CurrencyStatistics, "%s Highest Price of drawdown: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Highest.Value, 8, ".", ","), c.MaxDrawdown.Highest.Time)
log.Infof(common.CurrencyStatistics, "%s Lowest Price of drawdown: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Lowest.Value, 8, ".", ","), c.MaxDrawdown.Lowest.Time)
log.Infof(common.CurrencyStatistics, "%s Calculated Drawdown: %s%%", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.DrawdownPercent, 8, ".", ","))
log.Infof(common.CurrencyStatistics, "%s Difference: %s", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Highest.Value.Sub(c.MaxDrawdown.Lowest.Value), 2, ".", ","))
log.Infof(common.CurrencyStatistics, "%s Drawdown length: %s", sep, convert.IntToHumanFriendlyString(c.MaxDrawdown.IntervalDuration, ","))
if !usingExchangeLevelFunding && c.TotalOrders > 1 {
log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Ratios------------------------------------------------"+common.CMDColours.Default)
log.Info(common.CurrencyStatistics, common.CMDColours.H3+"------------------Rates-------------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.CurrencyStatistics, common.CMDColours.H2+"------------------Ratios------------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.CurrencyStatistics, common.CMDColours.H3+"------------------Rates-------------------------------------------------"+common.CMDColours.Default)
log.Infof(common.CurrencyStatistics, "%s Compound Annual Growth Rate: %s", sep, convert.DecimalToHumanFriendlyString(c.CompoundAnnualGrowthRate, 2, ".", ","))
log.Info(common.CurrencyStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.CurrencyStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default)
if c.ShowMissingDataWarning {
log.Infoln(common.CurrencyStatistics, "Missing data was detected during this backtesting run")
log.Infoln(common.CurrencyStatistics, "Ratio calculations will be skewed")
@@ -259,7 +259,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
log.Infof(common.CurrencyStatistics, "%s Information ratio: %v", sep, c.ArithmeticRatios.InformationRatio.Round(4))
log.Infof(common.CurrencyStatistics, "%s Calmar ratio: %v", sep, c.ArithmeticRatios.CalmarRatio.Round(4))
log.Info(common.CurrencyStatistics, common.CMDColours.H4+"------------------Geometric--------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.CurrencyStatistics, common.CMDColours.H4+"------------------Geometric--------------------------------------------"+common.CMDColours.Default)
if c.ShowMissingDataWarning {
log.Infoln(common.CurrencyStatistics, "Missing data was detected during this backtesting run")
log.Infoln(common.CurrencyStatistics, "Ratio calculations will be skewed")
@@ -270,7 +270,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
log.Infof(common.CurrencyStatistics, "%s Calmar ratio: %v", sep, c.GeometricRatios.CalmarRatio.Round(4))
}
log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Results------------------------------------"+common.CMDColours.Default)
log.Infoln(common.CurrencyStatistics, common.CMDColours.H2+"------------------Results------------------------------------"+common.CMDColours.Default)
log.Infof(common.CurrencyStatistics, "%s Starting Close Price: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.StartingClosePrice.Value, 8, ".", ","), c.StartingClosePrice.Time)
log.Infof(common.CurrencyStatistics, "%s Finishing Close Price: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.EndingClosePrice.Value, 8, ".", ","), c.EndingClosePrice.Time)
log.Infof(common.CurrencyStatistics, "%s Lowest Close Price: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.LowestClosePrice.Value, 8, ".", ","), c.LowestClosePrice.Time)
@@ -318,10 +318,10 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
}
}
if len(spotResults) > 0 || len(futuresResults) > 0 {
log.Info(common.FundingStatistics, common.CMDColours.H1+"------------------Funding------------------------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H1+"------------------Funding------------------------------------"+common.CMDColours.Default)
}
if len(spotResults) > 0 {
log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Spot Item Results------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Spot Item Results------------------"+common.CMDColours.Default)
for i := range spotResults {
if spotResults[i].ReportItem.AppendedViaAPI {
continue
@@ -346,12 +346,12 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
log.Infof(common.FundingStatistics, "%s Transfer fee: %s", sep, convert.DecimalToHumanFriendlyString(spotResults[i].ReportItem.TransferFee, 8, ".", ","))
}
if i != len(spotResults)-1 {
log.Info(common.FundingStatistics, "")
log.Infoln(common.FundingStatistics, "")
}
}
}
if len(futuresResults) > 0 {
log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Futures Item Results---------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Futures Item Results---------------"+common.CMDColours.Default)
for i := range futuresResults {
if futuresResults[i].ReportItem.AppendedViaAPI {
continue
@@ -373,14 +373,14 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
log.Infof(common.FundingStatistics, "%s Final Contract Holdings: %v %v at %v", sep, futuresResults[i].FinalHoldings.Value, futuresResults[i].ReportItem.Currency, futuresResults[i].FinalHoldings.Time)
}
if i != len(futuresResults)-1 {
log.Info(common.FundingStatistics, "")
log.Infoln(common.FundingStatistics, "")
}
}
}
if f.Report.DisableUSDTracking {
return nil
}
log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------USD Tracking Totals------------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H2+"------------------USD Tracking Totals------------------------"+common.CMDColours.Default)
sep := "USD Tracking Total |\t"
log.Infof(common.FundingStatistics, "%s Initial value: $%s", sep, convert.DecimalToHumanFriendlyString(f.Report.InitialFunds, 8, ".", ","))
@@ -392,14 +392,14 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
log.Infof(common.FundingStatistics, "%s Highest funds: $%s at %v", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.HighestHoldingValue.Value, 8, ".", ","), f.TotalUSDStatistics.HighestHoldingValue.Time)
log.Infof(common.FundingStatistics, "%s Lowest funds: $%s at %v", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.LowestHoldingValue.Value, 8, ".", ","), f.TotalUSDStatistics.LowestHoldingValue.Time)
log.Info(common.FundingStatistics, common.CMDColours.H3+"------------------Ratios------------------------------------------------"+common.CMDColours.Default)
log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Rates-------------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H3+"------------------Ratios------------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H4+"------------------Rates-------------------------------------------------"+common.CMDColours.Default)
log.Infof(common.FundingStatistics, "%s Risk free rate: %s%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.RiskFreeRate.Mul(decimal.NewFromInt(100)), 2, ".", ","))
log.Infof(common.FundingStatistics, "%s Compound Annual Growth Rate: %v%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.CompoundAnnualGrowthRate, 8, ".", ","))
if f.TotalUSDStatistics.ArithmeticRatios == nil || f.TotalUSDStatistics.GeometricRatios == nil {
return fmt.Errorf("%w missing ratio calculations", gctcommon.ErrNilPointer)
}
log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default)
if wasAnyDataMissing {
log.Infoln(common.FundingStatistics, "Missing data was detected during this backtesting run")
log.Infoln(common.FundingStatistics, "Ratio calculations will be skewed")
@@ -409,7 +409,7 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
log.Infof(common.FundingStatistics, "%s Information ratio: %v", sep, f.TotalUSDStatistics.ArithmeticRatios.InformationRatio.Round(4))
log.Infof(common.FundingStatistics, "%s Calmar ratio: %v", sep, f.TotalUSDStatistics.ArithmeticRatios.CalmarRatio.Round(4))
log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Geometric--------------------------------------------"+common.CMDColours.Default)
log.Infoln(common.FundingStatistics, common.CMDColours.H4+"------------------Geometric--------------------------------------------"+common.CMDColours.Default)
if wasAnyDataMissing {
log.Infoln(common.FundingStatistics, "Missing data was detected during this backtesting run")
log.Infoln(common.FundingStatistics, "Ratio calculations will be skewed")

View File

@@ -211,7 +211,7 @@ func (s *Statistic) AddComplianceSnapshotForTime(c *compliance.Snapshot, e commo
// CalculateAllResults calculates the statistics of all exchange asset pair holdings,
// orders, ratios and drawdowns
func (s *Statistic) CalculateAllResults() error {
log.Info(common.Statistics, "Calculating backtesting results")
log.Infoln(common.Statistics, "Calculating backtesting results")
s.PrintAllEventsChronologically()
currCount := 0
var finalResults []FinalResultsHolder
@@ -227,7 +227,7 @@ func (s *Statistic) CalculateAllResults() error {
}
err = stats.CalculateResults(s.RiskFreeRate)
if err != nil {
log.Error(common.Statistics, err)
log.Errorln(common.Statistics, err)
}
stats.FinalHoldings = last.Holdings
stats.InitialHoldings = stats.Events[0].Holdings

View File

@@ -138,7 +138,7 @@ func main() {
os.Exit(1)
}
err = log.SetupGlobalLogger()
err = log.SetupGlobalLogger("gct/backtester", false)
if err != nil {
fmt.Printf("Could not setup global logger. Error: %v\n", err)
os.Exit(1)
@@ -218,7 +218,7 @@ func main() {
runManager := backtest.NewTaskManager()
go func(c *config.BacktesterConfig) {
log.Info(log.GRPCSys, "Starting RPC server")
log.Infoln(log.GRPCSys, "Starting RPC server")
var s *backtest.GRPCServer
s, err = backtest.SetupRPCServer(c, runManager)
err = backtest.StartRPCServer(s)
@@ -226,7 +226,7 @@ func main() {
fmt.Printf("Could not start RPC server. Error: %v\n", err)
os.Exit(1)
}
log.Info(log.GRPCSys, "Ready to receive commands")
log.Infoln(log.GRPCSys, "Ready to receive commands")
}(btCfg)
interrupt := signaler.WaitForInterrupt()
log.Infof(log.Global, "Captured %v, shutdown requested\n", interrupt)
@@ -235,7 +235,7 @@ func main() {
var stopped []*backtest.TaskSummary
stopped, err = runManager.StopAllTasks()
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
for i := range stopped {
log.Infof(common.Backtester, "Task %v %v was stopped", stopped[i].MetaData.ID, stopped[i].MetaData.Strategy)
@@ -244,13 +244,13 @@ func main() {
var tasks []*backtest.TaskSummary
tasks, err = runManager.List()
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
}
for i := range tasks {
if tasks[i].MetaData.ClosePositionsOnStop && !tasks[i].MetaData.Closed {
err = runManager.StopTask(tasks[i].MetaData.ID)
if err != nil {
log.Error(common.Backtester, err)
log.Errorln(common.Backtester, err)
continue
}
log.Infof(common.Backtester, "Task %v %v was stopped", tasks[i].MetaData.ID, tasks[i].MetaData.Strategy)

View File

@@ -21,7 +21,7 @@ func (d *Data) GenerateReport() error {
if d.TemplatePath == "" || d.OutputPath == "" {
return nil
}
log.Info(common.Report, "Generating report")
log.Infoln(common.Report, "Generating report")
err := d.enhanceCandles()
if err != nil {
return err
@@ -95,7 +95,7 @@ func (d *Data) GenerateReport() error {
defer func() {
err = f.Close()
if err != nil {
log.Error(common.Report, err)
log.Errorln(common.Report, err)
}
}()

View File

@@ -104,19 +104,19 @@ func main() {
os.Exit(1)
}
err = log.SetupGlobalLogger()
err = log.SetupGlobalLogger("cmd/apicheck", false)
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)
log.Errorln(log.Global, err)
os.Exit(1)
}
testConfigData, err = readFileData(testJSONFile)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
usageData = testConfigData
@@ -130,7 +130,7 @@ func main() {
data.Repo = path
err = addExch(exchangeName, checkType, data, false)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
case htmlScrape:
@@ -145,7 +145,7 @@ func main() {
data.Path = path
err = addExch(exchangeName, checkType, data, false)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
}
@@ -156,7 +156,7 @@ func main() {
if trelloBoardName != "" {
a, err = trelloGetBoardID()
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
trelloBoardID = a
@@ -164,25 +164,25 @@ func main() {
if create {
err = createAndSet()
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
}
err = updateFile(backupFile)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
err = checkUpdates(jsonFile)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
} else {
log.Warnln(log.Global, "This is a test update since API keys are not set.")
err := checkUpdates(testJSONFile)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
log.Infoln(log.Global, "API update check completed successfully")

View File

@@ -26,18 +26,18 @@ func TestMain(m *testing.M) {
setTestVars()
err := log.SetGlobalLogConfig(log.GenDefaultSettings())
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
log.Infoln(log.Global, "set verbose to true for more detailed output")
configData, err = readFileData(jsonFile)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
testConfigData, err = readFileData(testJSONFile)
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
usageData = testConfigData
@@ -45,7 +45,7 @@ func TestMain(m *testing.M) {
testExitCode := m.Run()
err = removeTestFileVars()
if err != nil {
log.Error(log.Global, err)
log.Errorln(log.Global, err)
os.Exit(1)
}
os.Exit(testExitCode)

View File

@@ -142,7 +142,7 @@ func (c *Config) CheckClientBankAccounts() {
err := c.BankAccounts[i].Validate()
if err != nil {
c.BankAccounts[i].Enabled = false
log.Warn(log.ConfigMgr, err.Error())
log.Warnln(log.ConfigMgr, err.Error())
}
}
}
@@ -1047,7 +1047,7 @@ func (c *Config) CheckBankAccountConfig() {
err := c.BankAccounts[x].Validate()
if err != nil {
c.BankAccounts[x].Enabled = false
log.Warn(log.ConfigMgr, err.Error())
log.Warnln(log.ConfigMgr, err.Error())
}
}
}
@@ -1559,7 +1559,7 @@ func readEncryptedConfWithKey(reader *bufio.Reader, keyProvider func() ([]byte,
var c *Config
c, err = readEncryptedConf(bytes.NewReader(fileData), key)
if err != nil {
log.Error(log.ConfigMgr, "Could not decrypt and deserialise data with given key. Invalid password?", err)
log.Errorln(log.ConfigMgr, "Could not decrypt and deserialise data with given key. Invalid password?", err)
continue
}
return c, nil
@@ -1594,7 +1594,7 @@ func (c *Config) SaveConfigToFile(configPath string) error {
if writer != nil {
err = writer.Close()
if err != nil {
log.Error(log.ConfigMgr, err)
log.Errorln(log.ConfigMgr, err)
}
}
}()

View File

@@ -20,7 +20,7 @@ const (
func areAPICredtionalsSet(minAllowable uint8) bool {
if apiAccountPlanLevel != "" && apikey != "" {
if err := c.CheckAccountPlan(minAllowable); err != nil {
log.Warn(log.Global, "coinmarketpcap test suite - account plan not allowed for function, please review or upgrade plan to test")
log.Warnln(log.Global, "coinmarketpcap test suite - account plan not allowed for function, please review or upgrade plan to test")
return false
}
return true

View File

@@ -35,7 +35,7 @@ func Event(res *withdraw.Response) {
exchangeUUID, err := exchangeDB.UUIDByName(res.Exchange.Name)
if err != nil {
log.Error(log.DatabaseMgr, err)
log.Errorln(log.DatabaseMgr, err)
return
}
@@ -232,7 +232,7 @@ func GetEventByUUID(id string) (*withdraw.Response, error) {
func GetEventsByExchange(exchange string, limit int) ([]*withdraw.Response, error) {
exch, err := exchangeDB.UUIDByName(exchange)
if err != nil {
log.Error(log.DatabaseMgr, err)
log.Errorln(log.DatabaseMgr, err)
return nil, err
}
return getByColumns(generateWhereQuery([]string{"exchange_name_id"}, []string{exch.String()}, limit))
@@ -242,7 +242,7 @@ func GetEventsByExchange(exchange string, limit int) ([]*withdraw.Response, erro
func GetEventByExchangeID(exchange, id string) (*withdraw.Response, error) {
exch, err := exchangeDB.UUIDByName(exchange)
if err != nil {
log.Error(log.DatabaseMgr, err)
log.Errorln(log.DatabaseMgr, err)
return nil, err
}
resp, err := getByColumns(generateWhereQuery([]string{"exchange_name_id", "exchange_id"}, []string{exch.String(), id}, 1))
@@ -260,7 +260,7 @@ func GetEventsByDate(exchange string, start, end time.Time, limit int) ([]*withd
}
exch, err := exchangeDB.UUIDByName(exchange)
if err != nil {
log.Error(log.DatabaseMgr, err)
log.Errorln(log.DatabaseMgr, err)
return nil, err
}
return getByColumns(append(generateWhereQuery([]string{"exchange_name_id"}, []string{exch.String()}, 0), betweenQuery...))

View File

@@ -181,7 +181,7 @@ func (m *apiServerManager) StartRESTServer() error {
if err != nil {
atomic.StoreInt32(&m.restStarted, 0)
if !errors.Is(err, http.ErrServerClosed) {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
}
}()
@@ -296,7 +296,7 @@ func (m *apiServerManager) restGetAllEnabledAccountInfo(w http.ResponseWriter, r
func (m *apiServerManager) getIndex(w http.ResponseWriter, _ *http.Request) {
_, err := fmt.Fprint(w, restIndexResponse)
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
w.WriteHeader(http.StatusOK)
}
@@ -443,7 +443,7 @@ func (m *apiServerManager) StartWebsocketServer() error {
if err != nil {
atomic.StoreInt32(&m.websocketStarted, 0)
if !errors.Is(err, http.ErrServerClosed) {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
}
}()
@@ -502,7 +502,7 @@ func (c *websocketClient) read() {
c.Hub.Unregister <- c
conErr := c.Conn.Close()
if conErr != nil {
log.Error(log.APIServerMgr, conErr)
log.Errorln(log.APIServerMgr, conErr)
}
}()
@@ -547,7 +547,7 @@ func (c *websocketClient) read() {
log.Warnf(log.APIServerMgr, "Websocket: request %s failed due to unauthenticated request on an authenticated API\n", evt.Event)
err = c.SendWebsocketMessage(WebsocketEventResponse{Event: evt.Event, Error: "unauthorised request on authenticated API"})
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
continue
}
@@ -565,7 +565,7 @@ func (c *websocketClient) write() {
defer func() {
err := c.Conn.Close()
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
}()
for {
@@ -573,7 +573,7 @@ func (c *websocketClient) write() {
if !ok {
err := c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
log.Debugln(log.APIServerMgr, "websocket: hub closed the channel")
return
@@ -586,7 +586,7 @@ func (c *websocketClient) write() {
}
_, err = w.Write(message)
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
// Add queued chat messages to the current websocket message
@@ -594,7 +594,7 @@ func (c *websocketClient) write() {
for i := 0; i < n; i++ {
_, err = w.Write(<-c.Send)
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
}
}
@@ -661,7 +661,7 @@ func (m *apiServerManager) WebsocketClientHandler(w http.ResponseWriter, r *http
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(log.APIServerMgr, err)
log.Errorln(log.APIServerMgr, err)
return
}
@@ -703,7 +703,7 @@ func wsAuth(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -726,7 +726,7 @@ func wsAuth(client *websocketClient, data interface{}) error {
client.authFailures++
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
if client.authFailures >= client.maxAuthFailures {
log.Debugf(log.APIServerMgr,
@@ -765,7 +765,7 @@ func wsSaveConfig(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -776,7 +776,7 @@ func wsSaveConfig(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -786,7 +786,7 @@ func wsSaveConfig(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -826,7 +826,7 @@ func wsGetTicker(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -846,7 +846,7 @@ func wsGetTicker(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -855,7 +855,7 @@ func wsGetTicker(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -886,7 +886,7 @@ func wsGetOrderbook(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -906,7 +906,7 @@ func wsGetOrderbook(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}
@@ -915,7 +915,7 @@ func wsGetOrderbook(client *websocketClient, data interface{}) error {
wsResp.Error = err.Error()
sendErr := client.SendWebsocketMessage(wsResp)
if sendErr != nil {
log.Error(log.APIServerMgr, sendErr)
log.Errorln(log.APIServerMgr, sendErr)
}
return err
}

View File

@@ -160,7 +160,7 @@ func (m *DatabaseConnectionManager) run(wg *sync.WaitGroup) {
case <-t.C:
err := m.checkConnection()
if err != nil {
log.Error(log.DatabaseMgr, "Database connection error:", err)
log.Errorln(log.DatabaseMgr, "Database connection error:", err)
}
}
}
@@ -186,7 +186,7 @@ func (m *DatabaseConnectionManager) checkConnection() error {
}
if !m.dbConn.IsConnected() {
log.Info(log.DatabaseMgr, "Database connection reestablished")
log.Infoln(log.DatabaseMgr, "Database connection reestablished")
m.dbConn.SetConnected(true)
}
return nil

View File

@@ -132,7 +132,7 @@ func (m *DataHistoryManager) retrieveJobs() ([]*DataHistoryJob, error) {
}
err = m.validateJob(dbJob)
if err != nil {
log.Error(log.DataHistory, err)
log.Errorln(log.DataHistory, err)
continue
}
response = append(response, dbJob)
@@ -156,7 +156,7 @@ func (m *DataHistoryManager) PrepareJobs() ([]*DataHistoryJob, error) {
defer func() {
err = m.Stop()
if err != nil {
log.Error(log.DataHistory, err)
log.Errorln(log.DataHistory, err)
}
}()
return nil, fmt.Errorf("error retrieving jobs, has everything been setup? Data history manager will shut down. %w", err)
@@ -237,7 +237,7 @@ func (m *DataHistoryManager) run() {
if m.databaseConnectionInstance != nil && m.databaseConnectionInstance.IsConnected() {
go func() {
if err := m.runJobs(); err != nil {
log.Error(log.DataHistory, err)
log.Errorln(log.DataHistory, err)
}
}()
}
@@ -274,7 +274,7 @@ func (m *DataHistoryManager) runJobs() error {
for i := 0; (i < int(m.maxJobsPerCycle) || m.maxJobsPerCycle == -1) && i < len(validJobs); i++ {
err := m.runJob(validJobs[i])
if err != nil {
log.Error(log.DataHistory, err)
log.Errorln(log.DataHistory, err)
}
if m.verbose {
log.Debugf(log.DataHistory, "completed run of data history job %v", validJobs[i].Nickname)

View File

@@ -90,7 +90,7 @@ func NewFromSettings(settings *Settings, flagSet map[string]bool) (*Engine, erro
}
if *b.Config.Logging.Enabled {
err = gctlog.SetupGlobalLogger()
err = gctlog.SetupGlobalLogger(b.Config.Name, b.Config.Logging.AdvancedSettings.StructuredLogging)
if err != nil {
return nil, fmt.Errorf("failed to setup global logger. %w", err)
}
@@ -339,7 +339,7 @@ func (bot *Engine) Start() error {
if err != nil {
return fmt.Errorf("unable to set NTP check: %w", err)
}
gctlog.Info(gctlog.TimeMgr, responseMessage)
gctlog.Infoln(gctlog.TimeMgr, responseMessage)
}
bot.ntpManager, err = setupNTPManager(&bot.Config.NTPClient, *bot.Config.Logging.Enabled)
if err != nil {

View File

@@ -109,7 +109,7 @@ func (m *ntpManager) run() {
case <-t.C:
err := m.processTime()
if err != nil {
log.Error(log.TimeMgr, err)
log.Errorln(log.TimeMgr, err)
}
}
}
@@ -166,7 +166,7 @@ func (m *ntpManager) checkTimeInPools() time.Time {
log.Warnf(log.TimeMgr, "Unable to SetDeadline. Error: %s\n", err)
err = con.Close()
if err != nil {
log.Error(log.TimeMgr, err)
log.Errorln(log.TimeMgr, err)
}
continue
}
@@ -176,7 +176,7 @@ func (m *ntpManager) checkTimeInPools() time.Time {
log.Warnf(log.TimeMgr, "Unable to write. Error: %s\n", err)
err = con.Close()
if err != nil {
log.Error(log.TimeMgr, err)
log.Errorln(log.TimeMgr, err)
}
continue
}
@@ -186,7 +186,7 @@ func (m *ntpManager) checkTimeInPools() time.Time {
log.Warnf(log.TimeMgr, "Unable to read. Error: %s\n", err)
err = con.Close()
if err != nil {
log.Error(log.TimeMgr, err)
log.Errorln(log.TimeMgr, err)
}
continue
}
@@ -196,7 +196,7 @@ func (m *ntpManager) checkTimeInPools() time.Time {
err = con.Close()
if err != nil {
log.Error(log.TimeMgr, err)
log.Errorln(log.TimeMgr, err)
}
return time.Unix(int64(secs), nanos)
}

View File

@@ -147,12 +147,12 @@ func (m *OrderManager) CancelAllOrders(ctx context.Context, exchanges []exchange
log.Debugf(log.OrderMgr, "Cancelling order(s) for exchange %s.", exchanges[i].GetName())
cancel, err := orders[j].DeriveCancel()
if err != nil {
log.Error(log.OrderMgr, err)
log.Errorln(log.OrderMgr, err)
continue
}
err = m.Cancel(ctx, cancel)
if err != nil {
log.Error(log.OrderMgr, err)
log.Errorln(log.OrderMgr, err)
}
}
}
@@ -685,7 +685,7 @@ func (m *OrderManager) processOrders() {
var upsertResponse *OrderUpsertResponse
upsertResponse, err = m.UpsertOrder(&result[z])
if err != nil {
log.Error(log.OrderMgr, err)
log.Errorln(log.OrderMgr, err)
continue
}
for i := range orders {
@@ -708,7 +708,7 @@ func (m *OrderManager) processOrders() {
var sd time.Time
sd, err = m.orderStore.futuresPositionController.LastUpdated()
if err != nil {
log.Error(log.OrderMgr, err)
log.Errorln(log.OrderMgr, err)
return
}
if sd.IsZero() {
@@ -721,7 +721,7 @@ func (m *OrderManager) processOrders() {
})
if err != nil {
if !errors.Is(err, common.ErrNotYetImplemented) {
log.Error(log.OrderMgr, err)
log.Errorln(log.OrderMgr, err)
}
return
}
@@ -817,7 +817,7 @@ func (m *OrderManager) processMatchingOrders(exch exchange.IBotExchange, orders
}
err := m.FetchAndUpdateExchangeOrder(exch, &orders[x], orders[x].AssetType)
if err != nil {
log.Error(log.OrderMgr, err)
log.Errorln(log.OrderMgr, err)
}
}
if wg != nil {
@@ -919,10 +919,10 @@ func (m *OrderManager) UpsertOrder(od *order.Detail) (resp *OrderUpsertResponse,
upsertResponse.OrderDetails.Pair, upsertResponse.OrderDetails.Price, upsertResponse.OrderDetails.Amount,
upsertResponse.OrderDetails.Side, upsertResponse.OrderDetails.Type, upsertResponse.OrderDetails.Status)
if upsertResponse.IsNewOrder {
log.Info(log.OrderMgr, msg)
log.Infoln(log.OrderMgr, msg)
return upsertResponse, nil
}
log.Debug(log.OrderMgr, msg)
log.Debugln(log.OrderMgr, msg)
return upsertResponse, nil
}

View File

@@ -709,7 +709,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream
defer func() {
pipeErr := pipe.Release()
if pipeErr != nil {
log.Error(log.DispatchMgr, pipeErr)
log.Errorln(log.DispatchMgr, pipeErr)
}
}()
@@ -2183,7 +2183,7 @@ func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStr
defer func() {
pipeErr := pipe.Release()
if pipeErr != nil {
log.Error(log.DispatchMgr, pipeErr)
log.Errorln(log.DispatchMgr, pipeErr)
}
}()
@@ -2268,7 +2268,7 @@ func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gct
defer func() {
pipeErr := pipe.Release()
if pipeErr != nil {
log.Error(log.DispatchMgr, pipeErr)
log.Errorln(log.DispatchMgr, pipeErr)
}
}()
@@ -2321,7 +2321,7 @@ func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamReq
defer func() {
pipeErr := pipe.Release()
if pipeErr != nil {
log.Error(log.DispatchMgr, pipeErr)
log.Errorln(log.DispatchMgr, pipeErr)
}
}()
@@ -4111,7 +4111,7 @@ func (s *RPCServer) SetDataHistoryJobStatus(_ context.Context, r *gctrpc.SetData
status := "success"
err := s.dataHistoryManager.SetJobStatus(r.Nickname, r.Id, dataHistoryStatus(r.Status))
if err != nil {
log.Error(log.GRPCSys, err)
log.Errorln(log.GRPCSys, err)
status = "failed"
}

View File

@@ -218,7 +218,7 @@ func (m *syncManager) Start() error {
log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer stopping.")
err := m.Stop()
if err != nil {
log.Error(log.SyncMgr, err)
log.Errorln(log.SyncMgr, err)
}
return
}
@@ -547,7 +547,7 @@ func (m *syncManager) worker() {
m.add(c)
} else {
log.Error(log.SyncMgr, err)
log.Errorln(log.SyncMgr, err)
continue
}
}
@@ -595,7 +595,7 @@ func (m *syncManager) worker() {
}
updateErr := m.Update(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, err)
if updateErr != nil {
log.Error(log.SyncMgr, updateErr)
log.Errorln(log.SyncMgr, updateErr)
}
} else {
time.Sleep(time.Millisecond * 50)
@@ -673,7 +673,7 @@ func (m *syncManager) worker() {
}
updateErr := m.Update(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, err)
if updateErr != nil {
log.Error(log.SyncMgr, updateErr)
log.Errorln(log.SyncMgr, updateErr)
}
}
} else {
@@ -688,7 +688,7 @@ func (m *syncManager) worker() {
m.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, true)
err := m.Update(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, nil)
if err != nil {
log.Error(log.SyncMgr, err)
log.Errorln(log.SyncMgr, err)
}
}
}

View File

@@ -174,7 +174,7 @@ func (m *websocketRoutineManager) websocketDataReceiver(ws *stream.Websocket) er
for x := range m.dataHandlers {
err := m.dataHandlers[x](ws.GetName(), data)
if err != nil {
log.Error(log.WebsocketMgr, err)
log.Errorln(log.WebsocketMgr, err)
}
}
m.mu.RUnlock()
@@ -190,7 +190,7 @@ func (m *websocketRoutineManager) websocketDataReceiver(ws *stream.Websocket) er
func (m *websocketRoutineManager) websocketDataHandler(exchName string, data interface{}) error {
switch d := data.(type) {
case string:
log.Info(log.WebsocketMgr, d)
log.Infoln(log.WebsocketMgr, d)
case error:
return fmt.Errorf("exchange %s websocket error - %s", exchName, data)
case stream.FundingData:
@@ -267,7 +267,7 @@ func (m *websocketRoutineManager) websocketDataHandler(exchName string, data int
case order.ClassificationError:
return fmt.Errorf("%w %s", d.Err, d.Error())
case stream.UnhandledMessageWarning:
log.Warn(log.WebsocketMgr, d.Message)
log.Warnln(log.WebsocketMgr, d.Message)
case account.Change:
if m.verbose {
m.printAccountHoldingsChangeSummary(d)

View File

@@ -21,7 +21,7 @@ func (a *Alphapoint) WebsocketClient() {
var httpResp *http.Response
endpoint, err := a.API.Endpoints.GetURL(exchange.WebsocketSpot)
if err != nil {
log.Error(log.WebsocketMgr, err)
log.Errorln(log.WebsocketMgr, err)
}
a.WebsocketConn, httpResp, err = dialer.Dial(endpoint, http.Header{})
httpResp.Body.Close() // not used, so safely free the body
@@ -38,14 +38,14 @@ func (a *Alphapoint) WebsocketClient() {
err = a.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(`{"messageType": "logon"}`))
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
return
}
for a.Enabled {
msgType, resp, err := a.WebsocketConn.ReadMessage()
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
break
}
@@ -57,7 +57,7 @@ func (a *Alphapoint) WebsocketClient() {
msgType := MsgType{}
err := json.Unmarshal(resp, &msgType)
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
continue
}
@@ -65,7 +65,7 @@ func (a *Alphapoint) WebsocketClient() {
ticker := WebsocketTicker{}
err = json.Unmarshal(resp, &ticker)
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
continue
}
}

View File

@@ -296,7 +296,7 @@ func (b *Binance) batchAggregateTrades(ctx context.Context, arg *AggregatedTrade
err := b.SendHTTPRequest(ctx,
exchange.RestSpotSupplementary, path, spotDefaultRate, &resp)
if err != nil {
log.Warn(log.ExchangeSys, err.Error())
log.Warnln(log.ExchangeSys, err.Error())
return resp, err
}
}

View File

@@ -298,7 +298,7 @@ func (bi *Binanceus) batchAggregateTrades(ctx context.Context, arg *AggregatedTr
err := bi.SendHTTPRequest(ctx,
exchange.RestSpotSupplementary, path, spotDefaultRate, &resp)
if err != nil {
log.Warn(log.ExchangeSys, err.Error())
log.Warnln(log.ExchangeSys, err.Error())
return resp, err
}
}

View File

@@ -224,7 +224,7 @@ func (b *Bitmex) Run(ctx context.Context) {
if b.Verbose {
wsEndpoint, err := b.API.Endpoints.GetURL(exchange.WebsocketSpot)
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
}
log.Debugf(log.ExchangeSys,
"%s Websocket: %s. (url: %s).\n",

View File

@@ -60,7 +60,7 @@ func (c *COINUT) WsConnect() error {
err = c.wsAuthenticate(context.TODO())
if err != nil {
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
log.Error(log.WebsocketMgr, err)
log.Errorln(log.WebsocketMgr, err)
}
// define bi-directional communication

View File

@@ -150,7 +150,7 @@ func (g *Gemini) Setup(exch *config.Exchange) error {
if exch.UseSandbox {
err = g.API.Endpoints.SetRunning(exchange.RestSpot.String(), geminiSandboxAPIURL)
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
}
}

View File

@@ -226,7 +226,7 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error {
}
err := h.Websocket.AuthConn.SendJSONMessage(authPing)
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
}
return nil
}
@@ -446,7 +446,7 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error {
func (h *HUOBI) sendPingResponse(pong int64) {
err := h.Websocket.Conn.SendJSONMessage(WsPong{Pong: pong})
if err != nil {
log.Error(log.ExchangeSys, err)
log.Errorln(log.ExchangeSys, err)
}
}

View File

@@ -152,7 +152,7 @@ func (o *OKCoin) WsHandleData(respRaw []byte) error {
}
}
if o.Verbose {
log.Debug(log.ExchangeSys,
log.Debugln(log.ExchangeSys,
o.Name+" - "+eventResponse.Event+" on channel: "+eventResponse.Channel)
}
}

View File

@@ -374,7 +374,7 @@ func (w *Websocket) connectionMonitor() error {
if w.IsConnected() {
err := w.Shutdown()
if err != nil {
log.Error(log.WebsocketMgr, err)
log.Errorln(log.WebsocketMgr, err)
}
}
if w.verbose {
@@ -402,7 +402,7 @@ func (w *Websocket) connectionMonitor() error {
if !w.IsConnecting() && !w.IsConnected() {
err := w.Connect()
if err != nil {
log.Error(log.WebsocketMgr, err)
log.Errorln(log.WebsocketMgr, err)
}
}
if !timer.Stop() {

View File

@@ -114,7 +114,7 @@ func AddTradesToBuffer(exchangeName string, data ...Data) error {
func (p *Processor) Run(wg *sync.WaitGroup) {
wg.Done()
if !atomic.CompareAndSwapInt32(&p.started, 0, 1) {
log.Error(log.Trade, "trade processor already started")
log.Errorln(log.Trade, "trade processor already started")
return
}
defer func() {
@@ -136,7 +136,7 @@ func (p *Processor) Run(wg *sync.WaitGroup) {
}
err := SaveTradesToDatabase(bufferCopy...)
if err != nil {
log.Error(log.Trade, err)
log.Errorln(log.Trade, err)
}
}
}

View File

@@ -26,7 +26,7 @@ import (
// NewVM attempts to create a new Virtual Machine firstly from pool
func (g *GctScriptManager) NewVM() *VM {
if !g.IsRunning() {
log.Error(log.GCTScriptMgr, Error{
log.Errorln(log.GCTScriptMgr, Error{
Action: "NewVM",
Cause: ErrScriptingDisabled,
})
@@ -34,7 +34,7 @@ func (g *GctScriptManager) NewVM() *VM {
}
newUUID, err := uuid.NewV4()
if err != nil {
log.Error(log.GCTScriptMgr, Error{Action: "New: UUID", Cause: err})
log.Errorln(log.GCTScriptMgr, Error{Action: "New: UUID", Cause: err})
return nil
}
@@ -44,7 +44,7 @@ func (g *GctScriptManager) NewVM() *VM {
s, ok := pool.Get().(*tengo.Script)
if !ok {
log.Error(log.GCTScriptMgr, Error{
log.Errorln(log.GCTScriptMgr, Error{
Action: "NewVM",
Cause: errors.New("unable to type assert tengo script"),
})
@@ -144,30 +144,30 @@ func (vm *VM) CompileAndRun() {
}
err := vm.Compile()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
err = vm.unregister()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
}
return
}
err = vm.RunCtx()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
err = vm.unregister()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
}
return
}
if vm.Compiled.Get("timer").String() != "" {
vm.T, err = time.ParseDuration(vm.Compiled.Get("timer").String())
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
err = vm.Shutdown()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
}
return
}
@@ -177,12 +177,12 @@ func (vm *VM) CompileAndRun() {
}
if vm.T < 0 {
log.Error(log.GCTScriptMgr, "Repeat timer cannot be under 1 nano second")
log.Errorln(log.GCTScriptMgr, "Repeat timer cannot be under 1 nano second")
}
}
err = vm.Shutdown()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
}
}

View File

@@ -18,7 +18,7 @@ func (vm *VM) runner() {
vm.NextRun = time.Now().Add(vm.T)
err := vm.RunCtx()
if err != nil {
log.Error(log.GCTScriptMgr, err)
log.Errorln(log.GCTScriptMgr, err)
return
}
case <-vm.S:

View File

@@ -13,7 +13,7 @@ var (
ErrSubLoggerAlreadyRegistered = errors.New("sub logger already registered")
)
func newLogger(c *Config) Logger {
func newLogger(c *Config, botName string) Logger {
return Logger{
TimestampFormat: c.AdvancedSettings.TimeStampFormat,
Spacer: c.AdvancedSettings.Spacer,
@@ -23,6 +23,7 @@ func newLogger(c *Config) Logger {
DebugHeader: c.AdvancedSettings.Headers.Debug,
ShowLogSystemName: c.AdvancedSettings.ShowLogSystemName != nil && *c.AdvancedSettings.ShowLogSystemName,
BypassJobChannelFilledWarning: c.AdvancedSettings.BypassJobChannelFilledWarning,
botName: botName,
}
}

View File

@@ -1,6 +1,7 @@
package log
import (
"encoding/json"
"errors"
"fmt"
"io"
@@ -9,9 +10,14 @@ import (
)
var (
errWriterAlreadyLoaded = errors.New("io.Writer already loaded")
errJobsChannelIsFull = errors.New("logger jobs channel is filled")
errWriterIsNil = errors.New("io writer is nil")
errWriterAlreadyLoaded = errors.New("io.Writer already loaded")
errJobsChannelIsFull = errors.New("logger jobs channel is filled")
errWriterIsNil = errors.New("io writer is nil")
message Key = "message"
timestamp Key = "timestamp"
severity Key = "severity"
subLoggerName Key = "sublogger"
botName Key = "botname"
)
// loggerWorker handles all work staged to be written to configured io.Writer(s)
@@ -22,28 +28,58 @@ func loggerWorker() {
buffer := make([]byte, 0, defaultBufferCapacity)
var n int
var err error
structuredOutbound := ExtraFields{}
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' {
msg := j.fn()
if j.StructuredLogging {
structuredOutbound[message] = msg
structuredOutbound[timestamp] = time.Now().UnixMilli()
structuredOutbound[severity] = j.Severity
structuredOutbound[subLoggerName] = j.SubLoggerName
structuredOutbound[botName] = j.Instance
for k, v := range j.StructuredFields {
_, ok := structuredOutbound[k]
if ok {
// Disallow overwriting of key values
displayError(fmt.Errorf("structured logging: cannot overwrite key [%s]", k))
continue
}
structuredOutbound[k] = v
}
buffer, err = json.Marshal(structuredOutbound)
if err != nil {
log.Println("log: failed to marshal structured log data:", err)
}
for k := range j.StructuredFields {
// Delete non-persistent structured fields
delete(structuredOutbound, k)
}
buffer = append(buffer, '\n')
} else {
buffer = append(buffer, j.Header...)
if j.ShowLogSystemName {
buffer = append(buffer, j.Spacer...)
buffer = append(buffer, []byte(j.SubLoggerName)...)
}
buffer = append(buffer, j.Spacer...)
if j.TimestampFormat != "" {
buffer = time.Now().AppendFormat(buffer, j.TimestampFormat)
}
buffer = append(buffer, j.Spacer...)
buffer = append(buffer, msg...)
if msg == "" || msg[len(msg)-1] != '\n' {
buffer = append(buffer, '\n')
}
}
for x := range j.Writers {
// NOTE: byte slice is not copied, this is a pointer to the buffer.
// This is only safe if the buffer is not modified after this point.
n, err = j.Writers[x].Write(buffer)
if err != nil {
displayError(fmt.Errorf("%T %w", j.Writers[x], err))
@@ -51,7 +87,7 @@ func loggerWorker() {
displayError(fmt.Errorf("%T %w", j.Writers[x], io.ErrShortWrite))
}
}
buffer = buffer[:0] // Clean buffer
buffer = buffer[:0] // Clean buffer for next use
jobsPool.Put(j)
}
}
@@ -63,15 +99,19 @@ 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) {
func (mw *multiWriterHolder) StageLogEvent(fn deferral, header, slName, spacer, timestampFormat, instance, level string, showLogSystemName, bypassWarning, structuredLog bool, fields map[Key]interface{}) {
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.SubLoggerName = slName
newJob.ShowLogSystemName = showLogSystemName
newJob.Spacer = spacer
newJob.TimestampFormat = timestampFormat
newJob.Instance = instance
newJob.StructuredFields = fields
newJob.StructuredLogging = structuredLog
newJob.Severity = level
select {
case jobsChannel <- newJob:

View File

@@ -147,8 +147,9 @@ func SetupSubLoggers(s []SubLoggerConfig) error {
return nil
}
// SetupGlobalLogger setup the global loggers with the default global config values
func SetupGlobalLogger() error {
// SetupGlobalLogger setup the global loggers with the default global config
// values.
func SetupGlobalLogger(botName string, structuredOutput bool) error {
mu.Lock()
defer mu.Unlock()
@@ -167,12 +168,14 @@ func SetupGlobalLogger() error {
for _, subLogger := range SubLoggers {
subLogger.setLevels(splitLevel(globalLogConfig.Level))
subLogger.structuredLogging = structuredOutput
err = subLogger.setOutput(writers)
if err != nil {
return err
}
subLogger.botName = botName
}
logger = newLogger(globalLogConfig)
logger = newLogger(globalLogConfig, botName)
return nil
}
@@ -213,9 +216,11 @@ func registerNewSubLogger(subLogger string) *SubLogger {
}
temp := &SubLogger{
name: strings.ToUpper(subLogger),
output: tempHolder,
levels: splitLevel("INFO|WARN|DEBUG|ERROR"),
name: strings.ToUpper(subLogger),
output: tempHolder,
levels: splitLevel("INFO|WARN|DEBUG|ERROR"),
botName: logger.botName,
structuredLogging: globalLogConfig != nil && globalLogConfig.AdvancedSettings.StructuredLogging,
}
SubLoggers[subLogger] = temp
return temp

View File

@@ -1,6 +1,7 @@
package log
import (
"encoding/json"
"errors"
"io"
"log"
@@ -9,6 +10,7 @@ import (
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/convert"
)
@@ -76,7 +78,7 @@ func setupTestLoggers() error {
if err != nil {
return err
}
err = SetupGlobalLogger()
err = SetupGlobalLogger("test", false)
if err != nil {
return err
}
@@ -88,7 +90,7 @@ func SetupDisabled() error {
if err != nil {
return err
}
err = SetupGlobalLogger()
err = SetupGlobalLogger("test", false)
if err != nil {
return err
}
@@ -195,17 +197,17 @@ var errWriteError = errors.New("write error")
func TestMultiWriterWrite(t *testing.T) {
t.Parallel()
fields := &logFields{}
f := &fields{}
buff := newTestBuffer()
var err error
fields.output, err = multiWriter(io.Discard, buff)
f.output, err = multiWriter(io.Discard, buff)
if err != nil {
t.Fatal(err)
}
payload := "woooooooooooooooooooooooooooooooooooow"
fields.output.StageLogEvent(func() string { return payload }, "", "", "", "", false, false)
f.output.StageLogEvent(func() string { return payload }, "", "", "", "", "", "", false, false, false, nil)
if err != nil {
t.Fatal(err)
}
@@ -215,17 +217,17 @@ func TestMultiWriterWrite(t *testing.T) {
t.Errorf("received: '%v' but expected: '%v'", contents, payload)
}
fields.output, err = multiWriter(&WriteShorter{}, io.Discard)
f.output, err = multiWriter(&WriteShorter{}, io.Discard)
if err != nil {
t.Fatal(err)
}
fields.output.StageLogEvent(func() string { return payload }, "", "", "", "", false, false) // Will display error: Logger write error: *log.WriteShorter short write
f.output.StageLogEvent(func() string { return payload }, "", "", "", "", "", "", false, false, false, nil) // Will display error: Logger write error: *log.WriteShorter short write
fields.output, err = multiWriter(&WriteError{}, io.Discard)
f.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
f.output.StageLogEvent(func() string { return payload }, "", "", "", "", "", "", false, false, false, nil) // Will display error: Logger write error: *log.WriteError write error
}
func TestGetWriters(t *testing.T) {
@@ -343,8 +345,8 @@ func TestStageNewLogEvent(t *testing.T) {
w := newTestBuffer()
mw := &multiWriterHolder{writers: []io.Writer{w}}
fields := &logFields{output: mw}
fields.output.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", " space ", "", false, false)
f := &fields{output: mw}
f.output.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", " space ", "", "", "", false, false, false, nil)
<-w.Finished
if contents := w.Read(); contents != "header space space out\n" { //nolint:dupword // False positive
@@ -354,6 +356,7 @@ func TestStageNewLogEvent(t *testing.T) {
func TestInfo(t *testing.T) {
t.Parallel()
w := newTestBuffer()
mw := &multiWriterHolder{writers: []io.Writer{w}}
@@ -367,26 +370,22 @@ func TestInfo(t *testing.T) {
t.Fatal(err)
}
Info(sl, "Hello")
<-w.Finished
contents := w.Read()
if !strings.Contains(contents, "Hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
}
Infof(nil, "%s", "bad")
Infof(sl, "%s", "hello")
<-w.Finished
contents = w.Read()
contents := w.Read()
if !strings.Contains(contents, "hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
}
Infoln(nil, "hello", "bad")
Infoln(sl, "hello", "goodbye")
<-w.Finished
contents = w.Read()
if !strings.Contains(contents, "hello goodbye") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello goodbye")
if !strings.Contains(contents, "hellogoodbye") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hellogoodbye")
}
_, err = SetLevel("TESTYMCTESTALOTINFO", "")
@@ -396,12 +395,6 @@ func TestInfo(t *testing.T) {
// 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")
contents = w.Read()
if contents != "" {
@@ -424,26 +417,22 @@ func TestDebug(t *testing.T) {
t.Fatal(err)
}
Debug(sl, "Hello")
<-w.Finished
contents := w.Read()
if !strings.Contains(contents, "Hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
}
Debugf(nil, "%s", "bad")
Debugf(sl, "%s", "hello")
<-w.Finished
contents = w.Read()
contents := w.Read()
if !strings.Contains(contents, "hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
}
Debugln(nil, ":sun_with_face:", "bad")
Debugln(sl, ":sun_with_face:", ":angrysun:")
<-w.Finished
contents = w.Read()
if !strings.Contains(contents, ":sun_with_face: :angrysun:") {
t.Errorf("received: '%v' but expected: '%v'", contents, ":sun_with_face: :angrysun:")
if !strings.Contains(contents, ":sun_with_face::angrysun:") {
t.Errorf("received: '%v' but expected: '%v'", contents, ":sun_with_face::angrysun:")
}
_, err = SetLevel("TESTYMCTESTALOTDEBUG", "")
@@ -453,12 +442,6 @@ func TestDebug(t *testing.T) {
// 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")
contents = w.Read()
if contents != "" {
@@ -481,26 +464,22 @@ func TestWarn(t *testing.T) {
t.Fatal(err)
}
Warn(sl, "Hello")
<-w.Finished
contents := w.Read()
if !strings.Contains(contents, "Hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
}
Warnf(nil, "%s", "silly")
Warnf(sl, "%s", "hello")
<-w.Finished
contents = w.Read()
contents := w.Read()
if !strings.Contains(contents, "hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
}
Warnln(nil, "super", "silly")
Warnln(sl, "hello", "world")
<-w.Finished
contents = w.Read()
if !strings.Contains(contents, "hello world") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello world")
if !strings.Contains(contents, "helloworld") {
t.Errorf("received: '%v' but expected: '%v'", contents, "helloworld")
}
_, err = SetLevel("TESTYMCTESTALOTWARN", "")
@@ -510,12 +489,6 @@ func TestWarn(t *testing.T) {
// 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")
contents = w.Read()
if contents != "" {
@@ -543,26 +516,22 @@ func TestError(t *testing.T) {
t.Fatal(err)
}
Error(sl, "Hello")
<-w.Finished
contents := w.Read()
if !strings.Contains(contents, "Hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "Hello")
}
Errorf(nil, "%s", "oh wow")
Errorf(sl, "%s", "hello")
<-w.Finished
contents = w.Read()
contents := w.Read()
if !strings.Contains(contents, "hello") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello")
}
Errorln(nil, "nil", "days")
Errorln(sl, "hello", "goodbye")
<-w.Finished
contents = w.Read()
if !strings.Contains(contents, "hello goodbye") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hello goodbye")
if !strings.Contains(contents, "hellogoodbye") {
t.Errorf("received: '%v' but expected: '%v'", contents, "hellogoodbye")
}
_, err = SetLevel("TESTYMCTESTALOTERROR", "")
@@ -572,12 +541,6 @@ func TestError(t *testing.T) {
// 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")
contents = w.Read()
if contents != "" {
@@ -602,14 +565,14 @@ func TestSubLoggerName(t *testing.T) {
w := newTestBuffer()
mw := &multiWriterHolder{writers: []io.Writer{w}}
mw.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", "||", time.RFC3339, true, false)
mw.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", "||", "", "", time.RFC3339, true, false, false, nil)
<-w.Finished
contents := w.Read()
if !strings.Contains(contents, "SUBLOGGER") {
t.Error("Expected SUBLOGGER in output")
}
mw.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", "||", time.RFC3339, false, false)
mw.StageLogEvent(func() string { return "out" }, "header", "SUBLOGGER", "||", "", "", time.RFC3339, false, false, false, nil)
<-w.Finished
contents = w.Read()
if strings.Contains(contents, "SUBLOGGER") {
@@ -629,7 +592,7 @@ func TestNewSubLogger(t *testing.T) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
Debug(sl, "testerinos")
Debugln(sl, "testerinos")
_, err = NewSubLogger("TESTERINOS")
if !errors.Is(err, ErrSubLoggerAlreadyRegistered) {
@@ -694,19 +657,28 @@ func TestOpenNew(t *testing.T) {
}
type testBuffer struct {
value string
value []byte
Finished chan struct{}
}
func (tb *testBuffer) Write(p []byte) (int, error) {
tb.value = string(p)
cpy := make([]byte, len(p))
copy(cpy, p)
tb.value = cpy
tb.Finished <- struct{}{}
return len(p), nil
}
func (tb *testBuffer) Read() string {
defer func() { tb.value = "" }()
return tb.value
defer func() { tb.value = tb.value[:0] }()
return string(tb.value)
}
func (tb *testBuffer) ReadRaw() []byte {
defer func() { tb.value = tb.value[:0] }()
cpy := make([]byte, len(tb.value))
copy(cpy, tb.value)
return cpy
}
func newTestBuffer() *testBuffer {
@@ -717,7 +689,7 @@ func newTestBuffer() *testBuffer {
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)
mw.StageLogEvent(func() string { return "somedata" }, "header", "sublog", "||", "", "", time.RFC3339, true, false, false, nil)
}
}
@@ -725,7 +697,7 @@ func BenchmarkNewLogEvent(b *testing.B) {
func BenchmarkInfo(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
Info(Global, "Hello this is an info benchmark")
Infoln(Global, "Hello this is an info benchmark")
}
}
@@ -737,7 +709,7 @@ func BenchmarkInfoDisabled(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
Info(Global, "Hello this is an info benchmark")
Infoln(Global, "Hello this is an info benchmark")
}
}
@@ -756,3 +728,144 @@ func BenchmarkInfoln(b *testing.B) {
Infoln(Global, "Hello this is an infoln benchmark")
}
}
type testCapture struct {
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
Severity string `json:"severity"`
SubLogger string `json:"sublogger"`
BotName string `json:"botname"`
ID uuid.UUID `json:"id"`
}
func TestWithFields(t *testing.T) {
t.Parallel()
writer := newTestBuffer()
mwh := &multiWriterHolder{writers: []io.Writer{writer}}
sl, err := NewSubLogger("TESTSTRUCTUREDLOGGING")
if err != nil {
t.Fatal(err)
}
sl.structuredLogging = true
sl.setLevelsProtected(splitLevel("DEBUG|ERROR|INFO|WARN"))
err = sl.setOutputProtected(mwh)
if err != nil {
t.Fatal(err)
}
id, err := uuid.NewV4()
if err != nil {
t.Fatal(err)
}
ErrorlnWithFields(nil, ExtraFields{"id": id}, "nilerinos")
ErrorlnWithFields(sl, ExtraFields{"id": id}, "hello")
<-writer.Finished
var captured testCapture
bro := writer.ReadRaw()
err = json.Unmarshal(bro, &captured)
if err != nil {
t.Fatal(err, string(bro))
}
checkCapture(t, &captured, id, "hello", "error")
ErrorfWithFields(nil, ExtraFields{"id": id}, "%v", "nilerinos")
ErrorfWithFields(sl, ExtraFields{"id": id}, "%v", "good")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "good", "error")
DebuglnWithFields(nil, ExtraFields{"id": id}, "nilerinos")
DebuglnWithFields(sl, ExtraFields{"id": id}, "sir")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "sir", "debug")
DebugfWithFields(nil, ExtraFields{"id": id}, "%v", "nilerinos")
DebugfWithFields(sl, ExtraFields{"id": id}, "%v", "how")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "how", "debug")
WarnlnWithFields(nil, ExtraFields{"id": id}, "nilerinos")
WarnlnWithFields(sl, ExtraFields{"id": id}, "are")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "are", "warn")
WarnfWithFields(nil, ExtraFields{"id": id}, "%v", "nilerinos")
WarnfWithFields(sl, ExtraFields{"id": id}, "%v", "you")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "you", "warn")
InfolnWithFields(nil, ExtraFields{"id": id}, "nilerinos")
InfolnWithFields(sl, ExtraFields{"id": id}, "today")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "today", "info")
InfofWithFields(nil, ExtraFields{"id": id}, "%v", "nilerinos")
InfofWithFields(sl, ExtraFields{"id": id}, "%v", "?")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
checkCapture(t, &captured, id, "?", "info")
// Conflicting fields
InfofWithFields(nil, ExtraFields{botName: "lol"}, "%v", "nilerinos")
InfofWithFields(sl, ExtraFields{botName: "lol"}, "%v", "?")
<-writer.Finished
err = json.Unmarshal(writer.ReadRaw(), &captured)
if err != nil {
t.Fatal(err)
}
if captured.BotName != "test" {
t.Fatalf("received: '%v' but expected: '%v'", captured.BotName, "test")
}
}
func checkCapture(t *testing.T, c *testCapture, expID uuid.UUID, expMessage, expSeverity string) {
t.Helper()
if c.ID != expID {
t.Errorf("received: '%v' but expected: '%v'", c.ID, expID)
}
if c.Message != expMessage {
t.Errorf("received: '%v' but expected: '%v'", c.Message, expMessage)
}
if c.Severity != expSeverity {
t.Errorf("received: '%v' but expected: '%v'", c.Severity, expSeverity)
}
if c.SubLogger != "TESTSTRUCTUREDLOGGING" {
t.Errorf("received: '%v' but expected: '%v'", c.SubLogger, "TESTSTRUCTUREDLOGGING")
}
if c.BotName != "test" {
t.Errorf("received: '%v' but expected: '%v'", c.BotName, "test")
}
}

View File

@@ -31,7 +31,7 @@ var (
// 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} }}
logFieldsPool = &sync.Pool{New: func() interface{} { return &fields{logger: logger} }}
// LogPath system path to store log files in
logPath string
@@ -44,10 +44,14 @@ type job struct {
Writers []io.Writer
fn deferral
Header string
SlName string
SubLoggerName string
Spacer string
TimestampFormat string
ShowLogSystemName bool
Instance string
StructuredFields map[Key]interface{}
StructuredLogging bool
Severity string
Passback chan<- struct{}
}
@@ -66,6 +70,7 @@ type advancedSettings struct {
TimeStampFormat string `json:"timeStampFormat"`
Headers headers `json:"headers"`
BypassJobChannelFilledWarning bool `json:"bypassJobChannelFilledWarning"`
StructuredLogging bool `json:"structuredLogging"`
}
type headers struct {
@@ -95,6 +100,8 @@ type Logger struct {
TimestampFormat string
InfoHeader, ErrorHeader, DebugHeader, WarnHeader string
Spacer string
Level string
botName string
}
// Levels flags for each sub logger type
@@ -105,3 +112,11 @@ type Levels struct {
type multiWriterHolder struct {
writers []io.Writer
}
// ExtraFields is a map of key value pairs that can be added to a structured
// log output.
type ExtraFields map[Key]interface{}
// Key is used for structured logging fields to ensure no collisions occur.
// Unexported keys are default fields which cannot be overwritten.
type Key string

View File

@@ -5,244 +5,213 @@ import (
"log"
)
// Info takes a pointer subLogger struct and string sends to StageLogEvent
func Info(sl *SubLogger, data string) {
// Infoln is a logging function that takes a sublogger and an arbitrary number
// of interface{} arguments. This writes to configured io.Writer(s) as an
// information message using default formats for its operands. A new line is
// automatically added to the output.
func Infoln(sl *SubLogger, a ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.stageln(f.logger.InfoHeader, a...)
}
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 StageLogEvent
func Infoln(sl *SubLogger, v ...interface{}) {
// InfolnWithFields is a logging function that takes a sublogger, additional
// structured logging fields and an arbitrary number of interface{} arguments.
// This writes to configured io.Writer(s) as an information message using
// default formats for its operands. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func InfolnWithFields(sl *SubLogger, extra ExtraFields, a ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stageln(f.logger.InfoHeader, a...)
}
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 and interface formats sends to StageLogEvent
func Infof(sl *SubLogger, data string, v ...interface{}) {
// Infof is a logging function that takes a sublogger, a format string along
// with optional arguments. This writes to configured io.Writer(s) as an
// information message which formats according to the format specifier.
// A new line is automatically added to the output.
func Infof(sl *SubLogger, format string, a ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
sl.getFields().stagef(f.logger.InfoHeader, format, a...)
}
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 StageLogEvent
func Debug(sl *SubLogger, data string) {
// InfofWithFields is a logging function that takes a sublogger, additional
// structured logging fields, a format string along with optional arguments.
// This writes to configured io.Writer(s) as an information message which
// formats according to the format specifier. A new line is automatically added
// to the output. If structured logging is not enabled, the fields will be
// ignored.
func InfofWithFields(sl *SubLogger, extra ExtraFields, format string, a ...interface{}) { //nolint:goprintffuncname // False positive
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stagef(f.logger.InfoHeader, format, a...)
}
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 StageLogEvent
// Debugln is a logging function that takes a sublogger and an arbitrary number
// of interface{} arguments. This writes to configured io.Writer(s) as an
// debug message using default formats for its operands. A new line is
// automatically added to the output.
func Debugln(sl *SubLogger, v ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.stageln(f.logger.DebugHeader, v...)
}
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 and interface formats sends to StageLogEvent
// DebuglnWithFields is a logging function that takes a sublogger, additional
// structured logging fields and an arbitrary number of interface{} arguments.
// This writes to configured io.Writer(s) as an debug message using default
// formats for its operands. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func DebuglnWithFields(sl *SubLogger, extra ExtraFields, a ...interface{}) {
mu.RLock()
defer mu.RUnlock()
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stageln(f.logger.DebugHeader, a...)
}
}
// Debugf is a logging function that takes a sublogger, a format string along
// with optional arguments. This writes to configured io.Writer(s) as an
// debug message which formats according to the format specifier. A new line is
// automatically added to the output.
func Debugf(sl *SubLogger, data string, v ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
sl.getFields().stagef(f.logger.DebugHeader, data, v...)
}
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 StageLogEvent
func Warn(sl *SubLogger, data string) {
// DebugfWithFields is a logging function that takes a sublogger, additional
// structured logging fields, a format string along with optional arguments.
// This writes to configured io.Writer(s) as an debug message which formats
// according to the format specifier. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func DebugfWithFields(sl *SubLogger, extra ExtraFields, format string, a ...interface{}) { //nolint:goprintffuncname // False positive
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stagef(f.logger.DebugHeader, format, a...)
}
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 StageLogEvent
// Warnln is a logging function that takes a sublogger and an arbitrary number
// of interface{} arguments. This writes to configured io.Writer(s) as an
// warning message using default formats for its operands. A new line is
// automatically added to the output.
func Warnln(sl *SubLogger, v ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.stageln(f.logger.WarnHeader, v...)
}
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 and interface formats sends to StageLogEvent
// WarnlnWithFields is a logging function that takes a sublogger, additional
// structured logging fields and an arbitrary number of interface{} arguments.
// This writes to configured io.Writer(s) as an warning message using default
// formats for its operands. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func WarnlnWithFields(sl *SubLogger, extra ExtraFields, a ...interface{}) {
mu.RLock()
defer mu.RUnlock()
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stageln(f.logger.WarnHeader, a...)
}
}
// Warnf is a logging function that takes a sublogger, a format string along
// with optional arguments. This writes to configured io.Writer(s) as an
// warning message which formats according to the format specifier. A new line
// is automatically added to the output.
func Warnf(sl *SubLogger, data string, v ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
sl.getFields().stagef(f.logger.WarnHeader, data, v...)
}
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 StageLogEvent
func Error(sl *SubLogger, data ...interface{}) {
// WarnfWithFields is a logging function that takes a sublogger, additional
// structured logging fields, a format string along with optional arguments.
// This writes to configured io.Writer(s) as an warning message which formats
// according to the format specifier. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func WarnfWithFields(sl *SubLogger, extra ExtraFields, format string, a ...interface{}) { //nolint:goprintffuncname // False positive
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stagef(f.logger.WarnHeader, format, a...)
}
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 StageLogEvent
// Errorln is a logging function that takes a sublogger and an arbitrary number
// of interface{} arguments. This writes to configured io.Writer(s) as an
// error message using default formats for its operands. A new line is
// automatically added to the output.
func Errorln(sl *SubLogger, v ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
f.stageln(f.logger.ErrorHeader, v...)
}
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 and interface formats sends to StageLogEvent
// ErrorlnWithFields is a logging function that takes a sublogger, additional
// structured logging fields and an arbitrary number of interface{} arguments.
// This writes to configured io.Writer(s) as an error message using default
// formats for its operands. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func ErrorlnWithFields(sl *SubLogger, extra ExtraFields, a ...interface{}) {
mu.RLock()
defer mu.RUnlock()
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stageln(f.logger.ErrorHeader, a...)
}
}
// Errorf is a logging function that takes a sublogger, a format string along
// with optional arguments. This writes to configured io.Writer(s) as an
// error message which formats according to the format specifier. A new line
// is automatically added to the output.
func Errorf(sl *SubLogger, data string, v ...interface{}) {
mu.RLock()
defer mu.RUnlock()
fields := sl.getFields()
if fields == nil {
return
if f := sl.getFields(); f != nil {
sl.getFields().stagef(f.logger.ErrorHeader, data, v...)
}
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)
}
// ErrorfWithFields is a logging function that takes a sublogger, additional
// structured logging fields, a format string along with optional arguments.
// This writes to configured io.Writer(s) as an error message which formats
// according to the format specifier. A new line is automatically added to the
// output. If structured logging is not enabled, the fields will be ignored.
func ErrorfWithFields(sl *SubLogger, extra ExtraFields, format string, a ...interface{}) { //nolint:goprintffuncname // False positive
mu.RLock()
defer mu.RUnlock()
if f := sl.getFields(); f != nil {
f.structuredFields = extra
f.stagef(f.logger.ErrorHeader, format, a...)
}
logFieldsPool.Put(fields)
}
func displayError(err error) {
@@ -250,3 +219,57 @@ func displayError(err error) {
log.Printf("Logger write error: %v\n", err)
}
}
// enabled checks if the log level is enabled
func (l *fields) enabled(header string) string {
switch header {
case l.logger.InfoHeader:
if l.info {
return "info"
}
case l.logger.WarnHeader:
if l.warn {
return "warn"
}
case l.logger.ErrorHeader:
if l.error {
return "error"
}
case l.logger.DebugHeader:
if l.debug {
return "debug"
}
}
return ""
}
// stage stages a log event
func (l *fields) stage(header string, deferFunc deferral) {
if l == nil {
return
}
if level := l.enabled(header); level != "" {
l.output.StageLogEvent(deferFunc,
header,
l.name,
l.logger.Spacer,
l.logger.TimestampFormat,
l.botName,
level,
l.logger.ShowLogSystemName,
l.logger.BypassJobChannelFilledWarning,
l.structuredLogging,
l.structuredFields)
}
logFieldsPool.Put(l)
}
// stageln stages a log event
func (l *fields) stageln(header string, a ...interface{}) {
l.stage(header, func() string { return fmt.Sprint(a...) })
}
// stagef stages a log event
func (l *fields) stagef(header, format string, a ...interface{}) {
l.stage(header, func() string { return fmt.Sprintf(format, a...) })
}

View File

@@ -38,17 +38,18 @@ func (sl *SubLogger) setLevels(newLevels 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 {
func (sl *SubLogger) getFields() *fields {
if sl == nil || globalLogConfig == nil || globalLogConfig.Enabled == nil || !*globalLogConfig.Enabled {
return nil
}
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
f := logFieldsPool.Get().(*fields) //nolint:forcetypeassert // Not necessary from a pool
f.info = sl.levels.Info
f.warn = sl.levels.Warn
f.debug = sl.levels.Debug
f.error = sl.levels.Error
f.name = sl.name
f.output = sl.output
f.botName = sl.botName
f.structuredLogging = sl.structuredLogging
return f
}

View File

@@ -35,19 +35,24 @@ var (
// 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 *multiWriterHolder
name string
levels Levels
output *multiWriterHolder
botName string
structuredLogging bool
}
// logFields is used to store data in a non-global and thread-safe manner
// fields 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 {
info bool
warn bool
debug bool
error bool
name string
output *multiWriterHolder
logger Logger
type fields struct {
info bool
warn bool
debug bool
error bool
structuredLogging bool
name string
output *multiWriterHolder
logger Logger
botName string
structuredFields ExtraFields
}