diff --git a/go.mod b/go.mod index 1511839..6555b85 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200829165651-691a5b42a0b2 // indirect github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200701200531-7f61353ee73e // indirect + go.uber.org/zap v1.16.0 google.golang.org/grpc v1.31.0 google.golang.org/protobuf v1.23.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index d4cf2ce..3d2c77a 100644 --- a/go.sum +++ b/go.sum @@ -49,12 +49,14 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA= github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -71,6 +73,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -104,14 +107,24 @@ github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200815005552 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -145,6 +158,9 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -173,9 +189,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/app/input_controller.go b/internal/app/input_controller.go index f497f23..85b9372 100644 --- a/internal/app/input_controller.go +++ b/internal/app/input_controller.go @@ -35,6 +35,7 @@ type inputController struct { } func (c *inputController) init() { + logger.Infof("initializing input controller") c.SetServiceListModel(model.NewStringList(nil)) c.SetMethodListModel(model.NewStringList(nil)) c.SetRequestModel(model.NewMessage(nil)) @@ -91,7 +92,9 @@ func (c *inputController) processReflectionAPI(conn *grpc.ClientConn) error { func (c *inputController) processProtos(imports, protos []string) error { if len(protos) == 0 { - return errors.New("no *.proto files to process") + err := errors.New("no *.proto files to process") + logger.Infof("processProtos: %v", err) + return err } if len(imports) == 0 { // optomistacally try and use a import path @@ -106,7 +109,9 @@ func (c *inputController) processProtos(imports, protos []string) error { services := c.pbSource.Services() if len(services) == 0 { - return errors.New("no gRPC services found in proto files") + err := errors.New("no gRPC services found in proto files") + logger.Infof("processProtos: %v", err) + return err } c.ServiceListModel().SetStringList(services) @@ -115,6 +120,8 @@ func (c *inputController) processProtos(imports, protos []string) error { } func (c *inputController) serviceChanged(service string) { + logger.Infof("serviceChanged: %q", service) + methods := c.pbSource.Methods() srvMethods, ok := methods[service] @@ -131,6 +138,7 @@ func (c *inputController) serviceChanged(service string) { } func (c *inputController) methodChanged(service, method string) { + logger.Infof("methodChanged: \"%s/%s\"", service, method) md := c.pbSource.GetMethodDesc(service, method) if md == nil { return diff --git a/internal/app/logger.go b/internal/app/logger.go new file mode 100644 index 0000000..20a4e31 --- /dev/null +++ b/internal/app/logger.go @@ -0,0 +1,67 @@ +// Copyright 2020 Rogchap. All Rights Reserved. + +package app + +import ( + "net/url" + "path/filepath" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +// Logger is a simple logging interface +type Logger interface { + Errorf(template string, args ...interface{}) + Infof(template string, args ...interface{}) +} + +type rollingLogger struct { + *lumberjack.Logger +} + +// Sync is a noop to implement zapcore.WriteSyncer interface +func (*rollingLogger) Sync() error { + // noop + return nil +} + +func newLogger(appData string) (Logger, error) { + rl := &rollingLogger{ + &lumberjack.Logger{ + Filename: filepath.Join(appData, "app.log"), + MaxSize: 50, + MaxAge: 3, + }, + } + + zap.RegisterSink("rolling", func(*url.URL) (zap.Sink, error) { + return rl, nil + }) + + output := [1]string{"stdout"} + + if !isDebug { + output[0] = "rolling://" + } + + ecfg := zap.NewProductionEncoderConfig() + ecfg.EncodeTime = zapcore.ISO8601TimeEncoder + + cfg := zap.Config{ + Level: zap.NewAtomicLevelAt(zapcore.InfoLevel), + Encoding: "console", + OutputPaths: output[:], + ErrorOutputPaths: output[:], + EncoderConfig: ecfg, + } + + zl, err := cfg.Build() + if err != nil { + return nil, err + } + _ = zl + + return zl.Sugar(), nil +} diff --git a/internal/app/main_controller.go b/internal/app/main_controller.go index 6e29b1e..62370ea 100644 --- a/internal/app/main_controller.go +++ b/internal/app/main_controller.go @@ -21,6 +21,7 @@ type mainController struct { } func (c *mainController) init() { + logger.Infof("initializing main controller") c.SetWorkspaceCtrl(NewWorkspaceController(nil)) c.SetVersion(semver) } diff --git a/internal/app/startup.go b/internal/app/startup.go index 1a6b49f..388cd17 100644 --- a/internal/app/startup.go +++ b/internal/app/startup.go @@ -21,9 +21,14 @@ import ( var ( appname = "Wombat" semver = "0.1.0-beta.1" - isDebug = true + isDebug = false ) +// (rogchap) would prefer to not have a global logger, but unfortunately QObject constructors +// are unable to pass any arguments (for now). If this changes in the future, we should pass the logger +// to the NewMainController constructor and so forth. +var logger Logger + // Startup is the main startup of the application func Startup() int { core.QCoreApplication_SetApplicationName(appname) @@ -45,11 +50,17 @@ func Startup() int { } defer crashlog(appData) + var err error + if logger, err = newLogger(appData); err != nil { + panic(err) + } + mc := NewMainController(nil) engine.RootContext().SetContextProperty("mc", mc) engine.Load(core.NewQUrl3(entry, 0)) + logger.Infof("starting application: %s", semver) return app.Exec() } diff --git a/internal/app/workspace_controller.go b/internal/app/workspace_controller.go index b51d36b..17bf072 100644 --- a/internal/app/workspace_controller.go +++ b/internal/app/workspace_controller.go @@ -5,6 +5,7 @@ package app import ( "context" "errors" + "fmt" "os" "path/filepath" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/jhump/protoreflect/dynamic" "github.com/therecipe/qt/core" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/connectivity" @@ -46,6 +48,7 @@ type workspaceController struct { } func (c *workspaceController) init() { + logger.Infof("initializing workspace controller") c.ConnectFindProtoFiles(c.findProtoFiles) c.ConnectAddImport(c.addImport) @@ -59,7 +62,7 @@ func (c *workspaceController) init() { } var err error - c.store, err = db.NewStore(dbPath) + c.store, err = db.NewStore(dbPath, logger.(*zap.SugaredLogger)) if err != nil { println(err.Error()) } @@ -92,7 +95,7 @@ func (c *workspaceController) findProtoFiles(path string) { path = core.NewQUrl3(path, core.QUrl__StrictMode).ToLocalFile() var protoFiles []string - // TODO [RC] We should do the search async and show a loading/searching icon to the user + // TODO(roghcap) We should do the search async and show a loading/searching icon to the user filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if filepath.Ext(path) == ".proto" { protoFiles = append(protoFiles, path) @@ -101,11 +104,10 @@ func (c *workspaceController) findProtoFiles(path string) { }) if len(protoFiles) == 0 { - // TODO [RC] Show error to user that there is no proto files found + // TODO(rogchap) Show error to user that there is no proto files found return } - // TODO [RC] Shoud we be replacing or adding? c.Options().ProtoListModel().SetStringList(protoFiles) } @@ -131,8 +133,11 @@ func (c *workspaceController) processProtos() error { } func (c *workspaceController) connect(addr string) error { + logger.Infof("connect: %q", addr) if addr == "" { - return errors.New("no address to connect") + err := errors.New("no address to connect") + logger.Infof("connect: %v", err) + return err } if c.grpcConn != nil { @@ -144,6 +149,7 @@ func (c *workspaceController) connect(addr string) error { var err error c.grpcConn, err = BlockDial(addr, c.Options(), c.OutputCtrl()) if err != nil { + logger.Infof("connect BlockDial error: %v", err) return err } @@ -153,7 +159,9 @@ func (c *workspaceController) connect(addr string) error { go func() { defer func() { // TODO(rogchap) Should be a better way than swallowing this panic? - recover() + if r := recover(); r != nil { + logger.Errorf("panic waiting for gRPC state change: %v", r) + } }() for { @@ -186,17 +194,32 @@ func (c *workspaceController) connect(addr string) error { } c.workspace.Addr = addr + logger.Infof("saving workspace to store") c.store.SetWorkspace(defaultWorkspaceKey, c.workspace) }() return nil } -func (c *workspaceController) send(service, method string) error { +func (c *workspaceController) send(service, method string) (rerr error) { + defer func() { + if r := recover(); r != nil { + rerr = fmt.Errorf("unexpected error for \"%s/%s\": %v", service, method, r) + logger.Errorf("send panic: %v", rerr) + } + }() + if c.grpcConn == nil { + // noop return nil } md := c.InputCtrl().pbSource.GetMethodDesc(service, method) + if md == nil { + rerr = fmt.Errorf("unable to find descriptor for \"%s/%s\"", service, method) + logger.Errorf("send: %v", rerr) + return + } + req := processMessage(c.InputCtrl().RequestModel()) if data, err := req.Marshal(); err == nil { diff --git a/internal/db/store.go b/internal/db/store.go index 566a527..8c902ad 100644 --- a/internal/db/store.go +++ b/internal/db/store.go @@ -6,18 +6,29 @@ import ( "path/filepath" badger "github.com/dgraph-io/badger/v2" + "go.uber.org/zap" "google.golang.org/protobuf/proto" ) +type dbLogger struct { + *zap.SugaredLogger +} + +func (l *dbLogger) Warningf(template string, args ...interface{}) { + l.Warnf(template, args...) +} + // Store is a wrapper to a DB to store data to disk type Store struct { db *badger.DB } // NewStore creates a new store to save user data -func NewStore(path string) (*Store, error) { +func NewStore(path string, logger *zap.SugaredLogger) (*Store, error) { dbPath := filepath.Join(path, "db") - db, err := badger.Open(badger.DefaultOptions(dbPath)) + opts := badger.DefaultOptions(dbPath) + opts = opts.WithLogger(&dbLogger{logger}) + db, err := badger.Open(opts) if err != nil { return nil, err } diff --git a/qml/views/MethodSelect.qml b/qml/views/MethodSelect.qml index 32357ac..2163cc6 100644 --- a/qml/views/MethodSelect.qml +++ b/qml/views/MethodSelect.qml @@ -43,6 +43,17 @@ Item { anchors.right: parent.right text: qsTr("Send") color: Style.primaryColor - onClicked: wc.send(cbServiceList.displayText, cbMethodList.displayText) + onClicked: { + const err = wc.send(cbServiceList.displayText, cbMethodList.displayText) + if (err) { + errMsg.text = err + errMsg.open() + } + } + + MessageBox { + id: errMsg + title: qsTr("Error invoking RPC") + } } }