🦔

Goエラーハンドリングを簡単にするOSS作った

2021/09/29に公開

Goのエラーハンドリングを簡単にするOSS

こちら思い付きで即興で作りました。
少しずつ手直ししたりテスト書いたりしてます。
https://github.com/maru44/perr

MITライセンスで公開しています
興味ある方は使ってみてください
issue待ってます!
アドバイス等もお待ちしております!

作った背景

エラーが発生した際に開発者に必要な情報を提供しつつ、クライアント(ユーザーやフロントエンド)に対して適した情報を提供するためのツールです。
例えば dial tcp 127.0.0.1:3306: connect: connection refused のようなmysqlのエラーが発生した際にはそれをクライアントに露出させたくはないです。
クライアントに伝えるべきは Internal Server Error です。
逆にログが Internal Server Error だけだと何が原因なのか一目でわかりません。

つまりエラーにクライアントのための顔と、開発者のための顔を用意してあげると色々と楽になれるのではないかと思いました。
そこでユングのペルソナになぞらえてperrという名前にしました。

加えて

  • エラーハンドリングを簡単にするために比較メソッド Is()
  • ログや通知のための Level() メソッド
  • 解析のためにスタックトレース出力メソッド Traces()
  • 構造的に保存できるように Map()Json() メソッド

を用意しました。

使い方

ザックリと解説していきます。
基本的には相手に見せるものとログ用のエラーを指定します。

基本

p := perr.Wrap(err, perr.BadRequest)
p := perr.New("new error", perr.BadRequest)

どちらもperr.Errのポインタを返します。perr.ErrはError() stringインターフェースを満たしているのでerrorインターフェースとして扱うことが可能となっています。

WrapとNewの細かい説明はいかにします。

既存のエラーをラップする

Wrapメソッドを使います。
第一引数に既存のエラーを、第二引数にどういうエラーとして扱うか?(クライアント向け)を指定します。
第一引数がnilであればnilを返します。

wrap.go
_, err := strconv.Atoi("sample")
p := perr.Wrap(err, perr.BadRequest) # ラップする

fmt.Printf("Client: %v\n", p.Output().Error())
fmt.Printf("Developer: %v\n", p.Error())

// 出力結果
// Client: Bad Request
// Developer: strconv.Atoi: parsing "sample": invalid syntax

WrapithLevel()を使うと独自にレベルを付与できます。

withlevel.go
l := perr.ErrLevel("Dangerous")

_, err := strconv.Atoi("sample")

// 普通
p := perr.Wrap(err, perr.BadRequest)
fmt.Printf("Default Level: %v\n", p.Level())

// レベル付与
pl := perr.WrapWithLevel(err, perr.BadRequest, l)
fmt.Printf("With Level: %v\n", pl.Level())

// 出力結果
// Default Level: EXTERNAL ERROR
// With Level: Dangerous

新規エラー

Newメソッドを使います

第一引数にstringを指定します。この文字列がerrors.New()されてログ用のエラーとなります。
第二引数にどういうエラーとして扱うか?(クライアント向け)を指定します。

第一引数がブランクであれば開発者向けとクライアント向けが同じになります。
第二引数がnilであれば上と同様です。

第一引数がブランクかつ第二引数がnilであればnilを返します。

new.go
err := perr.New("Someone pour coffee into a tea cup", perr.BadRequest)

fmt.Printf("Client: %v\n", err.Output().Error())
fmt.Printf("Developer: %v\n", err.Error())

// 出力結果
// Client: I'm a teapot
// Developer: Someone pour coffee into tea cup.

またオプショナルな第三引数を使うとクライアントへのメッセージを変えられます。

new2.go
err := perr.New("Someone pour coffee into a tea cup", perr.BadRequest, "Don't pour coffee!")

fmt.Printf("Client: %v\n", err.Output().Error())
fmt.Printf("Developer: %v\n", err.Error())

// 出力結果
// Client: Don't pour coffee!
// Developer: Someone pour coffee into tea cup.

こちらもラップと同様NewWithLevel()を使うと独自にレベルを付与できます。

stacktrace

スタックトレースを取得できます

stack.go
_, err := strconv.Atoi("sample")
p := perr.Wrap(err, perr.BadRequest) # ラップする

fmt.Print(p.Traces().String())

// 出力結果
// /secret/stack.go:81 ===> main

// p.Traces()でStackTrace
// StackTracesを取得してます

Map & Json

それぞれ構造体とJson([]byte)を用意してくれます。
保存等するのに便利だと思います。

細かくは省略しますが、それぞれMap()Json()メソッドを使用します。

今後の展望

本当に思いつきで作っただけなのでブラッシュアップしていきたいです。
ただ小回りの効かないものにはしたくないのでできる限り最小限にしようとは思っています。

あとまだだいぶ荒いのでちょっとずつ改善していきます。

階層化

ラップ時に対象のerrorPerrorインターフェースを満たしていればstacktraceを追加する

test や CI

タイトル通り

GitHubで編集を提案

Discussion