mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 15:46:44 +00:00
refactor: make sentry report its own package
This commit is contained in:
@ -494,7 +494,10 @@ func (c *client) DeleteAuth() (err error) {
|
||||
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.
|
||||
func (c *client) ClearData() {
|
||||
|
||||
@ -336,12 +336,7 @@ func TestClient_Logout(t *testing.T) {
|
||||
c.Logout()
|
||||
|
||||
r.Eventually(t, func() bool {
|
||||
// TODO: Use a method like IsConnected() which returns whether the client was logged out or not.
|
||||
return c.accessToken == "" &&
|
||||
c.uid == "" &&
|
||||
c.kr == nil &&
|
||||
c.addresses == nil &&
|
||||
c.user == nil
|
||||
return c.IsConnected() == false && c.kr == nil && c.addresses == nil && c.user == nil
|
||||
}, 10*time.Second, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
|
||||
@ -98,6 +98,7 @@ type Client interface {
|
||||
Auth2FA(twoFactorCode string, auth *Auth) (*Auth2FA, error)
|
||||
Logout()
|
||||
DeleteAuth() error
|
||||
IsConnected() bool
|
||||
ClearData()
|
||||
|
||||
CurrentUser() (*User, error)
|
||||
@ -131,7 +132,6 @@ type Client interface {
|
||||
|
||||
ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error
|
||||
SendSimpleMetric(category, action, label string) error
|
||||
ReportSentryCrash(reportErr error) (err error)
|
||||
|
||||
GetMailSettings() (MailSettings, error)
|
||||
GetContactEmailByEmail(string, int, int) ([]ContactEmail, error)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (m *MockClient) KeyRingForAddressID(arg0 string) *crypto.KeyRing {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (m *MockClient) SendMessage(arg0 string, arg1 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@ -1,173 +0,0 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package pmapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
)
|
||||
|
||||
const fileParseError = "[file parse error]"
|
||||
|
||||
var isGoroutine = regexp.MustCompile("^goroutine [[:digit:]]+.*") //nolint[gochecknoglobals]
|
||||
|
||||
// SentryThreads implements standard sentry thread report.
|
||||
type SentryThreads struct {
|
||||
Values []Thread `json:"values"`
|
||||
}
|
||||
|
||||
// Class specifier.
|
||||
func (s *SentryThreads) Class() string { return "threads" }
|
||||
|
||||
// Thread wraps a single stacktrace.
|
||||
type Thread struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Crashed bool `json:"crashed"`
|
||||
Stacktrace *raven.Stacktrace `json:"stacktrace"`
|
||||
}
|
||||
|
||||
// TraceAllRoutines traces all goroutines and saves them to the current object.
|
||||
func (s *SentryThreads) TraceAllRoutines() {
|
||||
s.Values = []Thread{}
|
||||
goroutines := &strings.Builder{}
|
||||
_ = pprof.Lookup("goroutine").WriteTo(goroutines, 2)
|
||||
|
||||
thread := Thread{ID: -1}
|
||||
var frame *raven.StacktraceFrame
|
||||
for _, v := range strings.Split(goroutines.String(), "\n") {
|
||||
// Ignore empty lines.
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// New routine.
|
||||
if isGoroutine.MatchString(v) {
|
||||
if thread.ID >= 0 {
|
||||
s.Values = append(s.Values, thread)
|
||||
}
|
||||
thread = Thread{ID: thread.ID + 1, Name: v, Crashed: thread.ID == -1, Stacktrace: &raven.Stacktrace{Frames: []*raven.StacktraceFrame{}}}
|
||||
continue
|
||||
}
|
||||
|
||||
// New function.
|
||||
if frame == nil {
|
||||
frame = &raven.StacktraceFrame{Function: v}
|
||||
continue
|
||||
}
|
||||
|
||||
// Set filename and add frame.
|
||||
if frame.Filename == "" {
|
||||
fld := strings.Fields(v)
|
||||
if len(fld) != 2 {
|
||||
frame.Filename = fileParseError
|
||||
frame.AbsolutePath = v
|
||||
} else {
|
||||
frame.Filename = fld[0]
|
||||
sp := strings.Split(fld[0], ":")
|
||||
if len(sp) > 1 {
|
||||
i, err := strconv.Atoi(sp[len(sp)-1])
|
||||
if err == nil {
|
||||
frame.Filename = strings.Join(sp[:len(sp)-1], ":")
|
||||
frame.Lineno = i
|
||||
}
|
||||
}
|
||||
}
|
||||
if frame.AbsolutePath == "" && frame.Filename != fileParseError {
|
||||
frame.AbsolutePath = frame.Filename
|
||||
if sp := strings.Split(frame.Filename, "/"); len(sp) > 1 {
|
||||
frame.Filename = sp[len(sp)-1]
|
||||
}
|
||||
}
|
||||
thread.Stacktrace.Frames = append([]*raven.StacktraceFrame{frame}, thread.Stacktrace.Frames...)
|
||||
frame = nil
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Add last thread.
|
||||
s.Values = append(s.Values, thread)
|
||||
}
|
||||
|
||||
func findPanicSender(s *SentryThreads, err error) string {
|
||||
out := "error nil"
|
||||
if err != nil {
|
||||
out = err.Error()
|
||||
}
|
||||
for _, thread := range s.Values {
|
||||
if !thread.Crashed {
|
||||
continue
|
||||
}
|
||||
for i, fr := range thread.Stacktrace.Frames {
|
||||
if strings.HasSuffix(fr.Filename, "panic.go") && strings.HasPrefix(fr.Function, "panic") {
|
||||
// Next frame if any.
|
||||
j := 0
|
||||
if i > j {
|
||||
j = i - 1
|
||||
}
|
||||
|
||||
// Directory and filename.
|
||||
fname := thread.Stacktrace.Frames[j].AbsolutePath
|
||||
if sp := strings.Split(fname, "/"); len(sp) > 2 {
|
||||
fname = strings.Join(sp[len(sp)-2:], "/")
|
||||
}
|
||||
|
||||
// Line number.
|
||||
if ln := thread.Stacktrace.Frames[j].Lineno; ln > 0 {
|
||||
fname = fmt.Sprintf("%s:%d", fname, ln)
|
||||
}
|
||||
|
||||
out = fmt.Sprintf("%s: %s", fname, out)
|
||||
break // Just first panic.
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ReportSentryCrash reports a sentry crash with stacktrace from all goroutines.
|
||||
func (c *client) ReportSentryCrash(reportErr error) (err error) {
|
||||
if reportErr == nil {
|
||||
return
|
||||
}
|
||||
tags := map[string]string{
|
||||
"OS": runtime.GOOS,
|
||||
"Client": c.cm.config.ClientID,
|
||||
"Version": c.cm.config.AppVersion,
|
||||
"UserAgent": CurrentUserAgent,
|
||||
"UserID": c.userID,
|
||||
}
|
||||
|
||||
threads := &SentryThreads{}
|
||||
threads.TraceAllRoutines()
|
||||
errorWithFile := findPanicSender(threads, reportErr)
|
||||
packet := raven.NewPacket(errorWithFile, threads)
|
||||
|
||||
eventID, ch := raven.Capture(packet, tags)
|
||||
if err = <-ch; err == nil {
|
||||
c.log.Warn("Reported error with id: ", eventID)
|
||||
} else {
|
||||
c.log.Errorf("Can not report `%s` due to `%s`", reportErr.Error(), err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package pmapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
)
|
||||
|
||||
func TestSentryCrashReport(t *testing.T) {
|
||||
cm := NewClientManager(testClientConfig)
|
||||
c := cm.GetClient("bridgetest")
|
||||
if err := c.ReportSentryCrash(errors.New("Testing crash report - api proxy; goroutines with threads, find origin")); err != nil {
|
||||
t.Fatal("Expected no error while report, but have", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SentryThreads) TraceAllRoutinesTest() {
|
||||
s.Values = []Thread{
|
||||
{
|
||||
ID: 0,
|
||||
Name: "goroutine 20 [running]",
|
||||
Crashed: true,
|
||||
Stacktrace: &raven.Stacktrace{
|
||||
Frames: []*raven.StacktraceFrame{
|
||||
{
|
||||
Filename: "/home/dev/build/go-1.10.2/go/src/runtime/pprof/pprof.go",
|
||||
Function: "runtime/pprof.writeGoroutineStacks(0x9b7de0, 0xc4203e2900, 0xd0, 0xd0)",
|
||||
Lineno: 650,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 1,
|
||||
Name: "goroutine 20 [chan receive]",
|
||||
Crashed: false,
|
||||
Stacktrace: &raven.Stacktrace{
|
||||
Frames: []*raven.StacktraceFrame{
|
||||
{
|
||||
Filename: "/home/dev/build/go-1.10.2/go/src/testing/testing.go",
|
||||
Function: "testing.(*T).Run(0xc4203e42d0, 0x90f445, 0x15, 0x97d358, 0x47a501)",
|
||||
Lineno: 825,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user