summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-08-27 20:35:55 +1000
committerJesse Duffield <jessedduffield@gmail.com>2018-08-27 20:35:55 +1000
commit23a9f41d9db3e449ec79e4fde46c54b3c5611368 (patch)
tree969b5f9348c5f8e1e4e824a050695f6a604670f0
parent1901901d243f4a4f9c90ce10b61aecc36b83374b (diff)
parenta1c6adab59e742adc6d4207fecd41c7fd18ceeca (diff)
Merge branch 'feature/anonymous-reporting'
-rw-r--r--Gopkg.lock25
-rw-r--r--docs/Config.md1
-rw-r--r--pkg/app/app.go33
-rw-r--r--pkg/commands/git.go4
-rw-r--r--pkg/commands/git_test.go4
-rw-r--r--pkg/commands/os.go4
-rw-r--r--pkg/config/app_config.go1
-rw-r--r--pkg/git/branch_list_builder.go4
-rw-r--r--pkg/gui/gui.go19
-rw-r--r--pkg/i18n/english.go5
-rw-r--r--pkg/i18n/i18n.go8
-rw-r--r--pkg/i18n/i18n_test.go10
-rw-r--r--vendor/github.com/heroku/rollrus/LICENSE22
-rw-r--r--vendor/github.com/heroku/rollrus/rollrus.go287
-rw-r--r--vendor/github.com/pkg/errors/LICENSE23
-rw-r--r--vendor/github.com/pkg/errors/errors.go269
-rw-r--r--vendor/github.com/pkg/errors/stack.go178
-rw-r--r--vendor/github.com/stvp/roll/LICENSE22
-rw-r--r--vendor/github.com/stvp/roll/client.go240
-rw-r--r--vendor/github.com/stvp/roll/stack.go98
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(),
+ }