Slide 1

Slide 1 text

Consider better error handling for web apps Go Conference 2017 Autumn - by @izumin5210

Slide 2

Slide 2 text

How do you handle errors in applications ?

Slide 3

Slide 3 text

Ignore ? ``` resp, _ := DoSomething() ```

Slide 4

Slide 4 text

Panic ? ``` resp, err := DoSomething() if err != nil { panic(err) } ```

Slide 5

Slide 5 text

Wrap ? ``` import ( "github.com/pkg/errors" ) resp, err := DoSomething() if err != nil { return errors.Wrap(err) } ```

Slide 6

Slide 6 text

### Why should we `Wrap` errors ? 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. github.com/pkg/errors (emphasis mine) 6

Slide 7

Slide 7 text

### `return nil, errors.Wrap(err)` , and what then? ``` func GetProfile(w http.ResponseWriter, r *http.Request) { // ... s, err := store.GetProfileByID(userID) if err != nil { w.WriteHeader(500) return } // ... } ``` 7

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Error reporting

Slide 10

Slide 10 text

### e.g. Error reporting as a Service - Honeybadger.io - Sentry etc. 10

Slide 11

Slide 11 text

From where do we send error reports? 11

Slide 12

Slide 12 text

#### 1. レスポンス直前 ``` if err != nil { notifyError(err) // <-- here! w.WriteHeader(500) return } // or middlewares, interceptors, etc. ``` - expected errorとunexpected errorをどう区別する? - status codeをどうやって決定する? 12

Slide 13

Slide 13 text

#### 2. エラー出次第? ``` q := "INSERT INTO working_histories (profile_id, company) VALUES ($1, $2)" result, err := conn.Exec(q, 1253477, "Wantedly, Inc.") if err != nil { errorreporter.NotifyError(err) // <-- here! return nil, errors.Wrap(err, "failed to write new working history") } ``` - あらゆるレイヤがerror reporterと密結合することになる - 「どこで `NotifyError` したか」わからなくなる 13

Slide 14

Slide 14 text

### For appropriate error reporting - Adds context to errors - It's provided by `github.com/pkg/errors` - Decides whether errors is as expected or unexpected - Decides responses to users (e.g. HTTP Status Code, etc.) 14

Slide 15

Slide 15 text

## Better error annotations for applications e.g. `github.com/creasty/apperrors` `apperrors` provides contextual metadata to errors. - Stack trace - Additional information - Status code (for a HTTP server) - Reportability (for an integration with error reporting service) 15

Slide 16

Slide 16 text

``` if isProfileExists(profileID) { // can annotate errors with error codes return nil, apperrors.WithStatusCode( apperrors.New("profile does not exist"), http.StatusNotFound, ) } result, err := conn.Exec("INSERT INTO ...", profileID, company) if err != nil { // can annotate unexpected errors return nil, apperrors.WithReport( apperrors.WithMessage(err, "failed to write new working history"), ) } ``` 16

Slide 17

Slide 17 text

``` func HandleError(w http.ResponseWriter, err error) { appErr := apperrors.Unwrap(err) if appErr.Report { // We can send report only about unexpected errors go uploadAppError(appErr) } if appErr.StatusCode > 0 { w.WriteHeader(appErr.StatusCode) } else { w.WriteHeader(http.StatusInternalServerError) } } ``` 17

Slide 18

Slide 18 text

``` err := store.CreateWorkingHistory(userID, company) if err != nil { HandleError(w, err) return } ``` 18

Slide 19

Slide 19 text

### 独自Error typeはいいのか? Gocon Spring 2016 Keynote - APIは `github.com/pkg/errors` + α - `WithReport()` と `WithStatusCode()` ぐらい - errorをmiddlewareで処理すれば依存は最小限? 19

Slide 20

Slide 20 text

### `WithStatusCode` をDBに近いレイヤーで使うの? - これはあまりうれしくない - HTTP Status CodeはclientへのViewの一つでしかない - そのドメインでのエラーコード一覧をつくる? - エラー処理時にHTTP, gRPC等のStatus Codeへマップ 20

Slide 21

Slide 21 text

Sample application izumin5210-sandbox/grpc-and-gateway-sample-app-go on github

Slide 22

Slide 22 text

### `ErrorCode` ``` // type/system/error.go type ErrorCode int const ( ErrorUnknown ErrorCode = iota ErrorNotFound ErrorFailedToReadDB ) ``` 22

Slide 23

Slide 23 text

### Functions for `ErrorCode` ``` // type/system/error.go func (c ErrorCode) Wrap(err error) error { return apperrors.WithStatusCode(apperrors.Wrap(err), int(c)) } func (c ErrorCode) WithReport(err error) error { return apperrors.WithReport(c.Wrap(err)) } ``` 23

Slide 24

Slide 24 text

### Annotate errors ``` // store/profile/store.go err := s.DB.Get(prof, "SELECT * FROM profiles WHERE user_id = $1", userID) if err != nil { if err == sql.ErrNoRows { err = errors.Wrap(err, "profile was not found") return nil, system.ErrorNotFound.Wrap(err) } err = errors.Wrap(err, "failed to read profile") return nil, system.ErrorFailedToReadDB.WithReport(err) } ``` 24

Slide 25

Slide 25 text

### Handle errors on interceptor ( `github.com/izumin5210/grpc-errors` : gRPCサーバ で `apperrors` をいい感じにやるやつ) 25

Slide 26

Slide 26 text

#### Convert status code ``` // store/interceptor/errors.go var grpcCodeBySystemCode = map[system.ErrorCode]codes.Code{ system.ErrorUnknown: codes.Unknown, system.ErrorNotFound: codes.NotFound, system.ErrorFailedToReadDB: codes.Internal, } ``` 26

Slide 27

Slide 27 text

``` // store/interceptor/errors.go grpcerrors.WithStatusCodeMapper(func(code int) codes.Code { grpcCode, ok := grpcCodeBySystemCode[system.ErrorCode(code)] if !ok { return codes.Unknown } return grpcCode }) ``` 27

Slide 28

Slide 28 text

#### Send error reports ``` // store/interceptor/errors.go grpcerrors.WithUnaryServerReportableErrorHandler(func(...) error { st := &raven.Stacktrace{} for _, t := range err.StackTrace { // create and append stacktrace frames } pckt := raven.NewPacket(err.Error(), st) // set contextual metadata to packet raven.Capture(pckt, map[string]string{"method": info.FullMethod}) return err }), ``` 28

Slide 29

Slide 29 text

#### Error report on sentry.io 29

Slide 30

Slide 30 text

30

Slide 31

Slide 31 text

## Conclusion - アプリケーション改善には `pkg/errors` じゃ足りない? - error reportやstatus codeをうまく扱いたい - `creasty/apperrors` - これをmiddlewareで処理する - これがベストとは思っていない - みなさんどうしてますか 31

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

## References - articles - Gocon Spring 2016 Keynote - Golangのエラー処理とpkg/errors | SOTA - packages - github.com/pkg/errors - github.com/creasty/apperrors - github.com/izumii5210/grpc-errors - Sample application - izumin5210-sandbox/grpc-and-gateway-sample-app-go 33