refactor: make sentry report its own package

This commit is contained in:
James Houlahan
2020-04-16 16:27:57 +02:00
parent 4809d97cb1
commit 38f0425670
8 changed files with 56 additions and 46 deletions

View File

@ -19,6 +19,7 @@ package config
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -31,6 +32,7 @@ import (
"time" "time"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -57,11 +59,10 @@ var logCrashRgx = regexp.MustCompile("^v.*_crash_.*\\.log$") //nolint[gochecknog
// HandlePanic reports the crash to sentry or local file when sentry fails. // HandlePanic reports the crash to sentry or local file when sentry fails.
func HandlePanic(cfg *Config, output string) { func HandlePanic(cfg *Config, output string) {
if !cfg.IsDevMode() { if !cfg.IsDevMode() {
// TODO: Is it okay to just create a throwaway client like this? apiCfg := cfg.GetAPIConfig()
c := pmapi.NewClientManager(cfg.GetAPIConfig()).GetAnonymousClient() clientID := apiCfg.ClientID
defer c.Logout() appVersion := apiCfg.AppVersion
if err := sentry.ReportSentryCrash(clientID, appVersion, pmapi.CurrentUserAgent, errors.New(output)); err != nil {
if err := c.ReportSentryCrash(fmt.Errorf(output)); err != nil {
log.Error("Sentry crash report failed: ", err) log.Error("Sentry crash report failed: ", err)
} }
} }

View File

@ -494,7 +494,10 @@ func (c *client) DeleteAuth() (err error) {
return return
} }
// TODO: Need a method like IsConnected() to be able to detect whether a client is logged in or not. // IsConnected returns whether the client is authorized to access the API.
func (c *client) IsConnected() bool {
return c.uid != "" && c.accessToken != ""
}
// ClearData clears sensitive data from the client. // ClearData clears sensitive data from the client.
func (c *client) ClearData() { func (c *client) ClearData() {

View File

@ -336,12 +336,7 @@ func TestClient_Logout(t *testing.T) {
c.Logout() c.Logout()
r.Eventually(t, func() bool { r.Eventually(t, func() bool {
// TODO: Use a method like IsConnected() which returns whether the client was logged out or not. return c.IsConnected() == false && c.kr == nil && c.addresses == nil && c.user == nil
return c.accessToken == "" &&
c.uid == "" &&
c.kr == nil &&
c.addresses == nil &&
c.user == nil
}, 10*time.Second, 10*time.Millisecond) }, 10*time.Second, 10*time.Millisecond)
} }

View File

@ -98,6 +98,7 @@ type Client interface {
Auth2FA(twoFactorCode string, auth *Auth) (*Auth2FA, error) Auth2FA(twoFactorCode string, auth *Auth) (*Auth2FA, error)
Logout() Logout()
DeleteAuth() error DeleteAuth() error
IsConnected() bool
ClearData() ClearData()
CurrentUser() (*User, error) CurrentUser() (*User, error)
@ -131,7 +132,6 @@ type Client interface {
ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error
SendSimpleMetric(category, action, label string) error SendSimpleMetric(category, action, label string) error
ReportSentryCrash(reportErr error) (err error)
GetMailSettings() (MailSettings, error) GetMailSettings() (MailSettings, error)
GetContactEmailByEmail(string, int, int) ([]ContactEmail, error) GetContactEmailByEmail(string, int, int) ([]ContactEmail, error)

View File

@ -418,6 +418,20 @@ func (mr *MockClientMockRecorder) Import(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockClient)(nil).Import), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockClient)(nil).Import), arg0)
} }
// IsConnected mocks base method
func (m *MockClient) IsConnected() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsConnected")
ret0, _ := ret[0].(bool)
return ret0
}
// IsConnected indicates an expected call of IsConnected
func (mr *MockClientMockRecorder) IsConnected() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockClient)(nil).IsConnected))
}
// KeyRingForAddressID mocks base method // KeyRingForAddressID mocks base method
func (m *MockClient) KeyRingForAddressID(arg0 string) *crypto.KeyRing { func (m *MockClient) KeyRingForAddressID(arg0 string) *crypto.KeyRing {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -531,20 +545,6 @@ func (mr *MockClientMockRecorder) ReportBugWithEmailClient(arg0, arg1, arg2, arg
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBugWithEmailClient", reflect.TypeOf((*MockClient)(nil).ReportBugWithEmailClient), arg0, arg1, arg2, arg3, arg4, arg5, arg6) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBugWithEmailClient", reflect.TypeOf((*MockClient)(nil).ReportBugWithEmailClient), arg0, arg1, arg2, arg3, arg4, arg5, arg6)
} }
// ReportSentryCrash mocks base method
func (m *MockClient) ReportSentryCrash(arg0 error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReportSentryCrash", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ReportSentryCrash indicates an expected call of ReportSentryCrash
func (mr *MockClientMockRecorder) ReportSentryCrash(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportSentryCrash", reflect.TypeOf((*MockClient)(nil).ReportSentryCrash), arg0)
}
// SendMessage mocks base method // SendMessage mocks base method
func (m *MockClient) SendMessage(arg0 string, arg1 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) { func (m *MockClient) SendMessage(arg0 string, arg1 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package pmapi package sentry
import ( import (
"fmt" "fmt"
@ -26,19 +26,20 @@ import (
"strings" "strings"
"github.com/getsentry/raven-go" "github.com/getsentry/raven-go"
log "github.com/sirupsen/logrus"
) )
const fileParseError = "[file parse error]" const fileParseError = "[file parse error]"
var isGoroutine = regexp.MustCompile("^goroutine [[:digit:]]+.*") //nolint[gochecknoglobals] var isGoroutine = regexp.MustCompile("^goroutine [[:digit:]]+.*") //nolint[gochecknoglobals]
// SentryThreads implements standard sentry thread report. // Threads implements standard sentry thread report.
type SentryThreads struct { type Threads struct {
Values []Thread `json:"values"` Values []Thread `json:"values"`
} }
// Class specifier. // Class specifier.
func (s *SentryThreads) Class() string { return "threads" } func (s *Threads) Class() string { return "threads" }
// Thread wraps a single stacktrace. // Thread wraps a single stacktrace.
type Thread struct { type Thread struct {
@ -49,7 +50,7 @@ type Thread struct {
} }
// TraceAllRoutines traces all goroutines and saves them to the current object. // TraceAllRoutines traces all goroutines and saves them to the current object.
func (s *SentryThreads) TraceAllRoutines() { func (s *Threads) TraceAllRoutines() {
s.Values = []Thread{} s.Values = []Thread{}
goroutines := &strings.Builder{} goroutines := &strings.Builder{}
_ = pprof.Lookup("goroutine").WriteTo(goroutines, 2) _ = pprof.Lookup("goroutine").WriteTo(goroutines, 2)
@ -109,7 +110,7 @@ func (s *SentryThreads) TraceAllRoutines() {
s.Values = append(s.Values, thread) s.Values = append(s.Values, thread)
} }
func findPanicSender(s *SentryThreads, err error) string { func findPanicSender(s *Threads, err error) string {
out := "error nil" out := "error nil"
if err != nil { if err != nil {
out = err.Error() out = err.Error()
@ -146,28 +147,31 @@ func findPanicSender(s *SentryThreads, err error) string {
} }
// ReportSentryCrash reports a sentry crash with stacktrace from all goroutines. // ReportSentryCrash reports a sentry crash with stacktrace from all goroutines.
func (c *client) ReportSentryCrash(reportErr error) (err error) { func ReportSentryCrash(clientID, appVersion, userAgent string, reportErr error) (err error) {
if reportErr == nil { if reportErr == nil {
return return
} }
tags := map[string]string{ tags := map[string]string{
"OS": runtime.GOOS, "OS": runtime.GOOS,
"Client": c.cm.config.ClientID, "Client": clientID,
"Version": c.cm.config.AppVersion, "Version": appVersion,
"UserAgent": CurrentUserAgent, "UserAgent": userAgent,
"UserID": c.userID, "UserID": "",
} }
threads := &SentryThreads{} threads := &Threads{}
threads.TraceAllRoutines() threads.TraceAllRoutines()
errorWithFile := findPanicSender(threads, reportErr) errorWithFile := findPanicSender(threads, reportErr)
packet := raven.NewPacket(errorWithFile, threads) packet := raven.NewPacket(errorWithFile, threads)
eventID, ch := raven.Capture(packet, tags) eventID, ch := raven.Capture(packet, tags)
if err = <-ch; err == nil { if err = <-ch; err == nil {
c.log.Warn("Reported error with id: ", eventID) log.WithField("errorID", eventID).Warn("Reported sentry error")
} else { } else {
c.log.Errorf("Can not report `%s` due to `%s`", reportErr.Error(), err.Error()) log.WithField("error", reportErr).WithError(err).Error("Failed to report sentry error")
} }
return err return err
} }

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package pmapi package sentry
import ( import (
"errors" "errors"
@ -25,14 +25,17 @@ import (
) )
func TestSentryCrashReport(t *testing.T) { func TestSentryCrashReport(t *testing.T) {
cm := NewClientManager(testClientConfig) if err := ReportSentryCrash(
c := cm.GetClient("bridgetest") "clientID",
if err := c.ReportSentryCrash(errors.New("Testing crash report - api proxy; goroutines with threads, find origin")); err != nil { "appVersion",
"useragent",
errors.New("Testing crash report - api proxy; goroutines with threads, find origin"),
); err != nil {
t.Fatal("Expected no error while report, but have", err) t.Fatal("Expected no error while report, but have", err)
} }
} }
func (s *SentryThreads) TraceAllRoutinesTest() { func (s *Threads) TraceAllRoutinesTest() {
s.Values = []Thread{ s.Values = []Thread{
{ {
ID: 0, ID: 0,

View File

@ -154,6 +154,10 @@ func (api *FakePMAPI) Logout() {
api.ClearData() api.ClearData()
} }
func (api *FakePMAPI) IsConnected() bool {
return api.uid != "" && api.lastToken != ""
}
func (api *FakePMAPI) DeleteAuth() error { func (api *FakePMAPI) DeleteAuth() error {
if err := api.checkAndRecordCall(DELETE, "/auth", nil); err != nil { if err := api.checkAndRecordCall(DELETE, "/auth", nil); err != nil {
return err return err