diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2018-08-27 20:35:55 +1000 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2018-08-27 20:35:55 +1000 |
commit | 23a9f41d9db3e449ec79e4fde46c54b3c5611368 (patch) | |
tree | 969b5f9348c5f8e1e4e824a050695f6a604670f0 | |
parent | 1901901d243f4a4f9c90ce10b61aecc36b83374b (diff) | |
parent | a1c6adab59e742adc6d4207fecd41c7fd18ceeca (diff) |
Merge branch 'feature/anonymous-reporting'
-rw-r--r-- | Gopkg.lock | 25 | ||||
-rw-r--r-- | docs/Config.md | 1 | ||||
-rw-r--r-- | pkg/app/app.go | 33 | ||||
-rw-r--r-- | pkg/commands/git.go | 4 | ||||
-rw-r--r-- | pkg/commands/git_test.go | 4 | ||||
-rw-r--r-- | pkg/commands/os.go | 4 | ||||
-rw-r--r-- | pkg/config/app_config.go | 1 | ||||
-rw-r--r-- | pkg/git/branch_list_builder.go | 4 | ||||
-rw-r--r-- | pkg/gui/gui.go | 19 | ||||
-rw-r--r-- | pkg/i18n/english.go | 5 | ||||
-rw-r--r-- | pkg/i18n/i18n.go | 8 | ||||
-rw-r--r-- | pkg/i18n/i18n_test.go | 10 | ||||
-rw-r--r-- | vendor/github.com/heroku/rollrus/LICENSE | 22 | ||||
-rw-r--r-- | vendor/github.com/heroku/rollrus/rollrus.go | 287 | ||||
-rw-r--r-- | vendor/github.com/pkg/errors/LICENSE | 23 | ||||
-rw-r--r-- | vendor/github.com/pkg/errors/errors.go | 269 | ||||
-rw-r--r-- | vendor/github.com/pkg/errors/stack.go | 178 | ||||
-rw-r--r-- | vendor/github.com/stvp/roll/LICENSE | 22 | ||||
-rw-r--r-- | vendor/github.com/stvp/roll/client.go | 240 | ||||
-rw-r--r-- | vendor/github.com/stvp/roll/stack.go | 98 |
20 files changed, 1236 insertions, 21 deletions
diff --git a/Gopkg.lock b/Gopkg.lock index 3336db68c..421c87726 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -165,6 +165,14 @@ [[projects]] branch = "master" + digest = "1:d457d39e88f678ed14ac29517c3d74927a48dbc6a9f073fa241cf364a68cbe5c" + name = "github.com/heroku/rollrus" + packages = ["."] + pruneopts = "NUT" + revision = "fc0cef2ff331aebb24cd4e9ded7e20650f3d7006" + +[[projects]] + branch = "master" digest = "1:62fe3a7ea2050ecbd753a71889026f83d73329337ada66325cbafd5dea5f713d" name = "github.com/jbenet/go-context" packages = ["io"] @@ -311,6 +319,14 @@ version = "v1.2.0" [[projects]] + digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "NUT" + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" name = "github.com/pmezard/go-difflib" packages = ["difflib"] @@ -414,6 +430,14 @@ version = "v1.2.2" [[projects]] + branch = "master" + digest = "1:e42372d3f4921ec35df07f9b23239631e9d28580f7c1edcca212bc6daddc68fe" + name = "github.com/stvp/roll" + packages = ["."] + pruneopts = "NUT" + revision = "3627a5cbeaeaa68023abd02bb8687925265f2f63" + +[[projects]] digest = "1:cd5ffc5bda4e0296ab3e4de90dbb415259c78e45e7fab13694b14cde8ab74541" name = "github.com/tcnksm/go-gitconfig" packages = ["."] @@ -591,6 +615,7 @@ "github.com/fatih/color", "github.com/golang-collections/collections/stack", "github.com/jesseduffield/go-getter", + "github.com/heroku/rollrus", "github.com/jesseduffield/gocui", "github.com/kardianos/osext", "github.com/mgutz/str", diff --git a/docs/Config.md b/docs/Config.md index 77f444178..b2711b603 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -21,6 +21,7 @@ update: method: prompt # can be: prompt | background | never days: 14 # how often an update is checked for + reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined' ``` ## Color Attributes: diff --git a/pkg/app/app.go b/pkg/app/app.go index f8cc01b6c..0b163f553 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" + "github.com/heroku/rollrus" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" @@ -18,7 +19,7 @@ type App struct { closers []io.Closer Config config.AppConfigurer - Log *logrus.Logger + Log *logrus.Entry OSCommand *commands.OSCommand GitCommand *commands.GitCommand Gui *gui.Gui @@ -26,12 +27,19 @@ type App struct { Updater *updates.Updater // may only need this on the Gui } -func newLogger(config config.AppConfigurer) *logrus.Logger { +func newProductionLogger(config config.AppConfigurer) *logrus.Logger { log := logrus.New() - if !config.GetDebug() { - log.Out = ioutil.Discard - return log + log.Out = ioutil.Discard + if config.GetUserConfig().GetString("reporting") == "on" { + // this isn't really a secret token: it only has permission to push new rollbar items + hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", "production") + log.Hooks.Add(hook) } + return log +} + +func newDevelopmentLogger() *logrus.Logger { + log := logrus.New() file, err := os.OpenFile("development.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function) @@ -40,6 +48,21 @@ func newLogger(config config.AppConfigurer) *logrus.Logger { return log } +func newLogger(config config.AppConfigurer) *logrus.Entry { + var log *logrus.Logger + if config.GetDebug() { + log = newDevelopmentLogger() + } else { + log = newProductionLogger(config) + } + return log.WithFields(logrus.Fields{ + "debug": config.GetDebug(), + "version": config.GetVersion(), + "commit": config.GetCommit(), + "buildDate": config.GetBuildDate(), + }) +} + // NewApp retruns a new applications func NewApp(config config.AppConfigurer) (*App, error) { app := &App{ diff --git a/pkg/commands/git.go b/pkg/commands/git.go index e5ee5dbfa..09fe610c2 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -16,14 +16,14 @@ import ( // GitCommand is our main git interface type GitCommand struct { - Log *logrus.Logger + Log *logrus.Entry OSCommand *OSCommand Worktree *gogit.Worktree Repo *gogit.Repository } // NewGitCommand it runs git commands -func NewGitCommand(log *logrus.Logger, osCommand *OSCommand) (*GitCommand, error) { +func NewGitCommand(log *logrus.Entry, osCommand *OSCommand) (*GitCommand, error) { gitCommand := &GitCommand{ Log: log, OSCommand: osCommand, diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 372009641..9c6a17b2c 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -9,10 +9,10 @@ import ( "github.com/sirupsen/logrus" ) -func newDummyLog() *logrus.Logger { +func newDummyLog() *logrus.Entry { log := logrus.New() log.Out = ioutil.Discard - return log + return log.WithField("test", "test") } func newDummyGitCommand() *GitCommand { diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 8b9ecf7ec..10f78c7c3 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -24,7 +24,7 @@ type Platform struct { // OSCommand holds all the os commands type OSCommand struct { - Log *logrus.Logger + Log *logrus.Entry Platform *Platform command func(string, ...string) *exec.Cmd getGlobalGitConfig func(string) (string, error) @@ -32,7 +32,7 @@ type OSCommand struct { } // NewOSCommand os command runner -func NewOSCommand(log *logrus.Logger) *OSCommand { +func NewOSCommand(log *logrus.Entry) *OSCommand { return &OSCommand{ Log: log, Platform: getPlatform(), diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index fd3acb212..c85b8e5eb 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -225,6 +225,7 @@ os: update: method: prompt # can be: prompt | background | never days: 14 # how often a update is checked for +reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined' `) } diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go index 37421d5b6..651b684f5 100644 --- a/pkg/git/branch_list_builder.go +++ b/pkg/git/branch_list_builder.go @@ -22,12 +22,12 @@ import ( // BranchListBuilder returns a list of Branch objects for the current repo type BranchListBuilder struct { - Log *logrus.Logger + Log *logrus.Entry GitCommand *commands.GitCommand } // NewBranchListBuilder builds a new branch list builder -func NewBranchListBuilder(log *logrus.Logger, gitCommand *commands.GitCommand) (*BranchListBuilder, error) { +func NewBranchListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand) (*BranchListBuilder, error) { return &BranchListBuilder{ Log: log, GitCommand: gitCommand, diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index ccc26841b..9544ef590 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -58,7 +58,7 @@ type Teml i18n.Teml // Gui wraps the gocui Gui object which handles rendering and events type Gui struct { g *gocui.Gui - Log *logrus.Logger + Log *logrus.Entry GitCommand *commands.GitCommand OSCommand *commands.OSCommand SubProcess *exec.Cmd @@ -86,7 +86,8 @@ type guiState struct { } // NewGui builds a new gui handler -func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { +func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { + initialState := guiState{ Files: make([]commands.File, 0), PreviousView: "files", @@ -298,6 +299,12 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err := gui.switchFocus(g, nil, filesView); err != nil { return err } + + if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" { + if err := gui.promptAnonymousReporting(); err != nil { + return err + } + } } gui.resizePopupPanels(g) @@ -305,6 +312,14 @@ func (gui *Gui) layout(g *gocui.Gui) error { return nil } +func (gui *Gui) promptAnonymousReporting() error { + return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error { + return gui.Config.InsertToUserConfig("reporting", "on") + }, func(g *gocui.Gui, v *gocui.View) error { + return gui.Config.InsertToUserConfig("reporting", "off") + }) +} + func (gui *Gui) fetch(g *gocui.Gui) error { gui.GitCommand.Fetch() gui.refreshStatus(g) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index c7214053e..578e8f295 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -327,6 +327,11 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "CouldNotFindBinaryErr", Other: "Could not find any binary at {{.url}}", + ID: "AnonymousReportingTitle", + Other: "Help make lazygit better", + }, &i18n.Message{ + ID: "AnonymousReportingPrompt", + Other: "Would you like to enable anonymous reporting data to help improve lazygit? (enter/esc)", }, ) } diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 37de57cc6..1628df707 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -14,11 +14,11 @@ type Teml map[string]interface{} type Localizer struct { i18nLocalizer *i18n.Localizer language string - Log *logrus.Logger + Log *logrus.Entry } // NewLocalizer creates a new Localizer -func NewLocalizer(log *logrus.Logger) *Localizer { +func NewLocalizer(log *logrus.Entry) *Localizer { userLang := detectLanguage(jibber_jabber.DetectLanguage) log.Info("language: " + userLang) @@ -60,7 +60,7 @@ func (l *Localizer) GetLanguage() string { } // add translation file(s) -func addBundles(log *logrus.Logger, i18nBundle *i18n.Bundle) { +func addBundles(log *logrus.Entry, i18nBundle *i18n.Bundle) { fs := []func(*i18n.Bundle) error{ addPolish, addDutch, @@ -85,7 +85,7 @@ func detectLanguage(langDetector func() (string, error)) string { } // setupLocalizer creates a new localizer using given userLang -func setupLocalizer(log *logrus.Logger, userLang string) *Localizer { +func setupLocalizer(log *logrus.Entry, userLang string) *Localizer { // create a i18n bundle that can be used to add translations and other things i18nBundle := &i18n.Bundle{DefaultLanguage: language.English} diff --git a/pkg/i18n/i18n_test.go b/pkg/i18n/i18n_test.go index 5eeddb907..e26b7d1dc 100644 --- a/pkg/i18n/i18n_test.go +++ b/pkg/i18n/i18n_test.go @@ -2,6 +2,7 @@ package i18n import ( "fmt" + "io/ioutil" "testing" "github.com/nicksnyder/go-i18n/v2/i18n" @@ -10,8 +11,13 @@ import ( "github.com/stretchr/testify/assert" ) +func getDummyLog() *logrus.Entry { + log := logrus.New() + log.Out = ioutil.Discard + return log.WithField("test", "test") +} func TestNewLocalizer(t *testing.T) { - assert.NotNil(t, NewLocalizer(logrus.New())) + assert.NotNil(t, NewLocalizer(getDummyLog())) } func TestDetectLanguage(t *testing.T) { @@ -76,6 +82,6 @@ func TestLocalizer(t *testing.T) { } for _, s := range scenarios { - s.test(setupLocalizer(logrus.New(), s.userLang)) + s.test(setupLocalizer(getDummyLog(), s.userLang)) } } diff --git a/vendor/github.com/heroku/rollrus/LICENSE b/vendor/github.com/heroku/rollrus/LICENSE new file mode 100644 index 000000000..8d4a5174d --- /dev/null +++ b/vendor/github.com/heroku/rollrus/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © Heroku 2014 - 2015 + +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/heroku/rollrus/rollrus.go b/vendor/github.com/heroku/rollrus/rollrus.go new file mode 100644 index 000000000..50dd225da --- /dev/null +++ b/vendor/github.com/heroku/rollrus/rollrus.go @@ -0,0 +1,287 @@ +// Package rollrus combines github.com/stvp/roll with github.com/sirupsen/logrus +// via logrus.Hook mechanism, so that whenever logrus' logger.Error/f(), +// logger.Fatal/f() or logger.Panic/f() are used the messages are +// intercepted and sent to rollbar. +// +// Using SetupLogging should suffice for basic use cases that use the logrus +// singleton logger. +// +// More custom uses are supported by creating a new Hook with NewHook and +// registering that hook with the logrus Logger of choice. +// +// The levels can be customized with the WithLevels OptionFunc. +// +// Specific errors can be ignored with the WithIgnoredErrors OptionFunc. This is +// useful for ignoring errors such as context.Canceled. +// +// See the Examples in the tests for more usage. +package rollrus + +import ( + "fmt" + "os" + "time" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/stvp/roll" +) + +var defaultTriggerLevels = []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, +} + +// Hook is a wrapper for the rollbar Client and is usable as a logrus.Hook. +type Hook struct { + roll.Client + triggers []logrus.Level + ignoredErrors map[error]struct{} + ignoreErrorFunc func(error) bool + ignoreFunc func(error, map[string]string) bool + + // only used for tests to verify whether or not a report happened. + reported bool +} + +// OptionFunc that can be passed to NewHook. +type OptionFunc func(*Hook) + +// wellKnownErrorFields are the names of the fields to be checked for values of +// type `error`, in priority order. +var wellKnownErrorFields = []string{ + logrus.ErrorKey, "err", +} + +// WithLevels is an OptionFunc that customizes the log.Levels the hook will +// report on. +func WithLevels(levels ...logrus.Level) OptionFunc { + return func(h *Hook) { + h.triggers = levels + } +} + +// WithMinLevel is an OptionFunc that customizes the log.Levels the hook will +// report on by selecting all levels more severe than the one provided. +func WithMinLevel(level logrus.Level) OptionFunc { + var levels []logrus.Level + for _, l := range logrus.AllLevels { + if l <= level { + levels = append(levels, l) + } + } + + return func(h *Hook) { + h.triggers = levels + } +} + +// WithIgnoredErrors is an OptionFunc that whitelists certain errors to prevent +// them from firing. +func WithIgnoredErrors(errors ...error) OptionFunc { + return func(h *Hook) { + for _, e := range errors { + h.ignoredErrors[e] = struct{}{} + } + } +} + +// WithIgnoreErrorFunc is an OptionFunc that receives the error that is about +// to be logged and returns true/false if it wants to fire a rollbar alert for. +func WithIgnoreErrorFunc(fn func(error) bool) OptionFunc { + return func(h *Hook) { + h.ignoreErrorFunc = fn + } +} + +// WithIgnoreFunc is an OptionFunc that receives the error and custom fields that are about +// to be logged and returns true/false if it wants to fire a rollbar alert for. +func WithIgnoreFunc(fn func(err error, fields map[string]string) bool) OptionFunc { + return func(h *Hook) { + h.ignoreFunc = fn + } +} + +// NewHook creates a hook that is intended for use with your own logrus.Logger +// instance. Uses the defualt report levels defined in wellKnownErrorFields. +func NewHook(token string, env string, opts ...OptionFunc) *Hook { + h := NewHookForLevels(token, env, defaultTriggerLevels) + + for _, o := range opts { + o(h) + } + + return h +} + +// NewHookForLevels provided by the caller. Otherwise works like NewHook. +func NewHookForLevels(token string, env string, levels []logrus.Level) *Hook { + return &Hook{ + Client: roll.New(token, env), + triggers: levels, + ignoredErrors: make(map[error]struct{}), + ignoreErrorFunc: func(error) bool { return false }, + ignoreFunc: func(error, map[string]string) bool { return false }, + } +} + +// SetupLogging for use on Heroku. If token is not an empty string a rollbar +// hook is added with the environment set to env. The log formatter is set to a +// TextFormatter with timestamps disabled. +func SetupLogging(token, env string) { + setupLogging(token, env, defaultTriggerLevels) +} + +// SetupLoggingForLevels works like SetupLogging, but allows you to +// set the levels on which to trigger this hook. +func SetupLoggingForLevels(token, env string, levels []logrus.Level) { + setupLogging(token, env, levels) +} + +func setupLogging(token, env string, levels []logrus.Level) { + logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true}) + + if token != "" { + logrus.AddHook(NewHookForLevels(token, env, levels)) + } +} + +// ReportPanic attempts to report the panic to rollbar using the provided +// client and then re-panic. If it can't report the panic it will print an +// error to stderr. +func (r *Hook) ReportPanic() { + if p := recover(); p != nil { + if _, err := r.Client.Critical(fmt.Errorf("panic: %q", p), nil); err != nil { + fmt.Fprintf(os.Stderr, "reporting_panic=false err=%q\n", err) + } + panic(p) + } +} + +// ReportPanic attempts to report the panic to rollbar if the token is set +func ReportPanic(token, env string) { + if token != "" { + h := &Hook{Client: roll.New(token, env)} + h.ReportPanic() + } +} + +// Levels returns the logrus log.Levels that this hook handles +func (r *Hook) Levels() []logrus.Level { + if r.triggers == nil { + return defaultTriggerLevels + } + return r.triggers +} + +// Fire the hook. This is called by Logrus for entries that match the levels +// returned by Levels(). +func (r *Hook) Fire(entry *logrus.Entry) error { + trace, cause := extractError(entry) + if _, ok := r.ignoredErrors[cause]; ok { + return nil + } + + if r.ignoreErrorFunc(cause) { + return nil + } + + m := convertFields(entry.Data) + if _, exists := m["time"]; !exists { + m["time"] = entry.Time.Format(time.RFC3339) + } + + if r.ignoreFunc(cause, m) { + return nil + } + + return r.report(entry, cause, m, trace) +} + +func (r *Hook) report(entry *logrus.Entry, cause error, m map[string]string, trace []uintptr) (err error) { + hasTrace := len(trace) > 0 + level := entry.Level + + r.reported = true + + switch { + case hasTrace && level == logrus.FatalLevel: + _, err = r.Client.CriticalStack(cause, trace, m) + case hasTrace && level == logrus.PanicLevel: + _, err = r.Client.CriticalStack(cause, trace, m) + case hasTrace && level == logrus.ErrorLevel: + _, err = r.Client.ErrorStack(cause, trace, m) + case hasTrace && level == logrus.WarnLevel: + _, err = r.Client.WarningStack(cause, trace, m) + case level == logrus.FatalLevel || level == logrus.PanicLevel: + _, err = r.Client.Critical(cause, m) + case level == logrus.ErrorLevel: + _, err = r.Client.Error(cause, m) + case level == logrus.WarnLevel: + _, err = r.Client.Warning(cause, m) + case level == logrus.InfoLevel: + _, err = r.Client.Info(entry.Message, m) + case level == logrus.DebugLevel: + _, err = r.Client.Debug(entry.Message, m) + } + return err +} + +// convertFields converts from log.Fields to map[string]string so that we can +// report extra fields to Rollbar +func convertFields(fields logrus.Fields) map[string]string { + m := make(map[string]string) + for k, v := range fields { + switch t := v.(type) { + case time.Time: + m[k] = t.Format(time.RFC3339) + default: + if s, ok := v.(fmt.Stringer); ok { + m[k] = s.String() + } else { + m[k] = fmt.Sprintf("%+v", t) + } + } + } + + return m +} + +// extractError attempts to extract an error from a well known field, err or error +func extractError(entry *logrus.Entry) ([]uintptr, error) { + var trace []uintptr + fields := entry.Data + + type stackTracer interface { + StackTrace() errors.StackTrace + } + + for _, f := range wellKnownErrorFields { + e, ok := fields[f] + if !ok { + continue + } + err, ok := e.(error) + if !ok { + continue + } + + cause := errors.Cause(err) + tracer, ok := err.(stackTracer) + if ok { + return copyStackTrace(tracer.StackTrace()), cause + } + return trace, cause + } + + // when no error found, default to the logged message. + return trace, fmt.Errorf(entry.Message) +} + +func copyStackTrace(trace errors.StackTrace) (out []uintptr) { + for _, frame := range trace { + out = append(out, uintptr(frame)) + } + return +} diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 000000000..835ba3e75 --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney <dave@cheney.net> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 000000000..842ee8045 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } |