diff --git a/cointop/cache.go b/cointop/cache.go index a46eef2..74925e1 100644 --- a/cointop/cache.go +++ b/cointop/cache.go @@ -4,17 +4,19 @@ import ( "fmt" "strings" "time" + + log "github.com/sirupsen/logrus" ) // CacheKey returns cached value given key func (ct *Cointop) CacheKey(key string) string { - ct.debuglog("CacheKey()") + log.Debug("CacheKey()") return strings.ToLower(fmt.Sprintf("%s_%s", ct.apiChoice, key)) } // CacheAllCoinsSlugMap writes the coins map to the memory and disk cache func (ct *Cointop) CacheAllCoinsSlugMap() { - ct.debuglog("CacheAllCoinsSlugMap()") + log.Debug("CacheAllCoinsSlugMap()") allCoinsSlugMap := make(map[string]*Coin) ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { allCoinsSlugMap[key.(string)] = value.(*Coin) diff --git a/cointop/chart.go b/cointop/chart.go index 8b4a54c..f0094c7 100644 --- a/cointop/chart.go +++ b/cointop/chart.go @@ -10,6 +10,7 @@ import ( "github.com/miguelmota/cointop/pkg/chartplot" "github.com/miguelmota/cointop/pkg/timeutil" "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" ) // ChartView is structure for chart view @@ -58,7 +59,7 @@ func ChartRangesMap() map[string]time.Duration { // UpdateChart updates the chart view func (ct *Cointop) UpdateChart() error { - ct.debuglog("UpdateChart()") + log.Debug("UpdateChart()") chartLock.Lock() defer chartLock.Unlock() @@ -97,7 +98,7 @@ func (ct *Cointop) UpdateChart() error { // ChartPoints calculates the the chart points func (ct *Cointop) ChartPoints(symbol string, name string) error { - ct.debuglog("ChartPoints()") + log.Debug("ChartPoints()") maxX := ct.ChartWidth() chartPointsLock.Lock() @@ -132,7 +133,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error { if found { // cache hit data, _ = cached.([]float64) - ct.debuglog("ChartPoints() soft cache hit") + log.Debug("ChartPoints() soft cache hit") } if len(data) == 0 { @@ -178,7 +179,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error { // PortfolioChart renders the portfolio chart func (ct *Cointop) PortfolioChart() error { - ct.debuglog("PortfolioChart()") + log.Debug("PortfolioChart()") maxX := ct.ChartWidth() chartPointsLock.Lock() defer chartPointsLock.Unlock() @@ -223,7 +224,7 @@ func (ct *Cointop) PortfolioChart() error { if found { // cache hit graphData, _ = cached.([]float64) - ct.debuglog("PortfolioChart() soft cache hit") + log.Debug("PortfolioChart() soft cache hit") } else { if ct.filecache != nil { ct.filecache.Get(cachekey, &graphData) @@ -273,7 +274,7 @@ func (ct *Cointop) PortfolioChart() error { // ShortenChart decreases the chart height by one row func (ct *Cointop) ShortenChart() error { - ct.debuglog("ShortenChart()") + log.Debug("ShortenChart()") candidate := ct.State.chartHeight - 1 if candidate < 5 { return nil @@ -287,7 +288,7 @@ func (ct *Cointop) ShortenChart() error { // EnlargeChart increases the chart height by one row func (ct *Cointop) EnlargeChart() error { - ct.debuglog("EnlargeChart()") + log.Debug("EnlargeChart()") candidate := ct.State.lastChartHeight + 1 if candidate > 30 { return nil @@ -301,7 +302,7 @@ func (ct *Cointop) EnlargeChart() error { // NextChartRange sets the chart to the next range option func (ct *Cointop) NextChartRange() error { - ct.debuglog("NextChartRange()") + log.Debug("NextChartRange()") sel := 0 max := len(ct.chartRanges) for i, k := range ct.chartRanges { @@ -322,7 +323,7 @@ func (ct *Cointop) NextChartRange() error { // PrevChartRange sets the chart to the prevous range option func (ct *Cointop) PrevChartRange() error { - ct.debuglog("PrevChartRange()") + log.Debug("PrevChartRange()") sel := 0 for i, k := range ct.chartRanges { if k == ct.State.selectedChartRange { @@ -341,7 +342,7 @@ func (ct *Cointop) PrevChartRange() error { // FirstChartRange sets the chart to the first range option func (ct *Cointop) FirstChartRange() error { - ct.debuglog("FirstChartRange()") + log.Debug("FirstChartRange()") ct.State.selectedChartRange = ct.chartRanges[0] go ct.UpdateChart() return nil @@ -349,7 +350,7 @@ func (ct *Cointop) FirstChartRange() error { // LastChartRange sets the chart to the last range option func (ct *Cointop) LastChartRange() error { - ct.debuglog("LastChartRange()") + log.Debug("LastChartRange()") ct.State.selectedChartRange = ct.chartRanges[len(ct.chartRanges)-1] go ct.UpdateChart() return nil @@ -357,7 +358,7 @@ func (ct *Cointop) LastChartRange() error { // ToggleCoinChart toggles between the global chart and the coin chart func (ct *Cointop) ToggleCoinChart() error { - ct.debuglog("ToggleCoinChart()") + log.Debug("ToggleCoinChart()") highlightedcoin := ct.HighlightedRowCoin() if ct.State.selectedCoin == highlightedcoin { ct.State.selectedCoin = nil @@ -379,7 +380,7 @@ func (ct *Cointop) ToggleCoinChart() error { // ShowChartLoader shows chart loading indicator func (ct *Cointop) ShowChartLoader() error { - ct.debuglog("ShowChartLoader()") + log.Debug("ShowChartLoader()") ct.UpdateUI(func() error { content := "\n\nLoading..." return ct.Views.Chart.Update(ct.colorscheme.Chart(content)) @@ -390,7 +391,7 @@ func (ct *Cointop) ShowChartLoader() error { // ChartWidth returns the width for chart func (ct *Cointop) ChartWidth() int { - ct.debuglog("ChartWidth()") + log.Debug("ChartWidth()") w := ct.Width() max := 175 if w > max { @@ -402,7 +403,7 @@ func (ct *Cointop) ChartWidth() int { // ToggleChartFullscreen toggles the chart fullscreen mode func (ct *Cointop) ToggleChartFullscreen() error { - ct.debuglog("ToggleChartFullscreen()") + log.Debug("ToggleChartFullscreen()") ct.State.onlyChart = !ct.State.onlyChart ct.State.onlyTable = false if !ct.State.onlyChart { diff --git a/cointop/coin.go b/cointop/coin.go index ad01a32..ef8f4fd 100644 --- a/cointop/coin.go +++ b/cointop/coin.go @@ -1,5 +1,7 @@ package cointop +import log "github.com/sirupsen/logrus" + // Coin is the row structure type Coin struct { ID string @@ -27,7 +29,7 @@ type Coin struct { // AllCoins returns a slice of all the coins func (ct *Cointop) AllCoins() []*Coin { - ct.debuglog("AllCoins()") + log.Debug("AllCoins()") if ct.IsFavoritesVisible() { var list []*Coin for i := range ct.State.allCoins { @@ -55,7 +57,7 @@ func (ct *Cointop) AllCoins() []*Coin { // CoinBySymbol returns the coin struct given the symbol func (ct *Cointop) CoinBySymbol(symbol string) *Coin { - ct.debuglog("CoinBySymbol()") + log.Debug("CoinBySymbol()") for i := range ct.State.allCoins { coin := ct.State.allCoins[i] if coin.Symbol == symbol { @@ -67,7 +69,7 @@ func (ct *Cointop) CoinBySymbol(symbol string) *Coin { // CoinByName returns the coin struct given the name func (ct *Cointop) CoinByName(name string) *Coin { - ct.debuglog("CoinByName()") + log.Debug("CoinByName()") for i := range ct.State.allCoins { coin := ct.State.allCoins[i] if coin.Name == name { @@ -79,7 +81,7 @@ func (ct *Cointop) CoinByName(name string) *Coin { // CoinByID returns the coin struct given the ID func (ct *Cointop) CoinByID(id string) *Coin { - ct.debuglog("CoinByID()") + log.Debug("CoinByID()") for i := range ct.State.allCoins { coin := ct.State.allCoins[i] if coin.ID == id { diff --git a/cointop/cointop.go b/cointop/cointop.go index d9bbebe..8efbcfd 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -17,6 +17,7 @@ import ( "github.com/miguelmota/cointop/pkg/table" "github.com/miguelmota/cointop/pkg/ui" "github.com/miguelmota/gocui" + log "github.com/sirupsen/logrus" ) // TODO: clean up and optimize codebase @@ -103,7 +104,6 @@ type Cointop struct { chartRangesMap map[string]time.Duration colorschemeName string colorscheme *Colorscheme - debug bool filecache *filecache.FileCache logfile *os.File forceRefresh chan bool @@ -200,9 +200,11 @@ var DefaultColorsDir = fmt.Sprintf("%s/colors", DefaultConfigFilepath) // NewCointop initializes cointop func NewCointop(config *Config) (*Cointop, error) { - var debug bool + // var debug bool + x := log.GetLevel() + log.Debug(x) if os.Getenv("DEBUG") != "" { - debug = true + log.SetLevel(log.DebugLevel) } if config == nil { @@ -235,7 +237,6 @@ func NewCointop(config *Config) (*Cointop, error) { colorsDir: config.ColorsDir, configFilepath: configFilepath, chartRanges: ChartRanges(), - debug: debug, chartRangesMap: ChartRangesMap(), limiter: time.NewTicker(2 * time.Second).C, filecache: nil, @@ -289,9 +290,7 @@ func NewCointop(config *Config) (*Cointop, error) { Input: NewInputView(), }, } - if debug { - ct.initlog() - } + ct.initlog() err := ct.SetupConfig() if err != nil { @@ -470,7 +469,7 @@ func NewCointop(config *Config) (*Cointop, error) { // Run runs cointop func (ct *Cointop) Run() error { - ct.debuglog("Run()") + log.Debug("Run()") ui, err := ui.NewUI() if err != nil { return err diff --git a/cointop/config.go b/cointop/config.go index 2b7ed83..055bf49 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -14,6 +14,7 @@ import ( "github.com/miguelmota/cointop/pkg/pathutil" "github.com/miguelmota/cointop/pkg/toml" + log "github.com/sirupsen/logrus" ) // FilePerm is the default file permissions @@ -51,7 +52,7 @@ type ConfigFileConfig struct { // SetupConfig loads config file func (ct *Cointop) SetupConfig() error { - ct.debuglog("SetupConfig()") + log.Debug("SetupConfig()") if err := ct.CreateConfigIfNotExists(); err != nil { return err } @@ -103,7 +104,7 @@ func (ct *Cointop) SetupConfig() error { // CreateConfigIfNotExists creates config file if it doesn't exist func (ct *Cointop) CreateConfigIfNotExists() error { - ct.debuglog("CreateConfigIfNotExists()") + log.Debug("CreateConfigIfNotExists()") ct.configFilepath = pathutil.NormalizePath(ct.configFilepath) @@ -133,7 +134,7 @@ func (ct *Cointop) CreateConfigIfNotExists() error { // ConfigDirPath returns the config directory path func (ct *Cointop) ConfigDirPath() string { - ct.debuglog("ConfigDirPath()") + log.Debug("ConfigDirPath()") path := pathutil.NormalizePath(ct.configFilepath) separator := string(filepath.Separator) parts := strings.Split(path, separator) @@ -142,13 +143,13 @@ func (ct *Cointop) ConfigDirPath() string { // ConfigFilePath return the config file path func (ct *Cointop) ConfigFilePath() string { - ct.debuglog("ConfigFilePath()") + log.Debug("ConfigFilePath()") return pathutil.NormalizePath(ct.configFilepath) } // ConfigPath return the config file path func (ct *Cointop) MakeConfigDir() error { - ct.debuglog("MakeConfigDir()") + log.Debug("MakeConfigDir()") path := ct.ConfigDirPath() if _, err := os.Stat(path); os.IsNotExist(err) { return os.MkdirAll(path, os.ModePerm) @@ -159,7 +160,7 @@ func (ct *Cointop) MakeConfigDir() error { // MakeConfigFile creates a new config file func (ct *Cointop) MakeConfigFile() error { - ct.debuglog("MakeConfigFile()") + log.Debug("MakeConfigFile()") path := ct.ConfigFilePath() if _, err := os.Stat(path); os.IsNotExist(err) { fo, err := os.Create(path) @@ -180,7 +181,7 @@ func (ct *Cointop) MakeConfigFile() error { // SaveConfig writes settings to the config file func (ct *Cointop) SaveConfig() error { - ct.debuglog("SaveConfig()") + log.Debug("SaveConfig()") ct.saveMux.Lock() defer ct.saveMux.Unlock() path := ct.ConfigFilePath() @@ -199,7 +200,7 @@ func (ct *Cointop) SaveConfig() error { // ParseConfig decodes the toml config file func (ct *Cointop) ParseConfig() error { - ct.debuglog("ParseConfig()") + log.Debug("ParseConfig()") var conf ConfigFileConfig path := ct.configFilepath if _, err := toml.DecodeFile(path, &conf); err != nil { @@ -212,7 +213,7 @@ func (ct *Cointop) ParseConfig() error { // ConfigToToml encodes config struct to TOML func (ct *Cointop) ConfigToToml() ([]byte, error) { - ct.debuglog("ConfigToToml()") + log.Debug("ConfigToToml()") shortcutsIfcs := map[string]interface{}{} for k, v := range ct.State.shortcutKeys { var i interface{} = v @@ -324,7 +325,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) { // LoadTableConfig loads table config from toml config into state struct func (ct *Cointop) loadTableConfig() error { - ct.debuglog("loadTableConfig()") + log.Debug("loadTableConfig()") err := ct.loadTableColumnsFromConfig() if err != nil { return err @@ -339,7 +340,7 @@ func (ct *Cointop) loadTableConfig() error { // LoadTableColumnsFromConfig loads preferred coins table columns from config file to struct func (ct *Cointop) loadTableColumnsFromConfig() error { - ct.debuglog("loadTableColumnsFromConfig()") + log.Debug("loadTableColumnsFromConfig()") columnsIfc, ok := ct.config.Table["columns"] if !ok { return nil @@ -365,7 +366,7 @@ func (ct *Cointop) loadTableColumnsFromConfig() error { // LoadShortcutsFromConfig loads keyboard shortcuts from config file to struct func (ct *Cointop) loadShortcutsFromConfig() error { - ct.debuglog("loadShortcutsFromConfig()") + log.Debug("loadShortcutsFromConfig()") for k, ifc := range ct.config.Shortcuts { if v, ok := ifc.(string); ok { if !ct.ActionExists(v) { @@ -379,7 +380,7 @@ func (ct *Cointop) loadShortcutsFromConfig() error { // LoadCurrencyFromConfig loads currency from config file to struct func (ct *Cointop) loadCurrencyFromConfig() error { - ct.debuglog("loadCurrencyFromConfig()") + log.Debug("loadCurrencyFromConfig()") if currency, ok := ct.config.Currency.(string); ok { ct.State.currencyConversion = strings.ToUpper(currency) } @@ -388,7 +389,7 @@ func (ct *Cointop) loadCurrencyFromConfig() error { // LoadDefaultViewFromConfig loads default view from config file to struct func (ct *Cointop) loadDefaultViewFromConfig() error { - ct.debuglog("loadDefaultViewFromConfig()") + log.Debug("loadDefaultViewFromConfig()") if defaultView, ok := ct.config.DefaultView.(string); ok { defaultView = strings.ToLower(defaultView) switch defaultView { @@ -411,7 +412,7 @@ func (ct *Cointop) loadDefaultViewFromConfig() error { // LoadDefaultChartRangeFromConfig loads default chart range from config file to struct func (ct *Cointop) loadDefaultChartRangeFromConfig() error { - ct.debuglog("loadDefaultChartRangeFromConfig()") + log.Debug("loadDefaultChartRangeFromConfig()") if defaultChartRange, ok := ct.config.DefaultChartRange.(string); ok { // validate configured value _, ok := ct.chartRangesMap[defaultChartRange] @@ -426,7 +427,7 @@ func (ct *Cointop) loadDefaultChartRangeFromConfig() error { // LoadAPIKeysFromConfig loads API keys from config file to struct func (ct *Cointop) loadAPIKeysFromConfig() error { - ct.debuglog("loadAPIKeysFromConfig()") + log.Debug("loadAPIKeysFromConfig()") for key, value := range ct.config.CoinMarketCap { k := strings.TrimSpace(strings.ToLower(key)) if k == "pro_api_key" { @@ -438,7 +439,7 @@ func (ct *Cointop) loadAPIKeysFromConfig() error { // LoadColorschemeFromConfig loads colorscheme name from config file to struct func (ct *Cointop) loadColorschemeFromConfig() error { - ct.debuglog("loadColorschemeFromConfig()") + log.Debug("loadColorschemeFromConfig()") if colorscheme, ok := ct.config.Colorscheme.(string); ok { ct.colorschemeName = colorscheme } @@ -448,7 +449,7 @@ func (ct *Cointop) loadColorschemeFromConfig() error { // LoadRefreshRateFromConfig loads refresh rate from config file to struct func (ct *Cointop) loadRefreshRateFromConfig() error { - ct.debuglog("loadRefreshRateFromConfig()") + log.Debug("loadRefreshRateFromConfig()") if refreshRate, ok := ct.config.RefreshRate.(int64); ok { ct.State.refreshRate = time.Duration(uint(refreshRate)) * time.Second } @@ -458,7 +459,7 @@ func (ct *Cointop) loadRefreshRateFromConfig() error { // LoadCacheDirFromConfig loads cache dir from config file to struct func (ct *Cointop) loadCacheDirFromConfig() error { - ct.debuglog("loadCacheDirFromConfig()") + log.Debug("loadCacheDirFromConfig()") if cacheDir, ok := ct.config.CacheDir.(string); ok { ct.State.cacheDir = pathutil.NormalizePath(cacheDir) } @@ -468,7 +469,7 @@ func (ct *Cointop) loadCacheDirFromConfig() error { // LoadAPIChoiceFromConfig loads API choices from config file to struct func (ct *Cointop) loadAPIChoiceFromConfig() error { - ct.debuglog("loadAPIKeysFromConfig()") + log.Debug("loadAPIKeysFromConfig()") apiChoice, ok := ct.config.API.(string) if ok { apiChoice = strings.TrimSpace(strings.ToLower(apiChoice)) @@ -479,7 +480,7 @@ func (ct *Cointop) loadAPIChoiceFromConfig() error { // LoadFavoritesFromConfig loads favorites data from config file to struct func (ct *Cointop) loadFavoritesFromConfig() error { - ct.debuglog("loadFavoritesFromConfig()") + log.Debug("loadFavoritesFromConfig()") for k, valueIfc := range ct.config.Favorites { ifcs, ok := valueIfc.([]interface{}) if !ok { @@ -521,7 +522,7 @@ func (ct *Cointop) loadFavoritesFromConfig() error { // LoadPortfolioFromConfig loads portfolio data from config file to struct func (ct *Cointop) loadPortfolioFromConfig() error { - ct.debuglog("loadPortfolioFromConfig()") + log.Debug("loadPortfolioFromConfig()") for key, valueIfc := range ct.config.Portfolio { if key == "columns" { @@ -586,7 +587,7 @@ func (ct *Cointop) loadPortfolioFromConfig() error { // LoadPriceAlertsFromConfig loads price alerts from config file to struct func (ct *Cointop) loadPriceAlertsFromConfig() error { - ct.debuglog("loadPriceAlertsFromConfig()") + log.Debug("loadPriceAlertsFromConfig()") priceAlertsIfc, ok := ct.config.PriceAlerts["alerts"] if !ok { return nil @@ -646,7 +647,7 @@ func (ct *Cointop) loadPriceAlertsFromConfig() error { // GetColorschemeColors loads colors from colorsheme file to struct func (ct *Cointop) GetColorschemeColors() (map[string]interface{}, error) { - ct.debuglog("GetColorschemeColors()") + log.Debug("GetColorschemeColors()") var colors map[string]interface{} if ct.colorschemeName == "" { ct.colorschemeName = DefaultColorscheme diff --git a/cointop/conversion.go b/cointop/conversion.go index be12ce4..334b54a 100644 --- a/cointop/conversion.go +++ b/cointop/conversion.go @@ -8,6 +8,7 @@ import ( color "github.com/miguelmota/cointop/pkg/color" "github.com/miguelmota/cointop/pkg/pad" + log "github.com/sirupsen/logrus" ) // FiatCurrencyNames is a mpa of currency symbols to names. @@ -151,7 +152,7 @@ func (ct *Cointop) SortedSupportedCurrencyConversions() []string { // UpdateConvertMenu updates the convert menu func (ct *Cointop) UpdateConvertMenu() error { - ct.debuglog("UpdateConvertMenu()") + log.Debug("UpdateConvertMenu()") header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Currency Conversion %s\n\n", pad.Left("[q] close ", ct.Width()-24, " "))) helpline := " Press the corresponding key to select currency for conversion\n\n" cnt := 0 @@ -226,7 +227,7 @@ func (ct *Cointop) SetCurrencyConverstion(convert string) error { // SetCurrencyConverstionFn sets the currency conversion function func (ct *Cointop) SetCurrencyConverstionFn(convert string) func() error { - ct.debuglog("SetCurrencyConverstionFn()") + log.Debug("SetCurrencyConverstionFn()") return func() error { ct.HideConvertMenu() @@ -245,13 +246,13 @@ func (ct *Cointop) SetCurrencyConverstionFn(convert string) func() error { // CurrencySymbol returns the symbol for the currency conversion func (ct *Cointop) CurrencySymbol() string { - ct.debuglog("CurrencySymbol()") + log.Debug("CurrencySymbol()") return CurrencySymbol(ct.State.currencyConversion) } // ShowConvertMenu shows the convert menu view func (ct *Cointop) ShowConvertMenu() error { - ct.debuglog("ShowConvertMenu()") + log.Debug("ShowConvertMenu()") ct.State.convertMenuVisible = true ct.UpdateConvertMenu() ct.SetActiveView(ct.Views.Menu.Name()) @@ -260,7 +261,7 @@ func (ct *Cointop) ShowConvertMenu() error { // HideConvertMenu hides the convert menu view func (ct *Cointop) HideConvertMenu() error { - ct.debuglog("HideConvertMenu()") + log.Debug("HideConvertMenu()") ct.State.convertMenuVisible = false ct.ui.SetViewOnBottom(ct.Views.Menu) ct.SetActiveView(ct.Views.Table.Name()) @@ -273,7 +274,7 @@ func (ct *Cointop) HideConvertMenu() error { // ToggleConvertMenu toggles the convert menu view func (ct *Cointop) ToggleConvertMenu() error { - ct.debuglog("ToggleConvertMenu()") + log.Debug("ToggleConvertMenu()") ct.State.convertMenuVisible = !ct.State.convertMenuVisible if ct.State.convertMenuVisible { return ct.ShowConvertMenu() diff --git a/cointop/debug.go b/cointop/debug.go index eff9c72..aab5dee 100644 --- a/cointop/debug.go +++ b/cointop/debug.go @@ -1,8 +1,9 @@ package cointop import ( - "log" "os" + + log "github.com/sirupsen/logrus" ) func (ct *Cointop) initlog() { @@ -14,12 +15,3 @@ func (ct *Cointop) initlog() { log.SetOutput(f) ct.logfile = f } - -// debuglog writes a debug log message to /tmp/cointop.log if the DEBUG environment is set. -func (ct *Cointop) debuglog(format string, args ...interface{}) { - if !ct.debug { - return - } - - log.Printf(format+"\n", args...) -} diff --git a/cointop/downsample.go b/cointop/downsample.go new file mode 100644 index 0000000..535011a --- /dev/null +++ b/cointop/downsample.go @@ -0,0 +1,119 @@ +package cointop + +import ( + "math" + "sort" +) + +func NewPoints() Points { + return make(Points, 0) +} + +func (ps Points) Downsample(newStep int) Points { + if len(ps) == 0 { + return []Point{} + } + + if !sort.IsSorted(ps) { + sort.Sort(ps) + } + + if len(ps) == 1 { + return []Point{ + Point{ps[0].Timestamp, ps[0].Value}, + } + } + + start := ps[0].Timestamp + end := ps[len(ps)-1].Timestamp + + // 不应该出现这种case + if start > end { + return []Point{} + } + + // 不应该出现这种case + if start == end { + return []Point{ + Point{ps[0].Timestamp, ps[0].Value}, + } + } + + res := make([]Point, 0) + for t1 := start; t1 <= end; t1 += int64(newStep) { + r := ps.avg(t1, t1+int64(newStep), end) + res = append(res, Point{t1, r}) + } + + return res +} + +func (ps Points) avg(t1, t2 int64, max int64) float64 { + l := len(ps) + var ( + count int = 0 + sum float64 + ) + idx := sort.Search(l, func(i int) bool { return ps[i].Timestamp >= t1 }) + if idx == l { + return math.NaN() // off the right + } + + if t2 > max { + for _, p := range ps { + if p.Timestamp >= ps[idx].Timestamp { + if v := float64(p.Value); !math.IsNaN(v) { + sum += v + count++ + } + } + } + } else { + for _, p := range ps { + if p.Timestamp >= ps[idx].Timestamp && p.Timestamp < t2 { + if v := float64(p.Value); !math.IsNaN(v) { + sum += v + count++ + } + } + } + } + + if count == 0 { + if idx == l-1 { + return 100 + ps[idx].Value // off the right + } + // Attempt to upsample + + // 2021/09/04 12:48:07 XXX OUT 2) 2021-09-01 13:49:37 47039.73 + // 2021/09/04 12:48:07 XXX OUT 3) 2021-09-01 14:13:39 146969.22 + // 2021/09/04 12:48:07 XXX OUT 4) 2021-09-01 14:37:41 147103.22 + // 2021/09/04 12:48:07 XXX OUT 5) 2021-09-01 15:01:43 47170.22 + // dt = + + // calculate change in value over time + left := ps[idx] + right := ps[idx+1] + dv := right.Value - left.Value + dt := right.Timestamp - left.Timestamp + dvdt := float64(dv) / float64(dt) + + // calculate midpoint of range + mid := (t1 + t2) / 2 + + return 100000 + left.Value + float64(mid-left.Timestamp)*dvdt + } + + return sum / float64(count) +} + +type Points []Point + +type Point struct { + Timestamp int64 + Value float64 +} + +func (ps Points) Less(i, j int) bool { return ps[i].Timestamp < ps[j].Timestamp } +func (ps Points) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } +func (ps Points) Len() int { return len(ps) } diff --git a/cointop/events.go b/cointop/events.go index 66566ee..ae735f1 100644 --- a/cointop/events.go +++ b/cointop/events.go @@ -1,7 +1,9 @@ package cointop +import log "github.com/sirupsen/logrus" + // RowChanged is called when the row is updated func (ct *Cointop) RowChanged() { - ct.debuglog("RowChanged()") + log.Debug("RowChanged()") ct.RefreshRowLink() } diff --git a/cointop/favorites.go b/cointop/favorites.go index 33c008b..c54a867 100644 --- a/cointop/favorites.go +++ b/cointop/favorites.go @@ -2,6 +2,8 @@ package cointop import ( "sort" + + log "github.com/sirupsen/logrus" ) // GetFavoritesTableHeaders returns the favorites table headers @@ -11,7 +13,7 @@ func (ct *Cointop) GetFavoritesTableHeaders() []string { // ToggleFavorite toggles coin as favorite func (ct *Cointop) ToggleFavorite() error { - ct.debuglog("ToggleFavorite()") + log.Debug("ToggleFavorite()") coin := ct.HighlightedRowCoin() if coin == nil { return nil @@ -37,7 +39,7 @@ func (ct *Cointop) ToggleFavorite() error { // ToggleFavorites toggles the favorites view func (ct *Cointop) ToggleFavorites() error { - ct.debuglog("ToggleFavorites()") + log.Debug("ToggleFavorites()") ct.ToggleSelectedView(FavoritesView) go ct.UpdateTable() return nil @@ -45,7 +47,7 @@ func (ct *Cointop) ToggleFavorites() error { // ToggleShowFavorites shows the favorites view func (ct *Cointop) ToggleShowFavorites() error { - ct.debuglog("ToggleShowFavorites()") + log.Debug("ToggleShowFavorites()") ct.ToggleSelectedView(FavoritesView) go ct.UpdateTable() return nil @@ -53,7 +55,7 @@ func (ct *Cointop) ToggleShowFavorites() error { // GetFavoritesSlice returns coin favorites as slice func (ct *Cointop) GetFavoritesSlice() []*Coin { - ct.debuglog("GetFavoritesSlice()") + log.Debug("GetFavoritesSlice()") sliced := []*Coin{} for i := range ct.State.allCoins { coin := ct.State.allCoins[i] diff --git a/cointop/help.go b/cointop/help.go index 651d5e4..d405d87 100644 --- a/cointop/help.go +++ b/cointop/help.go @@ -5,11 +5,12 @@ import ( "sort" "github.com/miguelmota/cointop/pkg/pad" + log "github.com/sirupsen/logrus" ) // UpdateHelp updates the help views func (ct *Cointop) UpdateHelp() { - ct.debuglog("UpdateHelp()") + log.Debug("UpdateHelp()") keys := make([]string, 0, len(ct.State.shortcutKeys)) for k := range ct.State.shortcutKeys { keys = append(keys, k) @@ -58,7 +59,7 @@ func (ct *Cointop) UpdateHelp() { // ShowHelp shows the help view func (ct *Cointop) ShowHelp() error { - ct.debuglog("ShowHelp()") + log.Debug("ShowHelp()") ct.State.helpVisible = true ct.UpdateHelp() ct.SetActiveView(ct.Views.Menu.Name()) @@ -67,7 +68,7 @@ func (ct *Cointop) ShowHelp() error { // HideHelp hides the help view func (ct *Cointop) HideHelp() error { - ct.debuglog("HideHelp()") + log.Debug("HideHelp()") ct.State.helpVisible = false ct.ui.SetViewOnBottom(ct.Views.Menu) ct.SetActiveView(ct.Views.Table.Name()) @@ -80,7 +81,7 @@ func (ct *Cointop) HideHelp() error { // ToggleHelp toggles the help view func (ct *Cointop) ToggleHelp() error { - ct.debuglog("ToggleHelp()") + log.Debug("ToggleHelp()") ct.State.helpVisible = !ct.State.helpVisible if ct.State.helpVisible { return ct.ShowHelp() diff --git a/cointop/layout.go b/cointop/layout.go index 98522b8..f056130 100644 --- a/cointop/layout.go +++ b/cointop/layout.go @@ -3,6 +3,8 @@ package cointop import ( "fmt" "strings" + + log "github.com/sirupsen/logrus" ) // TODO: break up into small functions @@ -11,7 +13,7 @@ var lastWidth int // layout sets initial layout func (ct *Cointop) layout() error { - ct.debuglog("Layout()") + log.Debug("Layout()") maxY := ct.Height() maxX := ct.Width() diff --git a/cointop/list.go b/cointop/list.go index 3a19987..796e0e0 100644 --- a/cointop/list.go +++ b/cointop/list.go @@ -5,6 +5,7 @@ import ( "time" types "github.com/miguelmota/cointop/pkg/api/types" + log "github.com/sirupsen/logrus" ) var coinslock sync.Mutex @@ -12,7 +13,7 @@ var updatecoinsmux sync.Mutex // UpdateCoins updates coins view func (ct *Cointop) UpdateCoins() error { - ct.debuglog("UpdateCoins()") + log.Debug("UpdateCoins()") coinslock.Lock() defer coinslock.Unlock() cachekey := ct.CacheKey("allCoinsSlugMap") @@ -23,12 +24,12 @@ func (ct *Cointop) UpdateCoins() error { if found { // cache hit allCoinsSlugMap, _ = cached.(map[string]types.Coin) - ct.debuglog("UpdateCoins() soft cache hit") + log.Debug("UpdateCoins() soft cache hit") } // cache miss if allCoinsSlugMap == nil { - ct.debuglog("UpdateCoins() cache miss") + log.Debug("UpdateCoins() cache miss") ch := make(chan []types.Coin) err = ct.api.GetAllCoinData(ct.State.currencyConversion, ch) if err != nil { @@ -47,7 +48,7 @@ func (ct *Cointop) UpdateCoins() error { // ProcessCoinsMap processes coins map func (ct *Cointop) processCoinsMap(coinsMap map[string]types.Coin) { - ct.debuglog("ProcessCoinsMap()") + log.Debug("ProcessCoinsMap()") var coins []types.Coin for _, v := range coinsMap { @@ -59,7 +60,7 @@ func (ct *Cointop) processCoinsMap(coinsMap map[string]types.Coin) { // ProcessCoins processes coins list func (ct *Cointop) processCoins(coins []types.Coin) { - ct.debuglog("ProcessCoins()") + log.Debug("ProcessCoins()") updatecoinsmux.Lock() defer updatecoinsmux.Unlock() @@ -160,7 +161,7 @@ func (ct *Cointop) processCoins(coins []types.Coin) { // GetListCount returns count of coins list func (ct *Cointop) GetListCount() int { - ct.debuglog("GetListCount()") + log.Debug("GetListCount()") if ct.IsFavoritesVisible() { return len(ct.State.favorites) } else if ct.IsPortfolioVisible() { diff --git a/cointop/marketbar.go b/cointop/marketbar.go index 1a781db..e2a0eec 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -11,6 +11,7 @@ import ( "github.com/miguelmota/cointop/pkg/humanize" "github.com/miguelmota/cointop/pkg/pad" "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" ) // MarketbarView is structure for marketbar view @@ -24,7 +25,7 @@ func NewMarketbarView() *MarketbarView { // UpdateMarketbar updates the market bar view func (ct *Cointop) UpdateMarketbar() error { - ct.debuglog("UpdateMarketbar()") + log.Debug("UpdateMarketbar()") maxX := ct.Width() logo := "❯❯❯cointop" if ct.colorschemeName == "cointop" { @@ -102,7 +103,7 @@ func (ct *Cointop) UpdateMarketbar() error { var ok bool market, ok = cached.(types.GlobalMarketData) if ok { - ct.debuglog("UpdateMarketbar() soft cache hit") + log.Debug("UpdateMarketbar() soft cache hit") } } diff --git a/cointop/menu.go b/cointop/menu.go index ffea9f0..70ae08c 100644 --- a/cointop/menu.go +++ b/cointop/menu.go @@ -1,6 +1,9 @@ package cointop -import "github.com/miguelmota/cointop/pkg/ui" +import ( + "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" +) // MenuView is structure for menu view type MenuView = ui.View @@ -13,6 +16,6 @@ func NewMenuView() *MenuView { // HideMenu hides the menu view func (ct *Cointop) HideMenu() error { - ct.debuglog("HideMenu()") + log.Debug("HideMenu()") return nil } diff --git a/cointop/navigation.go b/cointop/navigation.go index 754eda7..f6046b6 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -2,29 +2,31 @@ package cointop import ( "math" + + log "github.com/sirupsen/logrus" ) // CurrentPage returns the current page func (ct *Cointop) CurrentPage() int { - ct.debuglog("CurrentPage()") + log.Debug("CurrentPage()") return ct.State.page + 1 } // CurrentDisplayPage returns the current page in human readable format func (ct *Cointop) CurrentDisplayPage() int { - ct.debuglog("CurrentDisplayPage()") + log.Debug("CurrentDisplayPage()") return ct.State.page + 1 } // TotalPages returns the number of total pages func (ct *Cointop) TotalPages() int { - ct.debuglog("TotalPages()") + log.Debug("TotalPages()") return ct.GetListCount() / ct.State.perPage } // TotalPagesDisplay returns the number of total pages in human readable format func (ct *Cointop) TotalPagesDisplay() int { - ct.debuglog("TotalPagesDisplay()") + log.Debug("TotalPagesDisplay()") return ct.TotalPages() + 1 } @@ -35,7 +37,7 @@ func (ct *Cointop) TotalPerPage() int { // SetPage navigates to the selected page func (ct *Cointop) SetPage(page int) int { - ct.debuglog("SetPage()") + log.Debug("SetPage()") if (page*ct.State.perPage) < ct.GetListCount() && page >= 0 { ct.State.page = page } @@ -44,7 +46,7 @@ func (ct *Cointop) SetPage(page int) int { // CursorDown moves the cursor one row down func (ct *Cointop) CursorDown() error { - ct.debuglog("CursorDown()") + log.Debug("CursorDown()") // return if already at the bottom if ct.IsLastRow() { return nil @@ -69,7 +71,7 @@ func (ct *Cointop) CursorDown() error { // CursorUp moves the cursor one row up func (ct *Cointop) CursorUp() error { - ct.debuglog("CursorUp()") + log.Debug("CursorUp()") // return if already at the top if ct.IsFirstRow() { return nil @@ -94,7 +96,7 @@ func (ct *Cointop) CursorUp() error { // PageDown moves the cursor one page down func (ct *Cointop) PageDown() error { - ct.debuglog("PageDown()") + log.Debug("PageDown()") // return if already at the bottom if ct.IsLastRow() { return nil @@ -131,7 +133,7 @@ func (ct *Cointop) PageDown() error { // PageUp moves the cursor one page up func (ct *Cointop) PageUp() error { - ct.debuglog("PageUp()") + log.Debug("PageUp()") // return if already at the top if ct.IsFirstRow() { return nil @@ -159,7 +161,7 @@ func (ct *Cointop) PageUp() error { // NavigateFirstLine moves the cursor to the first row of the table func (ct *Cointop) NavigateFirstLine() error { - ct.debuglog("NavigateFirstLine()") + log.Debug("NavigateFirstLine()") // return if already at the top if ct.IsFirstRow() { return nil @@ -180,7 +182,7 @@ func (ct *Cointop) NavigateFirstLine() error { // NavigateLastLine moves the cursor to the last row of the table func (ct *Cointop) NavigateLastLine() error { - ct.debuglog("NavigateLastLine()") + log.Debug("NavigateLastLine()") // return if already at the bottom if ct.IsLastRow() { return nil @@ -209,7 +211,7 @@ func (ct *Cointop) NavigateLastLine() error { // NavigatePageFirstLine moves the cursor to the visible first row of the table func (ct *Cointop) NavigatePageFirstLine() error { - ct.debuglog("NavigatePageFirstLine()") + log.Debug("NavigatePageFirstLine()") // return if already at the correct line if ct.IsPageFirstLine() { return nil @@ -225,7 +227,7 @@ func (ct *Cointop) NavigatePageFirstLine() error { // NavigatePageMiddleLine moves the cursor to the visible middle row of the table func (ct *Cointop) NavigatePageMiddleLine() error { - ct.debuglog("NavigatePageMiddleLine()") + log.Debug("NavigatePageMiddleLine()") // return if already at the correct line if ct.IsPageMiddleLine() { return nil @@ -242,7 +244,7 @@ func (ct *Cointop) NavigatePageMiddleLine() error { // NavigatePageLastLine moves the cursor to the visible last row of the table func (ct *Cointop) navigatePageLastLine() error { - ct.debuglog("NavigatePageLastLine()") + log.Debug("NavigatePageLastLine()") // return if already at the correct line if ct.IsPageLastLine() { return nil @@ -259,7 +261,7 @@ func (ct *Cointop) navigatePageLastLine() error { // NextPage navigates to the next page func (ct *Cointop) NextPage() error { - ct.debuglog("NextPage()") + log.Debug("NextPage()") // return if already at the last page if ct.IsLastPage() { @@ -274,7 +276,7 @@ func (ct *Cointop) NextPage() error { // PrevPage navigates to the previous page func (ct *Cointop) PrevPage() error { - ct.debuglog("PrevPage()") + log.Debug("PrevPage()") // return if already at the first page if ct.IsFirstPage() { @@ -289,7 +291,7 @@ func (ct *Cointop) PrevPage() error { // NextPageTop navigates to the first row of the next page func (ct *Cointop) nextPageTop() error { - ct.debuglog("NextPageTop()") + log.Debug("NextPageTop()") ct.NextPage() ct.NavigateFirstLine() @@ -299,7 +301,7 @@ func (ct *Cointop) nextPageTop() error { // PrevPageTop navigates to the first row of the previous page func (ct *Cointop) PrevPageTop() error { - ct.debuglog("PrevtPageTop()") + log.Debug("PrevtPageTop()") ct.PrevPage() ct.NavigateLastLine() @@ -309,7 +311,7 @@ func (ct *Cointop) PrevPageTop() error { // FirstPage navigates to the first page func (ct *Cointop) FirstPage() error { - ct.debuglog("FirstPage()") + log.Debug("FirstPage()") // return if already at the first page if ct.IsFirstPage() { @@ -324,7 +326,7 @@ func (ct *Cointop) FirstPage() error { // LastPage navigates to the last page func (ct *Cointop) LastPage() error { - ct.debuglog("LastPage()") + log.Debug("LastPage()") // return if already at the last page if ct.IsLastPage() { @@ -339,7 +341,7 @@ func (ct *Cointop) LastPage() error { // IsFirstRow returns true if cursor is on first row func (ct *Cointop) IsFirstRow() bool { - ct.debuglog("IsFirstRow()") + log.Debug("IsFirstRow()") oy := ct.Views.Table.OriginY() cy := ct.Views.Table.CursorY() return (cy + oy) == 0 @@ -347,7 +349,7 @@ func (ct *Cointop) IsFirstRow() bool { // IsLastRow returns true if cursor is on last row func (ct *Cointop) IsLastRow() bool { - ct.debuglog("IsLastRow()") + log.Debug("IsLastRow()") oy := ct.Views.Table.OriginY() cy := ct.Views.Table.CursorY() numRows := ct.TableRowsLen() - 1 @@ -356,19 +358,19 @@ func (ct *Cointop) IsLastRow() bool { // IsFirstPage returns true if cursor is on the first page func (ct *Cointop) IsFirstPage() bool { - ct.debuglog("IsFirstPage()") + log.Debug("IsFirstPage()") return ct.State.page == 0 } // IsLastPage returns true if cursor is on the last page func (ct *Cointop) IsLastPage() bool { - ct.debuglog("IsLastPage()") + log.Debug("IsLastPage()") return ct.State.page == ct.TotalPages()-1 } // IsPageFirstLine returns true if the cursor is on the visible first row func (ct *Cointop) IsPageFirstLine() bool { - ct.debuglog("IsPageFirstLine()") + log.Debug("IsPageFirstLine()") cy := ct.Views.Table.CursorY() return cy == 0 @@ -376,7 +378,7 @@ func (ct *Cointop) IsPageFirstLine() bool { // IsPageMiddleLine returns true if the cursor is on the visible middle row func (ct *Cointop) IsPageMiddleLine() bool { - ct.debuglog("IsPageMiddleLine()") + log.Debug("IsPageMiddleLine()") cy := ct.Views.Table.CursorY() sy := ct.Views.Table.Height() return (sy/2)-1 == cy @@ -384,7 +386,7 @@ func (ct *Cointop) IsPageMiddleLine() bool { // IsPageLastLine returns true if the cursor is on the visible last row func (ct *Cointop) IsPageLastLine() bool { - ct.debuglog("IsPageLastLine()") + log.Debug("IsPageLastLine()") cy := ct.Views.Table.CursorY() sy := ct.Views.Table.Height() @@ -393,7 +395,7 @@ func (ct *Cointop) IsPageLastLine() bool { // GoToPageRowIndex navigates to the selected row index of the page func (ct *Cointop) GoToPageRowIndex(idx int) error { - ct.debuglog("GoToPageRowIndex()") + log.Debug("GoToPageRowIndex()") if idx < 0 { idx = 0 } @@ -407,7 +409,7 @@ func (ct *Cointop) GoToPageRowIndex(idx int) error { // GoToGlobalIndex navigates to the selected row index of all page rows func (ct *Cointop) GoToGlobalIndex(idx int) error { - ct.debuglog("GoToGlobalIndex()") + log.Debug("GoToGlobalIndex()") l := ct.TableRowsLen() atpage := idx / l ct.SetPage(atpage) @@ -422,7 +424,7 @@ func (ct *Cointop) HighlightRow(pageRowIndex int) error { if pageRowIndex < 0 { pageRowIndex = 0 } - ct.debuglog("HighlightRow()") + log.Debug("HighlightRow()") ct.Views.Table.SetOrigin(0, 0) ct.Views.Table.SetCursor(0, 0) ox := ct.Views.Table.OriginX() @@ -441,7 +443,7 @@ func (ct *Cointop) HighlightRow(pageRowIndex int) error { cy = h - (l - pageRowIndex) } } - ct.debuglog("HighlightRow idx:%v h:%v cy:%v oy:%v", pageRowIndex, h, cy, oy) + log.Debug("HighlightRow idx:%v h:%v cy:%v oy:%v", pageRowIndex, h, cy, oy) ct.Views.Table.SetOrigin(ox, oy) ct.Views.Table.SetCursor(cx, cy) return nil @@ -449,7 +451,7 @@ func (ct *Cointop) HighlightRow(pageRowIndex int) error { // GoToCoinRow navigates to the row of the matched coin func (ct *Cointop) GoToCoinRow(coin *Coin) error { - ct.debuglog("GoToCoinRow()") + log.Debug("GoToCoinRow()") if coin == nil { return nil } @@ -483,7 +485,7 @@ func (ct *Cointop) GetCoinRowIndex(coin *Coin) int { // CursorDownOrNextPage moves the cursor down one row or goes to the next page if cursor is on the last row func (ct *Cointop) CursorDownOrNextPage() error { - ct.debuglog("CursorDownOrNextPage()") + log.Debug("CursorDownOrNextPage()") if ct.IsLastRow() { if ct.IsLastPage() { return nil @@ -505,7 +507,7 @@ func (ct *Cointop) CursorDownOrNextPage() error { // CursorUpOrPreviousPage moves the cursor up one row or goes to the previous page if cursor is on the first row func (ct *Cointop) CursorUpOrPreviousPage() error { - ct.debuglog("CursorUpOrPreviousPage()") + log.Debug("CursorUpOrPreviousPage()") if ct.IsFirstRow() { if ct.IsFirstPage() { return nil @@ -578,7 +580,7 @@ func (ct *Cointop) MouseWheelDown() error { // TableRowsLen returns the number of table row entries func (ct *Cointop) TableRowsLen() int { - ct.debuglog("TableRowsLen()") + log.Debug("TableRowsLen()") if ct.IsFavoritesVisible() { return ct.FavoritesLen() } diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 36c45f7..4526e46 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -17,6 +17,7 @@ import ( "github.com/miguelmota/cointop/pkg/humanize" "github.com/miguelmota/cointop/pkg/pad" "github.com/miguelmota/cointop/pkg/table" + log "github.com/sirupsen/logrus" ) // SupportedPortfolioTableHeaders are all the supported portfolio table header columns @@ -306,7 +307,7 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { // TogglePortfolio toggles the portfolio view func (ct *Cointop) TogglePortfolio() error { - ct.debuglog("TogglePortfolio()") + log.Debug("TogglePortfolio()") ct.ToggleSelectedView(PortfolioView) go ct.UpdateChart() go ct.UpdateTable() @@ -315,7 +316,7 @@ func (ct *Cointop) TogglePortfolio() error { // ToggleShowPortfolio shows the portfolio view func (ct *Cointop) ToggleShowPortfolio() error { - ct.debuglog("ToggleShowPortfolio()") + log.Debug("ToggleShowPortfolio()") ct.SetSelectedView(PortfolioView) go ct.UpdateChart() go ct.UpdateTable() @@ -324,7 +325,7 @@ func (ct *Cointop) ToggleShowPortfolio() error { // TogglePortfolioUpdateMenu toggles the portfolio update menu func (ct *Cointop) TogglePortfolioUpdateMenu() error { - ct.debuglog("TogglePortfolioUpdateMenu()") + log.Debug("TogglePortfolioUpdateMenu()") if ct.IsPriceAlertsVisible() { return ct.ShowPriceAlertsUpdateMenu() } @@ -344,11 +345,11 @@ func (ct *Cointop) CoinHoldings(coin *Coin) float64 { // UpdatePortfolioUpdateMenu updates the portfolio update menu view func (ct *Cointop) UpdatePortfolioUpdateMenu() error { - ct.debuglog("UpdatePortfolioUpdateMenu()") + log.Debug("UpdatePortfolioUpdateMenu()") coin := ct.HighlightedRowCoin() exists := ct.PortfolioEntryExists(coin) value := strconv.FormatFloat(ct.CoinHoldings(coin), 'f', -1, 64) - ct.debuglog("UpdatePortfolioUpdateMenu() holdings %v", value) + log.Debug("UpdatePortfolioUpdateMenu() holdings %v", value) var mode string var current string var submitText string @@ -376,7 +377,7 @@ func (ct *Cointop) UpdatePortfolioUpdateMenu() error { // ShowPortfolioUpdateMenu shows the portfolio update menu func (ct *Cointop) ShowPortfolioUpdateMenu() error { - ct.debuglog("ShowPortfolioUpdateMenu()") + log.Debug("ShowPortfolioUpdateMenu()") // TODO: separation of concerns if ct.IsPriceAlertsVisible() { @@ -400,7 +401,7 @@ func (ct *Cointop) ShowPortfolioUpdateMenu() error { // HidePortfolioUpdateMenu hides the portfolio update menu func (ct *Cointop) HidePortfolioUpdateMenu() error { - ct.debuglog("HidePortfolioUpdateMenu()") + log.Debug("HidePortfolioUpdateMenu()") ct.State.portfolioUpdateMenuVisible = false ct.ui.SetViewOnBottom(ct.Views.Menu) ct.ui.SetViewOnBottom(ct.Views.Input) @@ -418,7 +419,7 @@ func (ct *Cointop) HidePortfolioUpdateMenu() error { // SetPortfolioHoldings sets portfolio entry holdings from inputed value func (ct *Cointop) SetPortfolioHoldings() error { - ct.debuglog("SetPortfolioHoldings()") + log.Debug("SetPortfolioHoldings()") defer ct.HidePortfolioUpdateMenu() coin := ct.HighlightedRowCoin() if coin == nil { @@ -469,7 +470,7 @@ func (ct *Cointop) SetPortfolioHoldings() error { // PortfolioEntry returns a portfolio entry func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) { - //ct.debuglog("PortfolioEntry()") // too many + //log.Debug("PortfolioEntry()") // too many if c == nil { return &PortfolioEntry{}, true } @@ -495,7 +496,7 @@ func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) { // SetPortfolioEntry sets a portfolio entry func (ct *Cointop) SetPortfolioEntry(coin string, holdings float64) error { - ct.debuglog("SetPortfolioEntry()") + log.Debug("SetPortfolioEntry()") ic, _ := ct.State.allCoinsSlugMap.Load(strings.ToLower(coin)) c, _ := ic.(*Coin) p, isNew := ct.PortfolioEntry(c) @@ -518,7 +519,7 @@ func (ct *Cointop) SetPortfolioEntry(coin string, holdings float64) error { // RemovePortfolioEntry removes a portfolio entry func (ct *Cointop) RemovePortfolioEntry(coin string) error { - ct.debuglog("RemovePortfolioEntry()") + log.Debug("RemovePortfolioEntry()") delete(ct.State.portfolio.Entries, strings.ToLower(coin)) if err := ct.Save(); err != nil { return err @@ -528,20 +529,20 @@ func (ct *Cointop) RemovePortfolioEntry(coin string) error { // PortfolioEntryExists returns true if portfolio entry exists func (ct *Cointop) PortfolioEntryExists(c *Coin) bool { - ct.debuglog("PortfolioEntryExists()") + log.Debug("PortfolioEntryExists()") _, isNew := ct.PortfolioEntry(c) return !isNew } // PortfolioEntriesCount returns the count of portfolio entries func (ct *Cointop) PortfolioEntriesCount() int { - ct.debuglog("PortfolioEntriesCount()") + log.Debug("PortfolioEntriesCount()") return len(ct.State.portfolio.Entries) } // GetPortfolioSlice returns portfolio entries as a slice func (ct *Cointop) GetPortfolioSlice() []*Coin { - ct.debuglog("GetPortfolioSlice()") + log.Debug("GetPortfolioSlice()") sliced := []*Coin{} if ct.PortfolioEntriesCount() == 0 { return sliced @@ -595,7 +596,7 @@ OUTER: // GetPortfolioTotal returns the total balance of portfolio entries func (ct *Cointop) GetPortfolioTotal() float64 { - ct.debuglog("GetPortfolioTotal()") + log.Debug("GetPortfolioTotal()") portfolio := ct.GetPortfolioSlice() var total float64 for _, p := range portfolio { @@ -606,7 +607,7 @@ func (ct *Cointop) GetPortfolioTotal() float64 { // RefreshPortfolioCoins refreshes portfolio entry coin data func (ct *Cointop) RefreshPortfolioCoins() error { - ct.debuglog("RefreshPortfolioCoins()") + log.Debug("RefreshPortfolioCoins()") holdings := ct.GetPortfolioSlice() holdingCoins := make([]string, len(holdings)) for i, entry := range holdings { @@ -654,7 +655,7 @@ var portfolioColumns = map[string]bool{ // PrintHoldingsTable prints the holdings in an ASCII table func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { - ct.debuglog("PrintHoldingsTable()") + log.Debug("PrintHoldingsTable()") if options == nil { options = &TablePrintOptions{} } @@ -853,7 +854,7 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { // PrintHoldingsTotal prints the total holdings amount func (ct *Cointop) PrintHoldingsTotal(options *TablePrintOptions) error { - ct.debuglog("PrintHoldingsTotal()") + log.Debug("PrintHoldingsTotal()") if options == nil { options = &TablePrintOptions{} } @@ -927,7 +928,7 @@ func (ct *Cointop) PrintHoldingsTotal(options *TablePrintOptions) error { // PrintHoldings24HChange prints the total holdings amount func (ct *Cointop) PrintHoldings24HChange(options *TablePrintOptions) error { - ct.debuglog("PrintHoldings24HChange()") + log.Debug("PrintHoldings24HChange()") if options == nil { options = &TablePrintOptions{} } diff --git a/cointop/price_alerts.go b/cointop/price_alerts.go index 1ad3fb9..070ee26 100644 --- a/cointop/price_alerts.go +++ b/cointop/price_alerts.go @@ -12,6 +12,7 @@ import ( "github.com/miguelmota/cointop/pkg/notifier" "github.com/miguelmota/cointop/pkg/pad" "github.com/miguelmota/cointop/pkg/table" + log "github.com/sirupsen/logrus" ) // GetPriceAlertsTableHeaders returns the alerts table headers @@ -42,7 +43,7 @@ var PriceAlertFrequencyMap = map[string]bool{ // GetPriceAlertsTable returns the table for displaying alerts func (ct *Cointop) GetPriceAlertsTable() *table.Table { - ct.debuglog("GetPriceAlertsTable()") + log.Debug("GetPriceAlertsTable()") maxX := ct.Width() t := table.NewTable().SetWidth(maxX) var rows [][]*table.RowCell @@ -145,7 +146,7 @@ func (ct *Cointop) GetPriceAlertsTable() *table.Table { // TogglePriceAlerts toggles the price alerts view func (ct *Cointop) TogglePriceAlerts() error { - ct.debuglog("TogglePriceAlerts()") + log.Debug("TogglePriceAlerts()") ct.ToggleSelectedView(PriceAlertsView) ct.NavigateFirstLine() go ct.UpdateTable() @@ -159,7 +160,7 @@ func (ct *Cointop) IsPriceAlertsVisible() bool { // PriceAlertWatcher starts the price alert watcher func (ct *Cointop) PriceAlertWatcher() error { - ct.debuglog("PriceAlertWatcher()") + log.Debug("PriceAlertWatcher()") alerts := ct.State.priceAlerts.Entries ticker := time.NewTicker(5 * time.Second) for range ticker.C { @@ -175,7 +176,7 @@ func (ct *Cointop) PriceAlertWatcher() error { // CheckPriceAlert checks the price alert func (ct *Cointop) CheckPriceAlert(alert *PriceAlert) error { - ct.debuglog("CheckPriceAlert()") + log.Debug("CheckPriceAlert()") if alert.Expired { return nil } @@ -227,7 +228,7 @@ func (ct *Cointop) CheckPriceAlert(alert *PriceAlert) error { // UpdatePriceAlertsUpdateMenu updates the alerts update menu view func (ct *Cointop) UpdatePriceAlertsUpdateMenu(isNew bool, coin *Coin) error { - ct.debuglog("UpdatePriceAlertsUpdateMenu()") + log.Debug("UpdatePriceAlertsUpdateMenu()") isEdit := false var value string @@ -293,7 +294,7 @@ func (ct *Cointop) UpdatePriceAlertsUpdateMenu(isNew bool, coin *Coin) error { // ShowPriceAlertsAddMenu shows the alert add menu func (ct *Cointop) ShowPriceAlertsAddMenu() error { - ct.debuglog("ShowPriceAlertsAddMenu()") + log.Debug("ShowPriceAlertsAddMenu()") coin := ct.HighlightedRowCoin() ct.SetSelectedView(PriceAlertsView) ct.UpdatePriceAlertsUpdateMenu(true, coin) @@ -306,7 +307,7 @@ func (ct *Cointop) ShowPriceAlertsAddMenu() error { // ShowPriceAlertsUpdateMenu shows the alerts update menu func (ct *Cointop) ShowPriceAlertsUpdateMenu() error { - ct.debuglog("ShowPriceAlertsUpdateMenu()") + log.Debug("ShowPriceAlertsUpdateMenu()") coin := ct.HighlightedRowCoin() ct.SetSelectedView(PriceAlertsView) ct.UpdatePriceAlertsUpdateMenu(false, coin) @@ -319,7 +320,7 @@ func (ct *Cointop) ShowPriceAlertsUpdateMenu() error { // HidePriceAlertsUpdateMenu hides the alerts update menu func (ct *Cointop) HidePriceAlertsUpdateMenu() error { - ct.debuglog("HidePriceAlertsUpdateMenu()") + log.Debug("HidePriceAlertsUpdateMenu()") ct.ui.SetViewOnBottom(ct.Views.Menu) ct.ui.SetViewOnBottom(ct.Views.Input) ct.ui.SetCursor(false) @@ -345,7 +346,7 @@ func (ct *Cointop) EnterKeyPressHandler() error { // CreatePriceAlert sets price from inputed value func (ct *Cointop) CreatePriceAlert() error { - ct.debuglog("CreatePriceAlert()") + log.Debug("CreatePriceAlert()") defer ct.HidePriceAlertsUpdateMenu() isNew := ct.State.priceAlertNewID != "" @@ -436,7 +437,7 @@ func (ct *Cointop) ParsePriceAlertInput(value string) (string, float64, error) { // SetPriceAlert sets a price alert func (ct *Cointop) SetPriceAlert(coinName string, operator string, targetPrice float64) error { - ct.debuglog("SetPriceAlert()") + log.Debug("SetPriceAlert()") if operator == "" { operator = "=" @@ -474,7 +475,7 @@ func (ct *Cointop) SetPriceAlert(coinName string, operator string, targetPrice f // RemovePriceAlert removes a price alert entry func (ct *Cointop) RemovePriceAlert(id string) error { - ct.debuglog("RemovePriceAlert()") + log.Debug("RemovePriceAlert()") for i, entry := range ct.State.priceAlerts.Entries { if entry.ID == ct.State.priceAlertEditID { ct.State.priceAlerts.Entries = append(ct.State.priceAlerts.Entries[:i], ct.State.priceAlerts.Entries[i+1:]...) diff --git a/cointop/quit.go b/cointop/quit.go index 2c08d10..2f30473 100644 --- a/cointop/quit.go +++ b/cointop/quit.go @@ -4,6 +4,7 @@ import ( "os" "github.com/miguelmota/gocui" + log "github.com/sirupsen/logrus" ) // Quit quits the program @@ -14,7 +15,7 @@ func (ct *Cointop) Quit() error { // QuitView exists the current view func (ct *Cointop) QuitView() error { - ct.debuglog("QuitView()") + log.Debug("QuitView()") if ct.State.selectedView != CoinsView { ct.SetSelectedView(CoinsView) return ct.UpdateTable() @@ -28,7 +29,7 @@ func (ct *Cointop) QuitView() error { // Exit safely exits the program func (ct *Cointop) Exit() { - ct.debuglog("Exit()") + log.Debug("Exit()") ct.logfile.Close() if ct.g != nil { ct.g.Close() diff --git a/cointop/refresh.go b/cointop/refresh.go index ad935b4..9d30a72 100644 --- a/cointop/refresh.go +++ b/cointop/refresh.go @@ -3,11 +3,13 @@ package cointop import ( "strings" "time" + + log "github.com/sirupsen/logrus" ) // Refresh triggers a force refresh of coin data func (ct *Cointop) Refresh() error { - ct.debuglog("Refresh()") + log.Debug("Refresh()") go func() { <-ct.limiter ct.forceRefresh <- true @@ -17,7 +19,7 @@ func (ct *Cointop) Refresh() error { // RefreshAll triggers a force refresh of all data func (ct *Cointop) RefreshAll() error { - ct.debuglog("RefreshAll()") + log.Debug("RefreshAll()") ct.refreshMux.Lock() defer ct.refreshMux.Unlock() ct.setRefreshStatus() @@ -33,7 +35,7 @@ func (ct *Cointop) RefreshAll() error { // SetRefreshStatus sets the refresh ticker func (ct *Cointop) setRefreshStatus() { - ct.debuglog("setRefreshStatus()") + log.Debug("setRefreshStatus()") go func() { ct.loadingTicks("refreshing", 900) ct.RowChanged() @@ -42,7 +44,7 @@ func (ct *Cointop) setRefreshStatus() { // LoadingTicks sets the loading ticking dots func (ct *Cointop) loadingTicks(s string, t int) { - ct.debuglog("loadingTicks()") + log.Debug("loadingTicks()") interval := 150 k := 0 for i := 0; i < (t / interval); i++ { @@ -57,7 +59,7 @@ func (ct *Cointop) loadingTicks(s string, t int) { // intervalFetchData does a force refresh at every interval func (ct *Cointop) intervalFetchData() { - ct.debuglog("intervalFetchData()") + log.Debug("intervalFetchData()") go func() { for { select { diff --git a/cointop/save.go b/cointop/save.go index 2f46067..aab691a 100644 --- a/cointop/save.go +++ b/cointop/save.go @@ -1,8 +1,10 @@ package cointop +import log "github.com/sirupsen/logrus" + // Save saves the cointop settings to the config file func (ct *Cointop) Save() error { - ct.debuglog("Save()") + log.Debug("Save()") ct.SetSavingStatus() if err := ct.SaveConfig(); err != nil { return err @@ -15,7 +17,7 @@ func (ct *Cointop) Save() error { // SetSavingStatus sets the saving indicator in the statusbar func (ct *Cointop) SetSavingStatus() { - ct.debuglog("SetSavingStatus()") + log.Debug("SetSavingStatus()") if ct.g == nil { return } diff --git a/cointop/search.go b/cointop/search.go index f4d3341..e54f598 100644 --- a/cointop/search.go +++ b/cointop/search.go @@ -6,6 +6,7 @@ import ( "github.com/miguelmota/cointop/pkg/levenshtein" "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" ) // SearchFieldView is structure for search field view @@ -28,7 +29,7 @@ func NewInputView() *InputView { // OpenSearch opens the search field func (ct *Cointop) OpenSearch() error { - ct.debuglog("OpenSearch()") + log.Debug("OpenSearch()") if ct.ui.ActiveViewName() != ct.Views.Table.Name() { return nil } @@ -40,7 +41,7 @@ func (ct *Cointop) OpenSearch() error { // CancelSearch closes the search field func (ct *Cointop) CancelSearch() error { - ct.debuglog("CancelSearch()") + log.Debug("CancelSearch()") ct.State.searchFieldVisible = false ct.ui.SetCursor(false) ct.SetActiveView(ct.Views.Table.Name()) @@ -49,7 +50,7 @@ func (ct *Cointop) CancelSearch() error { // DoSearch triggers the search and sets views func (ct *Cointop) DoSearch() error { - ct.debuglog("DoSearch()") + log.Debug("DoSearch()") ct.Views.SearchField.Rewind() b := make([]byte, 100) n, err := ct.Views.SearchField.Read(b) @@ -79,7 +80,7 @@ func (ct *Cointop) DoSearch() error { // Search performs the search and filtering func (ct *Cointop) Search(q string) error { - ct.debuglog("Search()") + log.Debug("Search()") q = strings.TrimSpace(strings.ToLower(q)) idx := -1 min := -1 diff --git a/cointop/selection.go b/cointop/selection.go index d164bbf..8b40544 100644 --- a/cointop/selection.go +++ b/cointop/selection.go @@ -1,8 +1,10 @@ package cointop +import log "github.com/sirupsen/logrus" + // SelectedCoinName returns the selected coin name func (ct *Cointop) SelectedCoinName() string { - ct.debuglog("SelectedCoinName()") + log.Debug("SelectedCoinName()") coin := ct.State.selectedCoin if coin != nil { return coin.Name @@ -13,7 +15,7 @@ func (ct *Cointop) SelectedCoinName() string { // SelectedCoinSymbol returns the selected coin symbol func (ct *Cointop) SelectedCoinSymbol() string { - ct.debuglog("SelectedCoinSymbol()") + log.Debug("SelectedCoinSymbol()") coin := ct.State.selectedCoin if coin != nil { return coin.Symbol diff --git a/cointop/size.go b/cointop/size.go index 71c72d8..3ee9dc6 100644 --- a/cointop/size.go +++ b/cointop/size.go @@ -1,8 +1,10 @@ package cointop +import log "github.com/sirupsen/logrus" + // Size returns window width and height func (ct *Cointop) Size() (int, int) { - ct.debuglog("Size()") + log.Debug("Size()") if ct.g == nil { return 0, 0 } @@ -12,21 +14,21 @@ func (ct *Cointop) Size() (int, int) { // Width returns window width func (ct *Cointop) Width() int { - ct.debuglog("Width()") + log.Debug("Width()") w, _ := ct.Size() return w } // Height returns window height func (ct *Cointop) Height() int { - ct.debuglog("Height()") + log.Debug("Height()") _, h := ct.Size() return h } // ViewWidth returns view width func (ct *Cointop) ViewWidth(view string) int { - ct.debuglog("ViewWidth()") + log.Debug("ViewWidth()") v, err := ct.g.View(view) if err != nil { return 0 @@ -37,7 +39,7 @@ func (ct *Cointop) ViewWidth(view string) int { // ClampedWidth returns the clamped width func (ct *Cointop) ClampedWidth() int { - ct.debuglog("ClampedWidth()") + log.Debug("ClampedWidth()") w := ct.Width() if w > ct.maxTableWidth { return ct.maxTableWidth diff --git a/cointop/sort.go b/cointop/sort.go index d16bd24..cc452d5 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -5,13 +5,14 @@ import ( "sync" "github.com/miguelmota/gocui" + log "github.com/sirupsen/logrus" ) var sortlock sync.Mutex // Sort sorts the list of coins func (ct *Cointop) Sort(sortBy string, desc bool, list []*Coin, renderHeaders bool) { - ct.debuglog("Sort()") + log.Debug("Sort()") sortlock.Lock() defer sortlock.Unlock() ct.State.sortBy = sortBy @@ -79,7 +80,7 @@ func (ct *Cointop) Sort(sortBy string, desc bool, list []*Coin, renderHeaders bo // SortAsc sorts list of coins in ascending order func (ct *Cointop) SortAsc() error { - ct.debuglog("SortAsc()") + log.Debug("SortAsc()") ct.State.sortDesc = false ct.UpdateTable() return nil @@ -87,7 +88,7 @@ func (ct *Cointop) SortAsc() error { // SortDesc sorts list of coins in descending order func (ct *Cointop) SortDesc() error { - ct.debuglog("SortDesc()") + log.Debug("SortDesc()") ct.State.sortDesc = true ct.UpdateTable() return nil @@ -95,7 +96,7 @@ func (ct *Cointop) SortDesc() error { // SortPrevCol sorts the previous column func (ct *Cointop) SortPrevCol() error { - ct.debuglog("SortPrevCol()") + log.Debug("SortPrevCol()") cols := ct.GetActiveTableHeaders() i := ct.GetSortColIndex() k := i - 1 @@ -110,7 +111,7 @@ func (ct *Cointop) SortPrevCol() error { // SortNextCol sorts the next column func (ct *Cointop) SortNextCol() error { - ct.debuglog("SortNextCol()") + log.Debug("SortNextCol()") cols := ct.GetActiveTableHeaders() l := len(cols) i := ct.GetSortColIndex() @@ -126,7 +127,7 @@ func (ct *Cointop) SortNextCol() error { // SortToggle toggles the sort order func (ct *Cointop) SortToggle(sortBy string, desc bool) error { - ct.debuglog("SortToggle()") + log.Debug("SortToggle()") if ct.State.sortBy == sortBy { desc = !ct.State.sortDesc } @@ -138,7 +139,7 @@ func (ct *Cointop) SortToggle(sortBy string, desc bool) error { // Sortfn returns the sort function as a wrapped gocui keybinding function func (ct *Cointop) Sortfn(sortBy string, desc bool) func(g *gocui.Gui, v *gocui.View) error { - ct.debuglog("Sortfn()") + log.Debug("Sortfn()") return func(g *gocui.Gui, v *gocui.View) error { coin := ct.HighlightedRowCoin() err := ct.SortToggle(sortBy, desc) @@ -157,7 +158,7 @@ func (ct *Cointop) Sortfn(sortBy string, desc bool) func(g *gocui.Gui, v *gocui. // GetSortColIndex gets the sort column index func (ct *Cointop) GetSortColIndex() int { - ct.debuglog("GetSortColIndex()") + log.Debug("GetSortColIndex()") cols := ct.GetActiveTableHeaders() for i, col := range cols { if ct.State.sortBy == col { diff --git a/cointop/statusbar.go b/cointop/statusbar.go index 56a7fe9..72746e7 100644 --- a/cointop/statusbar.go +++ b/cointop/statusbar.go @@ -7,6 +7,7 @@ import ( "github.com/miguelmota/cointop/pkg/open" "github.com/miguelmota/cointop/pkg/pad" "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" ) // StatusbarView is structure for statusbar view @@ -20,7 +21,7 @@ func NewStatusbarView() *StatusbarView { // UpdateStatusbar updates the statusbar view func (ct *Cointop) UpdateStatusbar(s string) error { - ct.debuglog("UpdateStatusbar()") + log.Debug("UpdateStatusbar()") currpage := ct.CurrentDisplayPage() totalpages := ct.TotalPagesDisplay() var quitText string @@ -72,7 +73,7 @@ func (ct *Cointop) UpdateStatusbar(s string) error { // RefreshRowLink updates the row link in the statusbar func (ct *Cointop) RefreshRowLink() error { - ct.debuglog("RefreshRowLink()") + log.Debug("RefreshRowLink()") var shortcut string if !open.CommandExists() { shortcut = "[O]Open " diff --git a/cointop/stdin.go b/cointop/stdin.go index 714f938..67c9716 100644 --- a/cointop/stdin.go +++ b/cointop/stdin.go @@ -5,11 +5,13 @@ import ( "fmt" "os" "strings" + + log "github.com/sirupsen/logrus" ) // ReadAPIKeyFromStdin reads the user inputed API from the stdin prompt func (ct *Cointop) ReadAPIKeyFromStdin(name string) (string, error) { - ct.debuglog("ReadAPIKeyFromStdin()") + log.Debug("ReadAPIKeyFromStdin()") reader := bufio.NewReader(os.Stdin) fmt.Printf("Enter %s API Key: ", name) text, err := reader.ReadString('\n') diff --git a/cointop/table.go b/cointop/table.go index 46d83b1..48da939 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" ) // TableView is structure for table view @@ -21,7 +22,7 @@ const dots = "..." // RefreshTable refreshes the table func (ct *Cointop) RefreshTable() error { - ct.debuglog("RefreshTable()") + log.Debug("RefreshTable()") statusText := "" switch ct.State.selectedView { @@ -64,7 +65,7 @@ func (ct *Cointop) RefreshTable() error { // UpdateTable updates the table func (ct *Cointop) UpdateTable() error { - ct.debuglog("UpdateTable()") + log.Debug("UpdateTable()") ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { k := key.(string) if v, ok := value.(*Coin); ok { @@ -96,7 +97,7 @@ func (ct *Cointop) UpdateTable() error { // GetTableCoinsSlice returns a slice of the table rows func (ct *Cointop) GetTableCoinsSlice() []*Coin { - ct.debuglog("GetTableCoinsSlice()") + log.Debug("GetTableCoinsSlice()") sliced := []*Coin{} start := ct.State.page * ct.State.perPage end := start + ct.State.perPage @@ -139,7 +140,7 @@ func (ct *Cointop) GetTableCoinsSlice() []*Coin { // HighlightedRowIndex returns the index of the highlighted row within the per-page limit func (ct *Cointop) HighlightedRowIndex() int { - ct.debuglog("HighlightedRowIndex()") + log.Debug("HighlightedRowIndex()") oy := ct.Views.Table.OriginY() cy := ct.Views.Table.CursorY() idx := oy + cy @@ -155,7 +156,7 @@ func (ct *Cointop) HighlightedRowIndex() int { // HighlightedRowCoin returns the coin at the index of the highlighted row func (ct *Cointop) HighlightedRowCoin() *Coin { - ct.debuglog("HighlightedRowCoin()") + log.Debug("HighlightedRowCoin()") idx := ct.HighlightedRowIndex() coins := ct.State.coins if ct.IsPriceAlertsVisible() { @@ -174,7 +175,7 @@ func (ct *Cointop) HighlightedRowCoin() *Coin { // HighlightedPageRowIndex returns the index of page row of the highlighted row func (ct *Cointop) HighlightedPageRowIndex() int { - ct.debuglog("HighlightedPageRowIndex()") + log.Debug("HighlightedPageRowIndex()") cy := ct.Views.Table.CursorY() idx := cy if idx < 0 { @@ -191,7 +192,7 @@ func (ct *Cointop) GetLastSelectedRowCoinIndex() int { // RowLink returns the row url link func (ct *Cointop) RowLink() string { - ct.debuglog("RowLink()") + log.Debug("RowLink()") coin := ct.HighlightedRowCoin() if coin == nil { return "" @@ -202,7 +203,7 @@ func (ct *Cointop) RowLink() string { // RowLinkShort returns a shortened version of the row url link func (ct *Cointop) RowLinkShort() string { - ct.debuglog("RowLinkShort()") + log.Debug("RowLinkShort()") link := ct.RowLink() if link != "" { u, err := url.Parse(link) @@ -226,7 +227,7 @@ func (ct *Cointop) RowLinkShort() string { // ToggleTableFullscreen toggles the table fullscreen mode func (ct *Cointop) ToggleTableFullscreen() error { - ct.debuglog("ToggleTableFullscreen()") + log.Debug("ToggleTableFullscreen()") ct.State.onlyTable = !ct.State.onlyTable ct.State.onlyChart = false if !ct.State.onlyTable { diff --git a/cointop/table_header.go b/cointop/table_header.go index 359c0a7..e42ae4a 100644 --- a/cointop/table_header.go +++ b/cointop/table_header.go @@ -8,6 +8,7 @@ import ( "github.com/miguelmota/cointop/pkg/pad" "github.com/miguelmota/cointop/pkg/ui" + log "github.com/sirupsen/logrus" ) // ArrowUp is up arrow unicode character @@ -147,7 +148,7 @@ func (ct *Cointop) GetActiveTableHeaders() []string { // UpdateTableHeader renders the table header func (ct *Cointop) UpdateTableHeader() error { - ct.debuglog("UpdateTableHeader()") + log.Debug("UpdateTableHeader()") baseColor := ct.colorscheme.TableHeaderSprintf() noSort := ct.IsPriceAlertsVisible() diff --git a/cointop/update.go b/cointop/update.go index b97e8fd..8f0a892 100644 --- a/cointop/update.go +++ b/cointop/update.go @@ -2,11 +2,12 @@ package cointop import ( "github.com/miguelmota/gocui" + log "github.com/sirupsen/logrus" ) // UpdateUI takes a callback which updates the view func (ct *Cointop) UpdateUI(f func() error) { - ct.debuglog("UpdateUI()") + log.Debug("UpdateUI()") if ct.g == nil { return diff --git a/cointop/util.go b/cointop/util.go index 01cd3e5..7efbb8c 100644 --- a/cointop/util.go +++ b/cointop/util.go @@ -9,11 +9,12 @@ import ( "sync" "github.com/miguelmota/cointop/pkg/open" + log "github.com/sirupsen/logrus" ) // OpenLink opens the url in a browser func (ct *Cointop) OpenLink() error { - ct.debuglog("OpenLink()") + log.Debug("OpenLink()") open.URL(ct.RowLink()) return nil } diff --git a/go.mod b/go.mod index c03196f..cab6ae3 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 github.com/olekukonko/tablewriter v0.0.5 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 diff --git a/go.sum b/go.sum index d9c36c8..095a94d 100644 --- a/go.sum +++ b/go.sum @@ -268,6 +268,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= @@ -430,6 +432,7 @@ golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/sirupsen/logrus/.gitignore b/vendor/github.com/sirupsen/logrus/.gitignore new file mode 100644 index 0000000..1fb13ab --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/.gitignore @@ -0,0 +1,4 @@ +logrus +vendor + +.idea/ diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml new file mode 100644 index 0000000..65dc285 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/.golangci.yml @@ -0,0 +1,40 @@ +run: + # do not run on test files yet + tests: false + +# all available settings of specific linters +linters-settings: + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: false + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: false + + lll: + line-length: 100 + tab-width: 4 + + prealloc: + simple: false + range-loops: false + for-loops: false + + whitespace: + multi-if: false # Enforces newlines (or comments) after every multi-line if statement + multi-func: false # Enforces newlines (or comments) after every multi-line function signature + +linters: + enable: + - megacheck + - govet + disable: + - maligned + - prealloc + disable-all: false + presets: + - bugs + - unused + fast: false diff --git a/vendor/github.com/sirupsen/logrus/.travis.yml b/vendor/github.com/sirupsen/logrus/.travis.yml new file mode 100644 index 0000000..c1dbd5a --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/.travis.yml @@ -0,0 +1,15 @@ +language: go +go_import_path: github.com/sirupsen/logrus +git: + depth: 1 +env: + - GO111MODULE=on +go: 1.15.x +os: linux +install: + - ./travis/install.sh +script: + - cd ci + - go run mage.go -v -w ../ crossBuild + - go run mage.go -v -w ../ lint + - go run mage.go -v -w ../ test diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md new file mode 100644 index 0000000..7567f61 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md @@ -0,0 +1,259 @@ +# 1.8.1 +Code quality: + * move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer + * improve timestamp format documentation + +Fixes: + * fix race condition on logger hooks + + +# 1.8.0 + +Correct versioning number replacing v1.7.1. + +# 1.7.1 + +Beware this release has introduced a new public API and its semver is therefore incorrect. + +Code quality: + * use go 1.15 in travis + * use magefile as task runner + +Fixes: + * small fixes about new go 1.13 error formatting system + * Fix for long time race condiction with mutating data hooks + +Features: + * build support for zos + +# 1.7.0 +Fixes: + * the dependency toward a windows terminal library has been removed + +Features: + * a new buffer pool management API has been added + * a set of `Fn()` functions have been added + +# 1.6.0 +Fixes: + * end of line cleanup + * revert the entry concurrency bug fix whic leads to deadlock under some circumstances + * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 + +Features: + * add an option to the `TextFormatter` to completely disable fields quoting + +# 1.5.0 +Code quality: + * add golangci linter run on travis + +Fixes: + * add mutex for hooks concurrent access on `Entry` data + * caller function field for go1.14 + * fix build issue for gopherjs target + +Feature: + * add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level + * add a `DisableHTMLEscape` option in the `JSONFormatter` + * add `ForceQuote` and `PadLevelText` options in the `TextFormatter` + +# 1.4.2 + * Fixes build break for plan9, nacl, solaris +# 1.4.1 +This new release introduces: + * Enhance TextFormatter to not print caller information when they are empty (#944) + * Remove dependency on golang.org/x/crypto (#932, #943) + +Fixes: + * Fix Entry.WithContext method to return a copy of the initial entry (#941) + +# 1.4.0 +This new release introduces: + * Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848). + * Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911) + * Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919). + +Fixes: + * Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893). + * Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903) + * Fix infinite recursion on unknown `Level.String()` (#907) + * Fix race condition in `getCaller` (#916). + + +# 1.3.0 +This new release introduces: + * Log, Logf, Logln functions for Logger and Entry that take a Level + +Fixes: + * Building prometheus node_exporter on AIX (#840) + * Race condition in TextFormatter (#468) + * Travis CI import path (#868) + * Remove coloured output on Windows (#862) + * Pointer to func as field in JSONFormatter (#870) + * Properly marshal Levels (#873) + +# 1.2.0 +This new release introduces: + * A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued + * A new trace level named `Trace` whose level is below `Debug` + * A configurable exit function to be called upon a Fatal trace + * The `Level` object now implements `encoding.TextUnmarshaler` interface + +# 1.1.1 +This is a bug fix release. + * fix the build break on Solaris + * don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized + +# 1.1.0 +This new release introduces: + * several fixes: + * a fix for a race condition on entry formatting + * proper cleanup of previously used entries before putting them back in the pool + * the extra new line at the end of message in text formatter has been removed + * a new global public API to check if a level is activated: IsLevelEnabled + * the following methods have been added to the Logger object + * IsLevelEnabled + * SetFormatter + * SetOutput + * ReplaceHooks + * introduction of go module + * an indent configuration for the json formatter + * output colour support for windows + * the field sort function is now configurable for text formatter + * the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater + +# 1.0.6 + +This new release introduces: + * a new api WithTime which allows to easily force the time of the log entry + which is mostly useful for logger wrapper + * a fix reverting the immutability of the entry given as parameter to the hooks + a new configuration field of the json formatter in order to put all the fields + in a nested dictionnary + * a new SetOutput method in the Logger + * a new configuration of the textformatter to configure the name of the default keys + * a new configuration of the text formatter to disable the level truncation + +# 1.0.5 + +* Fix hooks race (#707) +* Fix panic deadlock (#695) + +# 1.0.4 + +* Fix race when adding hooks (#612) +* Fix terminal check in AppEngine (#635) + +# 1.0.3 + +* Replace example files with testable examples + +# 1.0.2 + +* bug: quote non-string values in text formatter (#583) +* Make (*Logger) SetLevel a public method + +# 1.0.1 + +* bug: fix escaping in text formatter (#575) + +# 1.0.0 + +* Officially changed name to lower-case +* bug: colors on Windows 10 (#541) +* bug: fix race in accessing level (#512) + +# 0.11.5 + +* feature: add writer and writerlevel to entry (#372) + +# 0.11.4 + +* bug: fix undefined variable on solaris (#493) + +# 0.11.3 + +* formatter: configure quoting of empty values (#484) +* formatter: configure quoting character (default is `"`) (#484) +* bug: fix not importing io correctly in non-linux environments (#481) + +# 0.11.2 + +* bug: fix windows terminal detection (#476) + +# 0.11.1 + +* bug: fix tty detection with custom out (#471) + +# 0.11.0 + +* performance: Use bufferpool to allocate (#370) +* terminal: terminal detection for app-engine (#343) +* feature: exit handler (#375) + +# 0.10.0 + +* feature: Add a test hook (#180) +* feature: `ParseLevel` is now case-insensitive (#326) +* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308) +* performance: avoid re-allocations on `WithFields` (#335) + +# 0.9.0 + +* logrus/text_formatter: don't emit empty msg +* logrus/hooks/airbrake: move out of main repository +* logrus/hooks/sentry: move out of main repository +* logrus/hooks/papertrail: move out of main repository +* logrus/hooks/bugsnag: move out of main repository +* logrus/core: run tests with `-race` +* logrus/core: detect TTY based on `stderr` +* logrus/core: support `WithError` on logger +* logrus/core: Solaris support + +# 0.8.7 + +* logrus/core: fix possible race (#216) +* logrus/doc: small typo fixes and doc improvements + + +# 0.8.6 + +* hooks/raven: allow passing an initialized client + +# 0.8.5 + +* logrus/core: revert #208 + +# 0.8.4 + +* formatter/text: fix data race (#218) + +# 0.8.3 + +* logrus/core: fix entry log level (#208) +* logrus/core: improve performance of text formatter by 40% +* logrus/core: expose `LevelHooks` type +* logrus/core: add support for DragonflyBSD and NetBSD +* formatter/text: print structs more verbosely + +# 0.8.2 + +* logrus: fix more Fatal family functions + +# 0.8.1 + +* logrus: fix not exiting on `Fatalf` and `Fatalln` + +# 0.8.0 + +* logrus: defaults to stderr instead of stdout +* hooks/sentry: add special field for `*http.Request` +* formatter/text: ignore Windows for colors + +# 0.7.3 + +* formatter/\*: allow configuration of timestamp layout + +# 0.7.2 + +* formatter/text: Add configuration option for time format (#158) diff --git a/vendor/github.com/sirupsen/logrus/LICENSE b/vendor/github.com/sirupsen/logrus/LICENSE new file mode 100644 index 0000000..f090cb4 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md new file mode 100644 index 0000000..5152b6a --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/README.md @@ -0,0 +1,513 @@ +# Logrus :walrus: [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus) + +Logrus is a structured logger for Go (golang), completely API compatible with +the standard library logger. + +**Logrus is in maintenance-mode.** We will not be introducing new features. It's +simply too hard to do in a way that won't break many people's projects, which is +the last thing you want from your Logging library (again...). + +This does not mean Logrus is dead. Logrus will continue to be maintained for +security, (backwards compatible) bug fixes, and performance (where we are +limited by the interface). + +I believe Logrus' biggest contribution is to have played a part in today's +widespread use of structured logging in Golang. There doesn't seem to be a +reason to do a major, breaking iteration into Logrus V2, since the fantastic Go +community has built those independently. Many fantastic alternatives have sprung +up. Logrus would look like those, had it been re-designed with what we know +about structured logging in Go today. Check out, for example, +[Zerolog][zerolog], [Zap][zap], and [Apex][apex]. + +[zerolog]: https://github.com/rs/zerolog +[zap]: https://github.com/uber-go/zap +[apex]: https://github.com/apex/log + +**Seeing weird case-sensitive problems?** It's in the past been possible to +import Logrus as both upper- and lower-case. Due to the Go package environment, +this caused issues in the community and we needed a standard. Some environments +experienced problems with the upper-case variant, so the lower-case was decided. +Everything using `logrus` will need to use the lower-case: +`github.com/sirupsen/logrus`. Any package that isn't, should be changed. + +To fix Glide, see [these +comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437). +For an in-depth explanation of the casing issue, see [this +comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276). + +Nicely color-coded in development (when a TTY is attached, otherwise just +plain text): + +![Colored](http://i.imgur.com/PY7qMwd.png) + +With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash +or Splunk: + +```json +{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the +ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} + +{"level":"warning","msg":"The group's number increased tremendously!", +"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"A giant walrus appears!", +"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.", +"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} + +{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true, +"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} +``` + +With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not +attached, the output is compatible with the +[logfmt](http://godoc.org/github.com/kr/logfmt) format: + +```text +time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 +time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 +time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true +time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 +time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 +time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true +``` +To ensure this behaviour even if a TTY is attached, set your formatter as follows: + +```go + log.SetFormatter(&log.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + }) +``` + +#### Logging Method Name + +If you wish to add the calling method as a field, instruct the logger via: +```go +log.SetReportCaller(true) +``` +This adds the caller as 'method' like so: + +```json +{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by", +"time":"2014-03-10 19:57:38.562543129 -0400 EDT"} +``` + +```text +time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin +``` +Note that this does add measurable overhead - the cost will depend on the version of Go, but is +between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your +environment via benchmarks: +``` +go test -bench=.*CallerTracing +``` + + +#### Case-sensitivity + +The organization's name was changed to lower-case--and this will not be changed +back. If you are getting import conflicts due to case sensitivity, please use +the lower-case import: `github.com/sirupsen/logrus`. + +#### Example + +The simplest way to use Logrus is simply the package-level exported logger: + +```go +package main + +import ( + log "github.com/sirupsen/logrus" +) + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + }).Info("A walrus appears") +} +``` + +Note that it's completely api-compatible with the stdlib logger, so you can +replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"` +and you'll now have the flexibility of Logrus. You can customize it all you +want: + +```go +package main + +import ( + "os" + log "github.com/sirupsen/logrus" +) + +func init() { + // Log as JSON instead of the default ASCII formatter. + log.SetFormatter(&log.JSONFormatter{}) + + // Output to stdout instead of the default stderr + // Can be any io.Writer, see below for File example + log.SetOutput(os.Stdout) + + // Only log the warning severity or above. + log.SetLevel(log.WarnLevel) +} + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(log.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(log.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") + + // A common pattern is to re-use fields between logging statements by re-using + // the logrus.Entry returned from WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") +} +``` + +For more advanced usage such as logging to multiple locations from the same +application, you can also create an instance of the `logrus` Logger: + +```go +package main + +import ( + "os" + "github.com/sirupsen/logrus" +) + +// Create a new instance of the logger. You can have any number of instances. +var log = logrus.New() + +func main() { + // The API for setting attributes is a little different than the package level + // exported logger. See Godoc. + log.Out = os.Stdout + + // You could set this to any `io.Writer` such as a file + // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + // if err == nil { + // log.Out = file + // } else { + // log.Info("Failed to log to file, using default stderr") + // } + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") +} +``` + +#### Fields + +Logrus encourages careful, structured logging through logging fields instead of +long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +to send event %s to topic %s with key %d")`, you should log the much more +discoverable: + +```go +log.WithFields(log.Fields{ + "event": event, + "topic": topic, + "key": key, +}).Fatal("Failed to send event") +``` + +We've found this API forces you to think about logging in a way that produces +much more useful logging messages. We've been in countless situations where just +a single added field to a log statement that was already there would've saved us +hours. The `WithFields` call is optional. + +In general, with Logrus using any of the `printf`-family functions should be +seen as a hint you should add a field, however, you can still use the +`printf`-family functions with Logrus. + +#### Default Fields + +Often it's helpful to have fields _always_ attached to log statements in an +application or parts of one. For example, you may want to always log the +`request_id` and `user_ip` in the context of a request. Instead of writing +`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on +every line, you can create a `logrus.Entry` to pass around instead: + +```go +requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) +requestLogger.Info("something happened on that request") # will log request_id and user_ip +requestLogger.Warn("something not great happened") +``` + +#### Hooks + +You can add hooks for logging levels. For example to send errors to an exception +tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to +multiple places simultaneously, e.g. syslog. + +Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in +`init`: + +```go +import ( + log "github.com/sirupsen/logrus" + "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" + logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" + "log/syslog" +) + +func init() { + + // Use the Airbrake hook to report errors that have Error severity or above to + // an exception tracker. You can create custom hooks, see the Hooks section. + log.AddHook(airbrake.NewHook(123, "xyz", "production")) + + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + if err != nil { + log.Error("Unable to connect to local syslog daemon") + } else { + log.AddHook(hook) + } +} +``` +Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). + +A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) + + +#### Level logging + +Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. + +```go +log.Trace("Something very low level.") +log.Debug("Useful debugging information.") +log.Info("Something noteworthy happened!") +log.Warn("You should probably take a look at this.") +log.Error("Something failed but I'm not quitting.") +// Calls os.Exit(1) after logging +log.Fatal("Bye.") +// Calls panic() after logging +log.Panic("I'm bailing.") +``` + +You can set the logging level on a `Logger`, then it will only log entries with +that severity or anything above it: + +```go +// Will log anything that is info or above (warn, error, fatal, panic). Default. +log.SetLevel(log.InfoLevel) +``` + +It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +environment if your application has that. + +#### Entries + +Besides the fields added with `WithField` or `WithFields` some fields are +automatically added to all logging events: + +1. `time`. The timestamp when the entry was created. +2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after + the `AddFields` call. E.g. `Failed to send event.` +3. `level`. The logging level. E.g. `info`. + +#### Environments + +Logrus has no notion of environment. + +If you wish for hooks and formatters to only be used in specific environments, +you should handle that yourself. For example, if your application has a global +variable `Environment`, which is a string representation of the environment you +could do: + +```go +import ( + log "github.com/sirupsen/logrus" +) + +init() { + // do something here to set environment depending on an environment variable + // or command-line flag + if Environment == "production" { + log.SetFormatter(&log.JSONFormatter{}) + } else { + // The TextFormatter is default, you don't actually have to do this. + log.SetFormatter(&log.TextFormatter{}) + } +} +``` + +This configuration is how `logrus` was intended to be used, but JSON in +production is mostly only useful if you do log aggregation with tools like +Splunk or Logstash. + +#### Formatters + +The built-in logging formatters are: + +* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise + without colors. + * *Note:* to force colored output when there is no TTY, set the `ForceColors` + field to `true`. To force no colored output even if there is a TTY set the + `DisableColors` field to `true`. For Windows, see + [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable). + * When colors are enabled, levels are truncated to 4 characters by default. To disable + truncation set the `DisableLevelTruncation` field to `true`. + * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text. + * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). +* `logrus.JSONFormatter`. Logs fields as JSON. + * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). + +Third party logging formatters: + +* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. +* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). +* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events. +* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. +* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo. +* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. +* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files. +* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added. + +You can define your formatter by implementing the `Formatter` interface, +requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a +`Fields` type (`map[string]interface{}`) with all your fields as well as the +default ones (see Entries section above): + +```go +type MyJSONFormatter struct { +} + +log.SetFormatter(new(MyJSONFormatter)) + +func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { + // Note this doesn't include Time, Level and Message which are available on + // the Entry. Consult `godoc` on information about those fields or read the + // source of the official loggers. + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err) + } + return append(serialized, '\n'), nil +} +``` + +#### Logger as an `io.Writer` + +Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it. + +```go +w := logger.Writer() +defer w.Close() + +srv := http.Server{ + // create a stdlib log.Logger that writes to + // logrus.Logger. + ErrorLog: log.New(w, "", 0), +} +``` + +Each line written to that writer will be printed the usual way, using formatters +and hooks. The level for those entries is `info`. + +This means that we can override the standard library logger easily: + +```go +logger := logrus.New() +logger.Formatter = &logrus.JSONFormatter{} + +// Use logrus for standard log output +// Note that `log` here references stdlib's log +// Not logrus imported under the name `log`. +log.SetOutput(logger.Writer()) +``` + +#### Rotation + +Log rotation is not provided with Logrus. Log rotation should be done by an +external program (like `logrotate(8)`) that can compress and delete old log +entries. It should not be a feature of the application-level logger. + +#### Tools + +| Tool | Description | +| ---- | ----------- | +|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.| +|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) | + +#### Testing + +Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: + +* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook +* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any): + +```go +import( + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSomething(t*testing.T){ + logger, hook := test.NewNullLogger() + logger.Error("Helloerror") + + assert.Equal(t, 1, len(hook.Entries)) + assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level) + assert.Equal(t, "Helloerror", hook.LastEntry().Message) + + hook.Reset() + assert.Nil(t, hook.LastEntry()) +} +``` + +#### Fatal handlers + +Logrus can register one or more functions that will be called when any `fatal` +level message is logged. The registered handlers will be executed before +logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need +to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. + +``` +... +handler := func() { + // gracefully shutdown something... +} +logrus.RegisterExitHandler(handler) +... +``` + +#### Thread safety + +By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. +If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. + +Situation when locking is not needed includes: + +* You have no hooks registered, or hooks calling is already thread-safe. + +* Writing to logger.Out is already thread-safe, for example: + + 1) logger.Out is protected by locks. + + 2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing) + + (Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/) diff --git a/vendor/github.com/sirupsen/logrus/alt_exit.go b/vendor/github.com/sirupsen/logrus/alt_exit.go new file mode 100644 index 0000000..8fd189e --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/alt_exit.go @@ -0,0 +1,76 @@ +package logrus + +// The following code was sourced and modified from the +// https://github.com/tebeka/atexit package governed by the following license: +// +// Copyright (c) 2012 Miki Tebeka . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import ( + "fmt" + "os" +) + +var handlers = []func(){} + +func runHandler(handler func()) { + defer func() { + if err := recover(); err != nil { + fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err) + } + }() + + handler() +} + +func runHandlers() { + for _, handler := range handlers { + runHandler(handler) + } +} + +// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) +func Exit(code int) { + runHandlers() + os.Exit(code) +} + +// RegisterExitHandler appends a Logrus Exit handler to the list of handlers, +// call logrus.Exit to invoke all handlers. The handlers will also be invoked when +// any Fatal log entry is made. +// +// This method is useful when a caller wishes to use logrus to log a fatal +// message but also needs to gracefully shutdown. An example usecase could be +// closing database connections, or sending a alert that the application is +// closing. +func RegisterExitHandler(handler func()) { + handlers = append(handlers, handler) +} + +// DeferExitHandler prepends a Logrus Exit handler to the list of handlers, +// call logrus.Exit to invoke all handlers. The handlers will also be invoked when +// any Fatal log entry is made. +// +// This method is useful when a caller wishes to use logrus to log a fatal +// message but also needs to gracefully shutdown. An example usecase could be +// closing database connections, or sending a alert that the application is +// closing. +func DeferExitHandler(handler func()) { + handlers = append([]func(){handler}, handlers...) +} diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml new file mode 100644 index 0000000..df9d65c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/appveyor.yml @@ -0,0 +1,14 @@ +version: "{build}" +platform: x64 +clone_folder: c:\gopath\src\github.com\sirupsen\logrus +environment: + GOPATH: c:\gopath +branches: + only: + - master +install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - go version +build_script: + - go get -t + - go test diff --git a/vendor/github.com/sirupsen/logrus/buffer_pool.go b/vendor/github.com/sirupsen/logrus/buffer_pool.go new file mode 100644 index 0000000..4545dec --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/buffer_pool.go @@ -0,0 +1,52 @@ +package logrus + +import ( + "bytes" + "sync" +) + +var ( + bufferPool BufferPool +) + +type BufferPool interface { + Put(*bytes.Buffer) + Get() *bytes.Buffer +} + +type defaultPool struct { + pool *sync.Pool +} + +func (p *defaultPool) Put(buf *bytes.Buffer) { + p.pool.Put(buf) +} + +func (p *defaultPool) Get() *bytes.Buffer { + return p.pool.Get().(*bytes.Buffer) +} + +func getBuffer() *bytes.Buffer { + return bufferPool.Get() +} + +func putBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +// SetBufferPool allows to replace the default logrus buffer pool +// to better meets the specific needs of an application. +func SetBufferPool(bp BufferPool) { + bufferPool = bp +} + +func init() { + SetBufferPool(&defaultPool{ + pool: &sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, + }, + }) +} diff --git a/vendor/github.com/sirupsen/logrus/doc.go b/vendor/github.com/sirupsen/logrus/doc.go new file mode 100644 index 0000000..da67aba --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/doc.go @@ -0,0 +1,26 @@ +/* +Package logrus is a structured logger for Go, completely API compatible with the standard library logger. + + +The simplest way to use Logrus is simply the package-level exported logger: + + package main + + import ( + log "github.com/sirupsen/logrus" + ) + + func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "number": 1, + "size": 10, + }).Info("A walrus appears") + } + +Output: + time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10 + +For a full guide visit https://github.com/sirupsen/logrus +*/ +package logrus diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go new file mode 100644 index 0000000..07a1e5f --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -0,0 +1,431 @@ +package logrus + +import ( + "bytes" + "context" + "fmt" + "os" + "reflect" + "runtime" + "strings" + "sync" + "time" +) + +var ( + + // qualified package name, cached at first use + logrusPackage string + + // Positions in the call stack when tracing to report the calling method + minimumCallerDepth int + + // Used for caller information initialisation + callerInitOnce sync.Once +) + +const ( + maximumCallerDepth int = 25 + knownLogrusFrames int = 4 +) + +func init() { + // start at the bottom of the stack before the package-name cache is primed + minimumCallerDepth = 1 +} + +// Defines the key when adding errors using WithError. +var ErrorKey = "error" + +// An entry is the final or intermediate Logrus logging entry. It contains all +// the fields passed with WithField{,s}. It's finally logged when Trace, Debug, +// Info, Warn, Error, Fatal or Panic is called on it. These objects can be +// reused and passed around as much as you wish to avoid field duplication. +type Entry struct { + Logger *Logger + + // Contains all the fields set by the user. + Data Fields + + // Time at which the log entry was created + Time time.Time + + // Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic + // This field will be set on entry firing and the value will be equal to the one in Logger struct field. + Level Level + + // Calling method, with package name + Caller *runtime.Frame + + // Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic + Message string + + // When formatter is called in entry.log(), a Buffer may be set to entry + Buffer *bytes.Buffer + + // Contains the context set by the user. Useful for hook processing etc. + Context context.Context + + // err may contain a field formatting error + err string +} + +func NewEntry(logger *Logger) *Entry { + return &Entry{ + Logger: logger, + // Default is three fields, plus one optional. Give a little extra room. + Data: make(Fields, 6), + } +} + +func (entry *Entry) Dup() *Entry { + data := make(Fields, len(entry.Data)) + for k, v := range entry.Data { + data[k] = v + } + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} +} + +// Returns the bytes representation of this entry from the formatter. +func (entry *Entry) Bytes() ([]byte, error) { + return entry.Logger.Formatter.Format(entry) +} + +// Returns the string representation from the reader and ultimately the +// formatter. +func (entry *Entry) String() (string, error) { + serialized, err := entry.Bytes() + if err != nil { + return "", err + } + str := string(serialized) + return str, nil +} + +// Add an error as single field (using the key defined in ErrorKey) to the Entry. +func (entry *Entry) WithError(err error) *Entry { + return entry.WithField(ErrorKey, err) +} + +// Add a context to the Entry. +func (entry *Entry) WithContext(ctx context.Context) *Entry { + dataCopy := make(Fields, len(entry.Data)) + for k, v := range entry.Data { + dataCopy[k] = v + } + return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} +} + +// Add a single field to the Entry. +func (entry *Entry) WithField(key string, value interface{}) *Entry { + return entry.WithFields(Fields{key: value}) +} + +// Add a map of fields to the Entry. +func (entry *Entry) WithFields(fields Fields) *Entry { + data := make(Fields, len(entry.Data)+len(fields)) + for k, v := range entry.Data { + data[k] = v + } + fieldErr := entry.err + for k, v := range fields { + isErrField := false + if t := reflect.TypeOf(v); t != nil { + switch { + case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func: + isErrField = true + } + } + if isErrField { + tmp := fmt.Sprintf("can not add field %q", k) + if fieldErr != "" { + fieldErr = entry.err + ", " + tmp + } else { + fieldErr = tmp + } + } else { + data[k] = v + } + } + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} +} + +// Overrides the time of the Entry. +func (entry *Entry) WithTime(t time.Time) *Entry { + dataCopy := make(Fields, len(entry.Data)) + for k, v := range entry.Data { + dataCopy[k] = v + } + return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context} +} + +// getPackageName reduces a fully qualified function name to the package name +// There really ought to be to be a better way... +func getPackageName(f string) string { + for { + lastPeriod := strings.LastIndex(f, ".") + lastSlash := strings.LastIndex(f, "/") + if lastPeriod > lastSlash { + f = f[:lastPeriod] + } else { + break + } + } + + return f +} + +// getCaller retrieves the name of the first non-logrus calling function +func getCaller() *runtime.Frame { + // cache this package's fully-qualified name + callerInitOnce.Do(func() { + pcs := make([]uintptr, maximumCallerDepth) + _ = runtime.Callers(0, pcs) + + // dynamic get the package name and the minimum caller depth + for i := 0; i < maximumCallerDepth; i++ { + funcName := runtime.FuncForPC(pcs[i]).Name() + if strings.Contains(funcName, "getCaller") { + logrusPackage = getPackageName(funcName) + break + } + } + + minimumCallerDepth = knownLogrusFrames + }) + + // Restrict the lookback frames to avoid runaway lookups + pcs := make([]uintptr, maximumCallerDepth) + depth := runtime.Callers(minimumCallerDepth, pcs) + frames := runtime.CallersFrames(pcs[:depth]) + + for f, again := frames.Next(); again; f, again = frames.Next() { + pkg := getPackageName(f.Function) + + // If the caller isn't part of this package, we're done + if pkg != logrusPackage { + return &f //nolint:scopelint + } + } + + // if we got here, we failed to find the caller's context + return nil +} + +func (entry Entry) HasCaller() (has bool) { + return entry.Logger != nil && + entry.Logger.ReportCaller && + entry.Caller != nil +} + +func (entry *Entry) log(level Level, msg string) { + var buffer *bytes.Buffer + + newEntry := entry.Dup() + + if newEntry.Time.IsZero() { + newEntry.Time = time.Now() + } + + newEntry.Level = level + newEntry.Message = msg + + newEntry.Logger.mu.Lock() + reportCaller := newEntry.Logger.ReportCaller + newEntry.Logger.mu.Unlock() + + if reportCaller { + newEntry.Caller = getCaller() + } + + newEntry.fireHooks() + + buffer = getBuffer() + defer func() { + newEntry.Buffer = nil + putBuffer(buffer) + }() + buffer.Reset() + newEntry.Buffer = buffer + + newEntry.write() + + newEntry.Buffer = nil + + // To avoid Entry#log() returning a value that only would make sense for + // panic() to use in Entry#Panic(), we avoid the allocation by checking + // directly here. + if level <= PanicLevel { + panic(newEntry) + } +} + +func (entry *Entry) fireHooks() { + var tmpHooks LevelHooks + entry.Logger.mu.Lock() + tmpHooks = make(LevelHooks, len(entry.Logger.Hooks)) + for k, v := range entry.Logger.Hooks { + tmpHooks[k] = v + } + entry.Logger.mu.Unlock() + + err := tmpHooks.Fire(entry.Level, entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + } +} + +func (entry *Entry) write() { + serialized, err := entry.Logger.Formatter.Format(entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) + return + } + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + if _, err := entry.Logger.Out.Write(serialized); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } +} + +func (entry *Entry) Log(level Level, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.log(level, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Trace(args ...interface{}) { + entry.Log(TraceLevel, args...) +} + +func (entry *Entry) Debug(args ...interface{}) { + entry.Log(DebugLevel, args...) +} + +func (entry *Entry) Print(args ...interface{}) { + entry.Info(args...) +} + +func (entry *Entry) Info(args ...interface{}) { + entry.Log(InfoLevel, args...) +} + +func (entry *Entry) Warn(args ...interface{}) { + entry.Log(WarnLevel, args...) +} + +func (entry *Entry) Warning(args ...interface{}) { + entry.Warn(args...) +} + +func (entry *Entry) Error(args ...interface{}) { + entry.Log(ErrorLevel, args...) +} + +func (entry *Entry) Fatal(args ...interface{}) { + entry.Log(FatalLevel, args...) + entry.Logger.Exit(1) +} + +func (entry *Entry) Panic(args ...interface{}) { + entry.Log(PanicLevel, args...) +} + +// Entry Printf family functions + +func (entry *Entry) Logf(level Level, format string, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.Log(level, fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Tracef(format string, args ...interface{}) { + entry.Logf(TraceLevel, format, args...) +} + +func (entry *Entry) Debugf(format string, args ...interface{}) { + entry.Logf(DebugLevel, format, args...) +} + +func (entry *Entry) Infof(format string, args ...interface{}) { + entry.Logf(InfoLevel, format, args...) +} + +func (entry *Entry) Printf(format string, args ...interface{}) { + entry.Infof(format, args...) +} + +func (entry *Entry) Warnf(format string, args ...interface{}) { + entry.Logf(WarnLevel, format, args...) +} + +func (entry *Entry) Warningf(format string, args ...interface{}) { + entry.Warnf(format, args...) +} + +func (entry *Entry) Errorf(format string, args ...interface{}) { + entry.Logf(ErrorLevel, format, args...) +} + +func (entry *Entry) Fatalf(format string, args ...interface{}) { + entry.Logf(FatalLevel, format, args...) + entry.Logger.Exit(1) +} + +func (entry *Entry) Panicf(format string, args ...interface{}) { + entry.Logf(PanicLevel, format, args...) +} + +// Entry Println family functions + +func (entry *Entry) Logln(level Level, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.Log(level, entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Traceln(args ...interface{}) { + entry.Logln(TraceLevel, args...) +} + +func (entry *Entry) Debugln(args ...interface{}) { + entry.Logln(DebugLevel, args...) +} + +func (entry *Entry) Infoln(args ...interface{}) { + entry.Logln(InfoLevel, args...) +} + +func (entry *Entry) Println(args ...interface{}) { + entry.Infoln(args...) +} + +func (entry *Entry) Warnln(args ...interface{}) { + entry.Logln(WarnLevel, args...) +} + +func (entry *Entry) Warningln(args ...interface{}) { + entry.Warnln(args...) +} + +func (entry *Entry) Errorln(args ...interface{}) { + entry.Logln(ErrorLevel, args...) +} + +func (entry *Entry) Fatalln(args ...interface{}) { + entry.Logln(FatalLevel, args...) + entry.Logger.Exit(1) +} + +func (entry *Entry) Panicln(args ...interface{}) { + entry.Logln(PanicLevel, args...) +} + +// Sprintlnn => Sprint no newline. This is to get the behavior of how +// fmt.Sprintln where spaces are always added between operands, regardless of +// their type. Instead of vendoring the Sprintln implementation to spare a +// string allocation, we do the simplest thing. +func (entry *Entry) sprintlnn(args ...interface{}) string { + msg := fmt.Sprintln(args...) + return msg[:len(msg)-1] +} diff --git a/vendor/github.com/sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go new file mode 100644 index 0000000..017c30c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/exported.go @@ -0,0 +1,270 @@ +package logrus + +import ( + "context" + "io" + "time" +) + +var ( + // std is the name of the standard logger in stdlib `log` + std = New() +) + +func StandardLogger() *Logger { + return std +} + +// SetOutput sets the standard logger output. +func SetOutput(out io.Writer) { + std.SetOutput(out) +} + +// SetFormatter sets the standard logger formatter. +func SetFormatter(formatter Formatter) { + std.SetFormatter(formatter) +} + +// SetReportCaller sets whether the standard logger will include the calling +// method as a field. +func SetReportCaller(include bool) { + std.SetReportCaller(include) +} + +// SetLevel sets the standard logger level. +func SetLevel(level Level) { + std.SetLevel(level) +} + +// GetLevel returns the standard logger level. +func GetLevel() Level { + return std.GetLevel() +} + +// IsLevelEnabled checks if the log level of the standard logger is greater than the level param +func IsLevelEnabled(level Level) bool { + return std.IsLevelEnabled(level) +} + +// AddHook adds a hook to the standard logger hooks. +func AddHook(hook Hook) { + std.AddHook(hook) +} + +// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. +func WithError(err error) *Entry { + return std.WithField(ErrorKey, err) +} + +// WithContext creates an entry from the standard logger and adds a context to it. +func WithContext(ctx context.Context) *Entry { + return std.WithContext(ctx) +} + +// WithField creates an entry from the standard logger and adds a field to +// it. If you want multiple fields, use `WithFields`. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithField(key string, value interface{}) *Entry { + return std.WithField(key, value) +} + +// WithFields creates an entry from the standard logger and adds multiple +// fields to it. This is simply a helper for `WithField`, invoking it +// once for each field. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithFields(fields Fields) *Entry { + return std.WithFields(fields) +} + +// WithTime creates an entry from the standard logger and overrides the time of +// logs generated with it. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithTime(t time.Time) *Entry { + return std.WithTime(t) +} + +// Trace logs a message at level Trace on the standard logger. +func Trace(args ...interface{}) { + std.Trace(args...) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...interface{}) { + std.Debug(args...) +} + +// Print logs a message at level Info on the standard logger. +func Print(args ...interface{}) { + std.Print(args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...interface{}) { + std.Info(args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...interface{}) { + std.Warn(args...) +} + +// Warning logs a message at level Warn on the standard logger. +func Warning(args ...interface{}) { + std.Warning(args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...interface{}) { + std.Error(args...) +} + +// Panic logs a message at level Panic on the standard logger. +func Panic(args ...interface{}) { + std.Panic(args...) +} + +// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatal(args ...interface{}) { + std.Fatal(args...) +} + +// TraceFn logs a message from a func at level Trace on the standard logger. +func TraceFn(fn LogFunction) { + std.TraceFn(fn) +} + +// DebugFn logs a message from a func at level Debug on the standard logger. +func DebugFn(fn LogFunction) { + std.DebugFn(fn) +} + +// PrintFn logs a message from a func at level Info on the standard logger. +func PrintFn(fn LogFunction) { + std.PrintFn(fn) +} + +// InfoFn logs a message from a func at level Info on the standard logger. +func InfoFn(fn LogFunction) { + std.InfoFn(fn) +} + +// WarnFn logs a message from a func at level Warn on the standard logger. +func WarnFn(fn LogFunction) { + std.WarnFn(fn) +} + +// WarningFn logs a message from a func at level Warn on the standard logger. +func WarningFn(fn LogFunction) { + std.WarningFn(fn) +} + +// ErrorFn logs a message from a func at level Error on the standard logger. +func ErrorFn(fn LogFunction) { + std.ErrorFn(fn) +} + +// PanicFn logs a message from a func at level Panic on the standard logger. +func PanicFn(fn LogFunction) { + std.PanicFn(fn) +} + +// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1. +func FatalFn(fn LogFunction) { + std.FatalFn(fn) +} + +// Tracef logs a message at level Trace on the standard logger. +func Tracef(format string, args ...interface{}) { + std.Tracef(format, args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...interface{}) { + std.Debugf(format, args...) +} + +// Printf logs a message at level Info on the standard logger. +func Printf(format string, args ...interface{}) { + std.Printf(format, args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...interface{}) { + std.Infof(format, args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...interface{}) { + std.Warnf(format, args...) +} + +// Warningf logs a message at level Warn on the standard logger. +func Warningf(format string, args ...interface{}) { + std.Warningf(format, args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...interface{}) { + std.Errorf(format, args...) +} + +// Panicf logs a message at level Panic on the standard logger. +func Panicf(format string, args ...interface{}) { + std.Panicf(format, args...) +} + +// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatalf(format string, args ...interface{}) { + std.Fatalf(format, args...) +} + +// Traceln logs a message at level Trace on the standard logger. +func Traceln(args ...interface{}) { + std.Traceln(args...) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(args ...interface{}) { + std.Debugln(args...) +} + +// Println logs a message at level Info on the standard logger. +func Println(args ...interface{}) { + std.Println(args...) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(args ...interface{}) { + std.Infoln(args...) +} + +// Warnln logs a message at level Warn on the standard logger. +func Warnln(args ...interface{}) { + std.Warnln(args...) +} + +// Warningln logs a message at level Warn on the standard logger. +func Warningln(args ...interface{}) { + std.Warningln(args...) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(args ...interface{}) { + std.Errorln(args...) +} + +// Panicln logs a message at level Panic on the standard logger. +func Panicln(args ...interface{}) { + std.Panicln(args...) +} + +// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatalln(args ...interface{}) { + std.Fatalln(args...) +} diff --git a/vendor/github.com/sirupsen/logrus/formatter.go b/vendor/github.com/sirupsen/logrus/formatter.go new file mode 100644 index 0000000..4088837 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/formatter.go @@ -0,0 +1,78 @@ +package logrus + +import "time" + +// Default key names for the default fields +const ( + defaultTimestampFormat = time.RFC3339 + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyLogrusError = "logrus_error" + FieldKeyFunc = "func" + FieldKeyFile = "file" +) + +// The Formatter interface is used to implement a custom Formatter. It takes an +// `Entry`. It exposes all the fields, including the default ones: +// +// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. +// * `entry.Data["time"]`. The timestamp. +// * `entry.Data["level"]. The level the entry was logged at. +// +// Any additional fields added with `WithField` or `WithFields` are also in +// `entry.Data`. Format is expected to return an array of bytes which are then +// logged to `logger.Out`. +type Formatter interface { + Format(*Entry) ([]byte, error) +} + +// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// Would just silently drop the user provided level. Instead with this code +// it'll logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +// +// It's not exported because it's still using Data in an opinionated way. It's to +// avoid code duplication between the two default formatters. +func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) { + timeKey := fieldMap.resolve(FieldKeyTime) + if t, ok := data[timeKey]; ok { + data["fields."+timeKey] = t + delete(data, timeKey) + } + + msgKey := fieldMap.resolve(FieldKeyMsg) + if m, ok := data[msgKey]; ok { + data["fields."+msgKey] = m + delete(data, msgKey) + } + + levelKey := fieldMap.resolve(FieldKeyLevel) + if l, ok := data[levelKey]; ok { + data["fields."+levelKey] = l + delete(data, levelKey) + } + + logrusErrKey := fieldMap.resolve(FieldKeyLogrusError) + if l, ok := data[logrusErrKey]; ok { + data["fields."+logrusErrKey] = l + delete(data, logrusErrKey) + } + + // If reportCaller is not set, 'func' will not conflict. + if reportCaller { + funcKey := fieldMap.resolve(FieldKeyFunc) + if l, ok := data[funcKey]; ok { + data["fields."+funcKey] = l + } + fileKey := fieldMap.resolve(FieldKeyFile) + if l, ok := data[fileKey]; ok { + data["fields."+fileKey] = l + } + } +} diff --git a/vendor/github.com/sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go new file mode 100644 index 0000000..3f151cd --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/hooks.go @@ -0,0 +1,34 @@ +package logrus + +// A hook to be fired when logging on the logging levels returned from +// `Levels()` on your implementation of the interface. Note that this is not +// fired in a goroutine or a channel with workers, you should handle such +// functionality yourself if your call is non-blocking and you don't wish for +// the logging calls for levels returned from `Levels()` to block. +type Hook interface { + Levels() []Level + Fire(*Entry) error +} + +// Internal type for storing the hooks on a logger instance. +type LevelHooks map[Level][]Hook + +// Add a hook to an instance of logger. This is called with +// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. +func (hooks LevelHooks) Add(hook Hook) { + for _, level := range hook.Levels() { + hooks[level] = append(hooks[level], hook) + } +} + +// Fire all the hooks for the passed level. Used by `entry.log` to fire +// appropriate hooks for a log entry. +func (hooks LevelHooks) Fire(level Level, entry *Entry) error { + for _, hook := range hooks[level] { + if err := hook.Fire(entry); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go new file mode 100644 index 0000000..c96dc56 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/json_formatter.go @@ -0,0 +1,128 @@ +package logrus + +import ( + "bytes" + "encoding/json" + "fmt" + "runtime" +) + +type fieldKey string + +// FieldMap allows customization of the key names for default fields. +type FieldMap map[fieldKey]string + +func (f FieldMap) resolve(key fieldKey) string { + if k, ok := f[key]; ok { + return k + } + + return string(key) +} + +// JSONFormatter formats logs into parsable json +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + // The format to use is the same than for time.Format or time.Parse from the standard + // library. + // The standard Library already provides a set of predefined format. + TimestampFormat string + + // DisableTimestamp allows disabling automatic timestamps in output + DisableTimestamp bool + + // DisableHTMLEscape allows disabling html escaping in output + DisableHTMLEscape bool + + // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. + DataKey string + + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &JSONFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message", + // FieldKeyFunc: "@caller", + // }, + // } + FieldMap FieldMap + + // CallerPrettyfier can be set by the user to modify the content + // of the function and file keys in the json data when ReportCaller is + // activated. If any of the returned value is the empty string the + // corresponding key will be removed from json fields. + CallerPrettyfier func(*runtime.Frame) (function string, file string) + + // PrettyPrint will indent all json logs + PrettyPrint bool +} + +// Format renders a single log entry +func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields, len(entry.Data)+4) + for k, v := range entry.Data { + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/sirupsen/logrus/issues/137 + data[k] = v.Error() + default: + data[k] = v + } + } + + if f.DataKey != "" { + newData := make(Fields, 4) + newData[f.DataKey] = data + data = newData + } + + prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + + if entry.err != "" { + data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err + } + if !f.DisableTimestamp { + data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) + } + data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message + data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() + if entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } + if funcVal != "" { + data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal + } + if fileVal != "" { + data[f.FieldMap.resolve(FieldKeyFile)] = fileVal + } + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + encoder := json.NewEncoder(b) + encoder.SetEscapeHTML(!f.DisableHTMLEscape) + if f.PrettyPrint { + encoder.SetIndent("", " ") + } + if err := encoder.Encode(data); err != nil { + return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err) + } + + return b.Bytes(), nil +} diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go new file mode 100644 index 0000000..3377044 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/logger.go @@ -0,0 +1,404 @@ +package logrus + +import ( + "context" + "io" + "os" + "sync" + "sync/atomic" + "time" +) + +// LogFunction For big messages, it can be more efficient to pass a function +// and only call it if the log level is actually enables rather than +// generating the log message and then checking if the level is enabled +type LogFunction func() []interface{} + +type Logger struct { + // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a + // file, or leave it default which is `os.Stderr`. You can also set this to + // something more adventurous, such as logging to Kafka. + Out io.Writer + // Hooks for the logger instance. These allow firing events based on logging + // levels and log entries. For example, to send errors to an error tracking + // service, log to StatsD or dump the core on fatal errors. + Hooks LevelHooks + // All log entries pass through the formatter before logged to Out. The + // included formatters are `TextFormatter` and `JSONFormatter` for which + // TextFormatter is the default. In development (when a TTY is attached) it + // logs with colors, but to a file it wouldn't. You can easily implement your + // own that implements the `Formatter` interface, see the `README` or included + // formatters for examples. + Formatter Formatter + + // Flag for whether to log caller info (off by default) + ReportCaller bool + + // The logging level the logger should log at. This is typically (and defaults + // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be + // logged. + Level Level + // Used to sync writing to the log. Locking is enabled by Default + mu MutexWrap + // Reusable empty entry + entryPool sync.Pool + // Function to exit the application, defaults to `os.Exit()` + ExitFunc exitFunc +} + +type exitFunc func(int) + +type MutexWrap struct { + lock sync.Mutex + disabled bool +} + +func (mw *MutexWrap) Lock() { + if !mw.disabled { + mw.lock.Lock() + } +} + +func (mw *MutexWrap) Unlock() { + if !mw.disabled { + mw.lock.Unlock() + } +} + +func (mw *MutexWrap) Disable() { + mw.disabled = true +} + +// Creates a new logger. Configuration should be set by changing `Formatter`, +// `Out` and `Hooks` directly on the default logger instance. You can also just +// instantiate your own: +// +// var log = &logrus.Logger{ +// Out: os.Stderr, +// Formatter: new(logrus.TextFormatter), +// Hooks: make(logrus.LevelHooks), +// Level: logrus.DebugLevel, +// } +// +// It's recommended to make this a global instance called `log`. +func New() *Logger { + return &Logger{ + Out: os.Stderr, + Formatter: new(TextFormatter), + Hooks: make(LevelHooks), + Level: InfoLevel, + ExitFunc: os.Exit, + ReportCaller: false, + } +} + +func (logger *Logger) newEntry() *Entry { + entry, ok := logger.entryPool.Get().(*Entry) + if ok { + return entry + } + return NewEntry(logger) +} + +func (logger *Logger) releaseEntry(entry *Entry) { + entry.Data = map[string]interface{}{} + logger.entryPool.Put(entry) +} + +// WithField allocates a new entry and adds a field to it. +// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to +// this new returned entry. +// If you want multiple fields, use `WithFields`. +func (logger *Logger) WithField(key string, value interface{}) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithField(key, value) +} + +// Adds a struct of fields to the log entry. All it does is call `WithField` for +// each `Field`. +func (logger *Logger) WithFields(fields Fields) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithFields(fields) +} + +// Add an error as single field to the log entry. All it does is call +// `WithError` for the given `error`. +func (logger *Logger) WithError(err error) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithError(err) +} + +// Add a context to the log entry. +func (logger *Logger) WithContext(ctx context.Context) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithContext(ctx) +} + +// Overrides the time of the log entry. +func (logger *Logger) WithTime(t time.Time) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithTime(t) +} + +func (logger *Logger) Logf(level Level, format string, args ...interface{}) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Logf(level, format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Tracef(format string, args ...interface{}) { + logger.Logf(TraceLevel, format, args...) +} + +func (logger *Logger) Debugf(format string, args ...interface{}) { + logger.Logf(DebugLevel, format, args...) +} + +func (logger *Logger) Infof(format string, args ...interface{}) { + logger.Logf(InfoLevel, format, args...) +} + +func (logger *Logger) Printf(format string, args ...interface{}) { + entry := logger.newEntry() + entry.Printf(format, args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warnf(format string, args ...interface{}) { + logger.Logf(WarnLevel, format, args...) +} + +func (logger *Logger) Warningf(format string, args ...interface{}) { + logger.Warnf(format, args...) +} + +func (logger *Logger) Errorf(format string, args ...interface{}) { + logger.Logf(ErrorLevel, format, args...) +} + +func (logger *Logger) Fatalf(format string, args ...interface{}) { + logger.Logf(FatalLevel, format, args...) + logger.Exit(1) +} + +func (logger *Logger) Panicf(format string, args ...interface{}) { + logger.Logf(PanicLevel, format, args...) +} + +func (logger *Logger) Log(level Level, args ...interface{}) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Log(level, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) LogFn(level Level, fn LogFunction) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Log(level, fn()...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Trace(args ...interface{}) { + logger.Log(TraceLevel, args...) +} + +func (logger *Logger) Debug(args ...interface{}) { + logger.Log(DebugLevel, args...) +} + +func (logger *Logger) Info(args ...interface{}) { + logger.Log(InfoLevel, args...) +} + +func (logger *Logger) Print(args ...interface{}) { + entry := logger.newEntry() + entry.Print(args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warn(args ...interface{}) { + logger.Log(WarnLevel, args...) +} + +func (logger *Logger) Warning(args ...interface{}) { + logger.Warn(args...) +} + +func (logger *Logger) Error(args ...interface{}) { + logger.Log(ErrorLevel, args...) +} + +func (logger *Logger) Fatal(args ...interface{}) { + logger.Log(FatalLevel, args...) + logger.Exit(1) +} + +func (logger *Logger) Panic(args ...interface{}) { + logger.Log(PanicLevel, args...) +} + +func (logger *Logger) TraceFn(fn LogFunction) { + logger.LogFn(TraceLevel, fn) +} + +func (logger *Logger) DebugFn(fn LogFunction) { + logger.LogFn(DebugLevel, fn) +} + +func (logger *Logger) InfoFn(fn LogFunction) { + logger.LogFn(InfoLevel, fn) +} + +func (logger *Logger) PrintFn(fn LogFunction) { + entry := logger.newEntry() + entry.Print(fn()...) + logger.releaseEntry(entry) +} + +func (logger *Logger) WarnFn(fn LogFunction) { + logger.LogFn(WarnLevel, fn) +} + +func (logger *Logger) WarningFn(fn LogFunction) { + logger.WarnFn(fn) +} + +func (logger *Logger) ErrorFn(fn LogFunction) { + logger.LogFn(ErrorLevel, fn) +} + +func (logger *Logger) FatalFn(fn LogFunction) { + logger.LogFn(FatalLevel, fn) + logger.Exit(1) +} + +func (logger *Logger) PanicFn(fn LogFunction) { + logger.LogFn(PanicLevel, fn) +} + +func (logger *Logger) Logln(level Level, args ...interface{}) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Logln(level, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Traceln(args ...interface{}) { + logger.Logln(TraceLevel, args...) +} + +func (logger *Logger) Debugln(args ...interface{}) { + logger.Logln(DebugLevel, args...) +} + +func (logger *Logger) Infoln(args ...interface{}) { + logger.Logln(InfoLevel, args...) +} + +func (logger *Logger) Println(args ...interface{}) { + entry := logger.newEntry() + entry.Println(args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warnln(args ...interface{}) { + logger.Logln(WarnLevel, args...) +} + +func (logger *Logger) Warningln(args ...interface{}) { + logger.Warnln(args...) +} + +func (logger *Logger) Errorln(args ...interface{}) { + logger.Logln(ErrorLevel, args...) +} + +func (logger *Logger) Fatalln(args ...interface{}) { + logger.Logln(FatalLevel, args...) + logger.Exit(1) +} + +func (logger *Logger) Panicln(args ...interface{}) { + logger.Logln(PanicLevel, args...) +} + +func (logger *Logger) Exit(code int) { + runHandlers() + if logger.ExitFunc == nil { + logger.ExitFunc = os.Exit + } + logger.ExitFunc(code) +} + +//When file is opened with appending mode, it's safe to +//write concurrently to a file (within 4k message on Linux). +//In these cases user can choose to disable the lock. +func (logger *Logger) SetNoLock() { + logger.mu.Disable() +} + +func (logger *Logger) level() Level { + return Level(atomic.LoadUint32((*uint32)(&logger.Level))) +} + +// SetLevel sets the logger level. +func (logger *Logger) SetLevel(level Level) { + atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) +} + +// GetLevel returns the logger level. +func (logger *Logger) GetLevel() Level { + return logger.level() +} + +// AddHook adds a hook to the logger hooks. +func (logger *Logger) AddHook(hook Hook) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Hooks.Add(hook) +} + +// IsLevelEnabled checks if the log level of the logger is greater than the level param +func (logger *Logger) IsLevelEnabled(level Level) bool { + return logger.level() >= level +} + +// SetFormatter sets the logger formatter. +func (logger *Logger) SetFormatter(formatter Formatter) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Formatter = formatter +} + +// SetOutput sets the logger output. +func (logger *Logger) SetOutput(output io.Writer) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Out = output +} + +func (logger *Logger) SetReportCaller(reportCaller bool) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.ReportCaller = reportCaller +} + +// ReplaceHooks replaces the logger hooks and returns the old ones +func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { + logger.mu.Lock() + oldHooks := logger.Hooks + logger.Hooks = hooks + logger.mu.Unlock() + return oldHooks +} diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go new file mode 100644 index 0000000..2f16224 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/logrus.go @@ -0,0 +1,186 @@ +package logrus + +import ( + "fmt" + "log" + "strings" +) + +// Fields type, used to pass to `WithFields`. +type Fields map[string]interface{} + +// Level type +type Level uint32 + +// Convert the Level to a string. E.g. PanicLevel becomes "panic". +func (level Level) String() string { + if b, err := level.MarshalText(); err == nil { + return string(b) + } else { + return "unknown" + } +} + +// ParseLevel takes a string level and returns the Logrus log level constant. +func ParseLevel(lvl string) (Level, error) { + switch strings.ToLower(lvl) { + case "panic": + return PanicLevel, nil + case "fatal": + return FatalLevel, nil + case "error": + return ErrorLevel, nil + case "warn", "warning": + return WarnLevel, nil + case "info": + return InfoLevel, nil + case "debug": + return DebugLevel, nil + case "trace": + return TraceLevel, nil + } + + var l Level + return l, fmt.Errorf("not a valid logrus Level: %q", lvl) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (level *Level) UnmarshalText(text []byte) error { + l, err := ParseLevel(string(text)) + if err != nil { + return err + } + + *level = l + + return nil +} + +func (level Level) MarshalText() ([]byte, error) { + switch level { + case TraceLevel: + return []byte("trace"), nil + case DebugLevel: + return []byte("debug"), nil + case InfoLevel: + return []byte("info"), nil + case WarnLevel: + return []byte("warning"), nil + case ErrorLevel: + return []byte("error"), nil + case FatalLevel: + return []byte("fatal"), nil + case PanicLevel: + return []byte("panic"), nil + } + + return nil, fmt.Errorf("not a valid logrus level %d", level) +} + +// A constant exposing all logging levels +var AllLevels = []Level{ + PanicLevel, + FatalLevel, + ErrorLevel, + WarnLevel, + InfoLevel, + DebugLevel, + TraceLevel, +} + +// These are the different logging levels. You can set the logging level to log +// on your instance of logger, obtained with `logrus.New()`. +const ( + // PanicLevel level, highest level of severity. Logs and then calls panic with the + // message passed to Debug, Info, ... + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the + // logging level is set to Panic. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the + // application. + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel + // TraceLevel level. Designates finer-grained informational events than the Debug. + TraceLevel +) + +// Won't compile if StdLogger can't be realized by a log.Logger +var ( + _ StdLogger = &log.Logger{} + _ StdLogger = &Entry{} + _ StdLogger = &Logger{} +) + +// StdLogger is what your logrus-enabled library should take, that way +// it'll accept a stdlib logger and a logrus logger. There's no standard +// interface, this is the closest we get, unfortunately. +type StdLogger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} + +// The FieldLogger interface generalizes the Entry and Logger types +type FieldLogger interface { + WithField(key string, value interface{}) *Entry + WithFields(fields Fields) *Entry + WithError(err error) *Entry + + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Printf(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Warningf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) + Panicf(format string, args ...interface{}) + + Debug(args ...interface{}) + Info(args ...interface{}) + Print(args ...interface{}) + Warn(args ...interface{}) + Warning(args ...interface{}) + Error(args ...interface{}) + Fatal(args ...interface{}) + Panic(args ...interface{}) + + Debugln(args ...interface{}) + Infoln(args ...interface{}) + Println(args ...interface{}) + Warnln(args ...interface{}) + Warningln(args ...interface{}) + Errorln(args ...interface{}) + Fatalln(args ...interface{}) + Panicln(args ...interface{}) + + // IsDebugEnabled() bool + // IsInfoEnabled() bool + // IsWarnEnabled() bool + // IsErrorEnabled() bool + // IsFatalEnabled() bool + // IsPanicEnabled() bool +} + +// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is +// here for consistancy. Do not use. Use Logger or Entry instead. +type Ext1FieldLogger interface { + FieldLogger + Tracef(format string, args ...interface{}) + Trace(args ...interface{}) + Traceln(args ...interface{}) +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go new file mode 100644 index 0000000..2403de9 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go @@ -0,0 +1,11 @@ +// +build appengine + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return true +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go new file mode 100644 index 0000000..4997899 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go @@ -0,0 +1,13 @@ +// +build darwin dragonfly freebsd netbsd openbsd +// +build !js + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA + +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_js.go b/vendor/github.com/sirupsen/logrus/terminal_check_js.go new file mode 100644 index 0000000..ebdae3e --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_js.go @@ -0,0 +1,7 @@ +// +build js + +package logrus + +func isTerminal(fd int) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go new file mode 100644 index 0000000..97af92c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go @@ -0,0 +1,11 @@ +// +build js nacl plan9 + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go new file mode 100644 index 0000000..3293fb3 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go @@ -0,0 +1,17 @@ +// +build !appengine,!js,!windows,!nacl,!plan9 + +package logrus + +import ( + "io" + "os" +) + +func checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + return isTerminal(int(v.Fd())) + default: + return false + } +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go new file mode 100644 index 0000000..f6710b3 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go @@ -0,0 +1,11 @@ +package logrus + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go new file mode 100644 index 0000000..04748b8 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go @@ -0,0 +1,13 @@ +// +build linux aix zos +// +build !js + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS + +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_windows.go b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go new file mode 100644 index 0000000..2879eb5 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go @@ -0,0 +1,27 @@ +// +build !appengine,!js,windows + +package logrus + +import ( + "io" + "os" + + "golang.org/x/sys/windows" +) + +func checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + handle := windows.Handle(v.Fd()) + var mode uint32 + if err := windows.GetConsoleMode(handle, &mode); err != nil { + return false + } + mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING + if err := windows.SetConsoleMode(handle, mode); err != nil { + return false + } + return true + } + return false +} diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go new file mode 100644 index 0000000..be2c6ef --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -0,0 +1,339 @@ +package logrus + +import ( + "bytes" + "fmt" + "os" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "time" + "unicode/utf8" +) + +const ( + red = 31 + yellow = 33 + blue = 36 + gray = 37 +) + +var baseTimestamp time.Time + +func init() { + baseTimestamp = time.Now() +} + +// TextFormatter formats logs into text +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + + // Force disabling colors. + DisableColors bool + + // Force quoting of all values + ForceQuote bool + + // DisableQuote disables quoting for all values. + // DisableQuote will have a lower priority than ForceQuote. + // If both of them are set to true, quote will be forced on all values. + DisableQuote bool + + // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ + EnvironmentOverrideColors bool + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. + DisableTimestamp bool + + // Enable logging the full timestamp when a TTY is attached instead of just + // the time passed since beginning of execution. + FullTimestamp bool + + // TimestampFormat to use for display when a full timestamp is printed. + // The format to use is the same than for time.Format or time.Parse from the standard + // library. + // The standard Library already provides a set of predefined format. + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool + + // The keys sorting function, when uninitialized it uses sort.Strings. + SortingFunc func([]string) + + // Disables the truncation of the level text to 4 characters. + DisableLevelTruncation bool + + // PadLevelText Adds padding the level text so that all the levels output at the same length + // PadLevelText is a superset of the DisableLevelTruncation option + PadLevelText bool + + // QuoteEmptyFields will wrap empty fields in quotes if true + QuoteEmptyFields bool + + // Whether the logger's out is to a terminal + isTerminal bool + + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &TextFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message"}} + FieldMap FieldMap + + // CallerPrettyfier can be set by the user to modify the content + // of the function and file keys in the data when ReportCaller is + // activated. If any of the returned value is the empty string the + // corresponding key will be removed from fields. + CallerPrettyfier func(*runtime.Frame) (function string, file string) + + terminalInitOnce sync.Once + + // The max length of the level text, generated dynamically on init + levelTextMaxLength int +} + +func (f *TextFormatter) init(entry *Entry) { + if entry.Logger != nil { + f.isTerminal = checkIfTerminal(entry.Logger.Out) + } + // Get the max length of the level text + for _, level := range AllLevels { + levelTextLength := utf8.RuneCount([]byte(level.String())) + if levelTextLength > f.levelTextMaxLength { + f.levelTextMaxLength = levelTextLength + } + } +} + +func (f *TextFormatter) isColored() bool { + isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows")) + + if f.EnvironmentOverrideColors { + switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); { + case ok && force != "0": + isColored = true + case ok && force == "0", os.Getenv("CLICOLOR") == "0": + isColored = false + } + } + + return isColored && !f.DisableColors +} + +// Format renders a single log entry +func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields) + for k, v := range entry.Data { + data[k] = v + } + prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) + keys := make([]string, 0, len(data)) + for k := range data { + keys = append(keys, k) + } + + var funcVal, fileVal string + + fixedKeys := make([]string, 0, 4+len(data)) + if !f.DisableTimestamp { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) + } + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) + if entry.Message != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) + } + if entry.err != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) + } + if entry.HasCaller() { + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } else { + funcVal = entry.Caller.Function + fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + } + + if funcVal != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) + } + if fileVal != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile)) + } + } + + if !f.DisableSorting { + if f.SortingFunc == nil { + sort.Strings(keys) + fixedKeys = append(fixedKeys, keys...) + } else { + if !f.isColored() { + fixedKeys = append(fixedKeys, keys...) + f.SortingFunc(fixedKeys) + } else { + f.SortingFunc(keys) + } + } + } else { + fixedKeys = append(fixedKeys, keys...) + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + f.terminalInitOnce.Do(func() { f.init(entry) }) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + if f.isColored() { + f.printColored(b, entry, keys, data, timestampFormat) + } else { + + for _, key := range fixedKeys { + var value interface{} + switch { + case key == f.FieldMap.resolve(FieldKeyTime): + value = entry.Time.Format(timestampFormat) + case key == f.FieldMap.resolve(FieldKeyLevel): + value = entry.Level.String() + case key == f.FieldMap.resolve(FieldKeyMsg): + value = entry.Message + case key == f.FieldMap.resolve(FieldKeyLogrusError): + value = entry.err + case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): + value = funcVal + case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): + value = fileVal + default: + value = data[key] + } + f.appendKeyValue(b, key, value) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) { + var levelColor int + switch entry.Level { + case DebugLevel, TraceLevel: + levelColor = gray + case WarnLevel: + levelColor = yellow + case ErrorLevel, FatalLevel, PanicLevel: + levelColor = red + case InfoLevel: + levelColor = blue + default: + levelColor = blue + } + + levelText := strings.ToUpper(entry.Level.String()) + if !f.DisableLevelTruncation && !f.PadLevelText { + levelText = levelText[0:4] + } + if f.PadLevelText { + // Generates the format string used in the next line, for example "%-6s" or "%-7s". + // Based on the max level text length. + formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s" + // Formats the level text by appending spaces up to the max length, for example: + // - "INFO " + // - "WARNING" + levelText = fmt.Sprintf(formatString, levelText) + } + + // Remove a single newline if it already exists in the message to keep + // the behavior of logrus text_formatter the same as the stdlib log package + entry.Message = strings.TrimSuffix(entry.Message, "\n") + + caller := "" + if entry.HasCaller() { + funcVal := fmt.Sprintf("%s()", entry.Caller.Function) + fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } + + if fileVal == "" { + caller = funcVal + } else if funcVal == "" { + caller = fileVal + } else { + caller = fileVal + " " + funcVal + } + } + + switch { + case f.DisableTimestamp: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) + case !f.FullTimestamp: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) + default: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message) + } + for _, k := range keys { + v := data[k] + fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) + f.appendValue(b, v) + } +} + +func (f *TextFormatter) needsQuoting(text string) bool { + if f.ForceQuote { + return true + } + if f.QuoteEmptyFields && len(text) == 0 { + return true + } + if f.DisableQuote { + return false + } + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { + return true + } + } + return false +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { + if b.Len() > 0 { + b.WriteByte(' ') + } + b.WriteString(key) + b.WriteByte('=') + f.appendValue(b, value) +} + +func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { + stringVal, ok := value.(string) + if !ok { + stringVal = fmt.Sprint(value) + } + + if !f.needsQuoting(stringVal) { + b.WriteString(stringVal) + } else { + b.WriteString(fmt.Sprintf("%q", stringVal)) + } +} diff --git a/vendor/github.com/sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go new file mode 100644 index 0000000..72e8e3a --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/writer.go @@ -0,0 +1,70 @@ +package logrus + +import ( + "bufio" + "io" + "runtime" +) + +// Writer at INFO level. See WriterLevel for details. +func (logger *Logger) Writer() *io.PipeWriter { + return logger.WriterLevel(InfoLevel) +} + +// WriterLevel returns an io.Writer that can be used to write arbitrary text to +// the logger at the given log level. Each line written to the writer will be +// printed in the usual way using formatters and hooks. The writer is part of an +// io.Pipe and it is the callers responsibility to close the writer when done. +// This can be used to override the standard library logger easily. +func (logger *Logger) WriterLevel(level Level) *io.PipeWriter { + return NewEntry(logger).WriterLevel(level) +} + +func (entry *Entry) Writer() *io.PipeWriter { + return entry.WriterLevel(InfoLevel) +} + +func (entry *Entry) WriterLevel(level Level) *io.PipeWriter { + reader, writer := io.Pipe() + + var printFunc func(args ...interface{}) + + switch level { + case TraceLevel: + printFunc = entry.Trace + case DebugLevel: + printFunc = entry.Debug + case InfoLevel: + printFunc = entry.Info + case WarnLevel: + printFunc = entry.Warn + case ErrorLevel: + printFunc = entry.Error + case FatalLevel: + printFunc = entry.Fatal + case PanicLevel: + printFunc = entry.Panic + default: + printFunc = entry.Print + } + + go entry.writerScanner(reader, printFunc) + runtime.SetFinalizer(writer, writerFinalizer) + + return writer +} + +func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + printFunc(scanner.Text()) + } + if err := scanner.Err(); err != nil { + entry.Errorf("Error while reading from Writer: %s", err) + } + reader.Close() +} + +func writerFinalizer(writer *io.PipeWriter) { + writer.Close() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6c6d07e..e8c627e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -88,6 +88,9 @@ github.com/patrickmn/go-cache # github.com/rivo/uniseg v0.2.0 ## explicit; go 1.12 github.com/rivo/uniseg +# github.com/sirupsen/logrus v1.8.1 +## explicit; go 1.13 +github.com/sirupsen/logrus # github.com/spf13/cobra v1.2.1 ## explicit; go 1.14 github.com/spf13/cobra