Skip to content

Instantly share code, notes, and snippets.

@izumin5210

izumin5210/gocon2017autumn.md Secret

Last active Nov 5, 2017
Embed
What would you like to do?
Go Conference 2017 Autumn
fontSize page pageNumber
32
className
page
enable className
ture
pageNumber
<style> :host { --color-primary: rgba(255, 102, 102, 0.8); --color-blue: #5BBAD1; --spacing: 1.5625%; } :host > div { background-color: rgba(0, 0, 0, 0.04); padding: calc(3 * var(--spacing)); } * { font-family: mplus-2p-regular; font-weight: 500; line-height: 1.8; box-sizing: border-box; margin: 0; padding: 0; color rgba(0, 0, 0, 0.8); } h1 { font-size: 130%; } h2 { font-size: 120%; } h2:before { content: '##'; } h3 { font-size: 110%; } h3:before { content: '###'; } h4 { font-size: 100%; } h4:before { content: '####'; } h2, h3, h4 { margin-bottom: calc(3 * var(--spacing)); border-bottom: 2px solid rgba(0, 0, 0, 0.24); } h2:before, h3:before, h4:before { font-size: 80%; color: rgba(0, 0, 0, 0.24); padding-right: var(--spacing); } a:link, a:visited { color: rgba(0, 0, 0, 0.8); border-bottom: 2px dotted rgba(0, 0, 0, 0.24); text-decoration: none; } ul { margin-left: calc(2 * var(--spacing)); list-style: none; } li { margin-bottom: calc(2 * var(--spacing)); } li:before { content: '- '; font-size: 80%; color: rgba(0, 0, 0, 0.24); padding-right: var(--spacing); } li > p { display: inline; } code, pre { font-family: Ricty; } code { // background-color: rgba(0, 0, 0, 0.12); // padding: calc(0.25 * var(--spacing)) calc(0.5 * var(--spacing)); // border-radius: calc(0.5 * var(--spacing)); padding: 0 calc(0.5 * var(--spacing)); } code:before, code:after { color: rgba(0, 0, 0, 0.24); content: '`'; } pre { font-size: 85%; background-color: rgba(0, 0, 0, 0.04); padding: calc(0.5 * var(--spacing)) calc(1.5 * var(--spacing)); margin: calc(1 * var(--spacing)); } pre:before, pre:after { display: block; color: rgba(0, 0, 0, 0.24); content: '```'; padding: 0; } pre code.hljs { padding: 0; background-color: transparent; } pre code:before, pre code:after { content: ''; } strong { font-weight: normal; font-family: mplus-2p-bold; } img { max-width: 100%; max-height: 100%; } blockquote { font-size: 90%; border-left: 4px solid rgba(0, 0, 0, 0.24); padding-left: calc(2 * var(--spacing)); margin: 0 calc(2 * var(--spacing)); } blockquote a { font-size: 85%; } .vcenter { display: flex; justify-content: center; flex-direction: column; } .center { display: flex; align-items: center; justify-content: center; flex-direction: column; } .center pre { width: 100%; } .blue { background-color: var(--color-black); color: white; } .purple { background-color: #a96dd8; color: white; } .onlyHeading h2, .onlyHeading h3, .onlyHeading h4 { border: none; padding: 0; margin: 0; width: 100%; text-align: center; } .onlyHeading h2:before, .onlyHeading h3:before, .onlyHeading h4:before { content: ''; } .page { position: relative; } .pageNumber { position: absolute; font-size: 75%; color: #999; right: calc(2 * var(--spacing)); bottom: calc(2 * var(--spacing)); } .page-sm pre { font-size: 70%; } .references li { font-size: 80%; } .intro { padding: 0; margin: 0; } </style> <style> .title { align-items: flex-start; background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), linear-gradient(112.5deg, hsl(192, 44%, 51%), hsl(192, 44%, 21%)); color: white; } .title h1 { border-bottom: 1px solid white; margin-bottom: calc(1.5 * var(--spacing)); padding: calc(1.5 * var(--spacing)) calc(0.5 * var(--spacing)); font-size: 134%; font-family: mplus-2p-light; } .title h1 strong { font-weight: normal; font-family: mplus-2p-medium; } .title p { padding: 0 var(--spacing); font-weight: 100; font-size: 90%; font-family: mplus-2p-light; } </style>

Consider better error handling for web apps

Go Conference 2017 Autumn - by @izumin5210

How do you handle errors in applications ?

Ignore ?

resp,  _ := DoSomething()

Panic ?

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

Wrap ?

import (
	"github.com/pkg/errors"
)

resp, err := DoSomething()
if err != nil {
	return errors.Wrap(err)
}

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)

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
	}

	// ...
}

review.png (18.8 kB)

Error reporting

e.g. Error reporting as a Service

  • Honeybadger.io
  • Sentry

etc.

From where do we send error reports?

1. レスポンス直前

if err != nil {
	notifyError(err) // <-- here!
	w.WriteHeader(500)
	return
}

// or middlewares, interceptors, etc.
  • expected errorとunexpected errorをどう区別する?
  • status codeをどうやって決定する?

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したか」わからなくなる

For appropriate error reporting

  • Adds context to errors
  • Decides whether errors is as expected or unexpected
  • Decides responses to users (e.g. HTTP Status Code, etc.)

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)
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"),
	)
}
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)
	}
}
err := store.CreateWorkingHistory(userID, company)
if err != nil {
	HandleError(w, err)
	return
}

独自Error typeはいいのか?

image.png (13.7 kB)

Gocon Spring 2016 Keynote

  • APIはgithub.com/pkg/errors + α
    • WithReport()WithStatusCode()ぐらい
  • errorをmiddlewareで処理すれば依存は最小限?

WithStatusCodeをDBに近いレイヤーで使うの?

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

Sample application

izumin5210-sandbox/grpc-and-gateway-sample-app-go

on github

ErrorCode

// type/system/error.go

type ErrorCode int

const (
	ErrorUnknown ErrorCode = iota
	ErrorNotFound
	ErrorFailedToReadDB
)

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))
}

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)
}

Handle errors on interceptor

github.com/izumin5210/grpc-errors: gRPCサーバでapperrorsをいい感じにやるやつ)

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,
}
// store/interceptor/errors.go

grpcerrors.WithStatusCodeMapper(func(code int) codes.Code {
	grpcCode, ok := grpcCodeBySystemCode[system.ErrorCode(code)]
	if !ok {
		return codes.Unknown
	}
	return grpcCode
})

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
}),

Error report on sentry.io

image.png (55.2 kB)

image.png (62.8 kB)

Conclusion

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

https://gyazo.com/28fca481d7e8c7f6e9e9dcef5e5f7a35

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.