BackEnd

Go言語のエラーハンドリングとログローテーション

投稿日:2018年11月5日 更新日:

はじめに

こんにちは、今回はGo言語でのエラーハンドリングとログ関連についての記事となります。

例外のないGo言語でどのようにエラーを伝搬させていくのかについて一つの方法を紹介させていただき、その後、そのエラーをファイルに出力する方法を紹介したいと思います。
なお、開発環境の構築がまだの方は、ぜひこちらで環境構築をしてからお読みいただければと思います。

エラーハンドリング

エラーハンドリングについてですが、まずはGo言語でerrorとは一体何ものなのかについて触れたいと思います。その後、pkg/errorsパッケージでerrorをwrapをする方法を記載し、次の節でそれを活用した独自のerrorsパッケージを作成したいと思います。

error インターフェース

Go言語では例外の概念がないため(Go2では例外が組み込まれるかもしれませんが)、ファイルの処理などで下記のようにerrorを返却するコードをよく目にすると思います。

この os.Open関数で返却されるerrorとは何かと言うと、error インタフェースを実装した構造体となります。下記にerrorインタフェースの定義を記載しておきます。

そして、errors.New関数で生成され、返却されるものは、上記を実装した構造体の一つになります。標準パッケージのerrors.goの内容も掲載しておきます。

つまり、errorインタフェースさえ実装すれば、何でもerrorとして扱えるということになり、本ブログでもその特性を利用して、独自のerrorsパッケージの作成を後半で行います。

pkg/errors パッケージ

error関連のパッケージで、pkg/errorsパッケージを少しだけ紹介したいと思います。これは、errorを呼び出し元に返却する際に元のerrorをwrapすることで、errorをstackさせていけるようにするためのシンプルなパッケージです。

例えば、先ほどos.Openのエラーハンドリングを記載しましたが、pkg/errorsを利用すると下記のようにerrorをstackさせることができます。

それでは、このpkg/errorsパッケージを利用して、独自のerrorsパッケージを作成してみたいと思います。

独自のエラータイプ付き errorsパッケージを作成

先ほどの節で、errorインタフェースを実装した構造体であれば、errorとして扱えるということがわかったと思います。ただ、文字列しか表現できないerrorでは何かと扱いにくい場合もあると思います。そこで、今回は一例としてエラータイプというものを持たせたerrorをpkg/errorsパッケージを利用して、作成したいと思います。早速コードの全量になります。

それほどコード量がないのでみていただければ、だいたい理解できると思いますが、このerrorsパッケージの特徴を簡単に箇条書きで記載しておきます。

  • errorインタフェースを実装したtypedError構造体を定義し、そこにエラーそのものとエラータイプを保持させた
  • typedError構造体をパッケージスコープにして、外部から直接生成できないようにした(エラータイプはGetType関数で取得)
  • ErrorTypeを定義して、特定のエラータイプのerrorを生成する関数郡を用意した
  • pkg/errorsを使い、errorをstackできるようにした

なお、このerrorsパッケージの利用例については、最後の節でエラーの内容をログファイルに出力させるサンプルで紹介します。

log パッケージ

この節では、上記のエラーをファイル出力や標準出力するためのログについて記載していきます。標準でもlogパッケージが提供されていますが、ログレベルの調整などを行う場合には手間がかかるため、githubで公開されているパッケージを利用しようと思います。

log パッケージのGitHubスター数

スター数が1,000を超えている有名どころのlog関連のパッケージのいくつかを下記に紹介します。(※2018/11/02時点)

name star latest commit
zap 5,427 2018/08/15
go-kit 11,688 2018/11/01
zerolog 1,464 2018/11/01
glog 2,003 2016/01/26
logrus 8,805 2018/11/02

この中では、zapが高速であることをおしており、私も実際にベンチマークをしたところ、logrusと比べ数倍は早い結果になりました。ただ、今回はそれほどログを大量に出力することは考慮せず、もっとも使い勝手がよく開発も活発なlogrusで独自のloggersパッケージを作ることにしました。

logrus パッケージ

このパッケージについては、ほとんど説明することなるすぐに利用ができます。こちらのREADMEにある通り、「ログのフォーマット(SetFormatter)」「ログの出力先(SetOutput)」「ログの出力レベル(SetLevel)」を設定して、あとはInfoやErrorなどの関数で出力を行うだけです。実際のサンプル例はログローテーションと合わせて後の節で記載します。

ログローテーション

先ほど紹介した全てのlogパッケージは、ログをファイルに出力してもログローテーションまではしてくれません。つまり、一つのファイルに永遠と出力を続け、放っておくと巨大なサイズのファイルができあがります。

ログローテーションのやり方としては、Linuxなどの環境にインストールされているlogrotateパッケージをデプロイ先のサーバで設定するというのがあります。ただ、これだとGo言語で開発しているプロジェクト内でコントロールできないので、今回はlumberjackを使って、ソースコード内で設定をしようと思います。

lumberjack パッケージ

このパッケージも利用方法はとてもシンプルで、こちらのREADMEを読むだけですぐに設定できます。
設定項目としては、「ファイル名(Filename)」「ローテーションするファイルサイズ(MaxSize)」「古いログを保持する日数(MaxAge)」「保持する古いログの最大ファイル数(MaxBackups)」「バックアップファイルの時刻フォーマットをサーバローカル時間にするかどうか(LocalTime)」「ローテーションされたファイルをgzip圧縮するかどうか(Compress)」があります。こちらも実際のサンプル例は次の節でlogrusと合わせて紹介します。

独自のloggersパッケージを作成

それでは、logrus と lumberjackの利用して、独自のloggersパッケージを作成してみたいと思います。とてもシンプルなので、いきなり全量のコードをはります。

上記の解説を箇条書きで記載します。

  • init関数の中でlogrusの初期化処理をlumberjackも使い、全て完了させた
  • 後の関数はlogrusをラップしただけ。(他のコードでlogrusを直接意識して触らせないためです)
  • logrusのファイル出力はJSON形式とし、ログレベルとログの出力先は環境変数によって分けた
  • lumberjackは、ファイル名を環境変数によって決め、1年間ログを保持し、古いファイルは圧縮させた

独自のerrors と loggers でエラーログ出力

それでは最後に、上記で紹介した独自のerrorsパッケージとloggersパッケージを利用するサンプルコードと実行結果を紹介します。

まず、下記が 上記の2パッケージを利用するmain.goファイルになります。mysqlサーバからuserレコードを取得する際にエラーが発生したということを擬似的に表現しています。

実行結果と出力されたログファイルの中身を記載します。

特筆すべきところとしては、switch文でログタイプにより処理が分岐できている点と、ログファイルのmsgの中身がerrorのstackを出力できている点です。これで、どのようにエラーが伝搬して最終的にログファイルに書き込まれたのかがわかるようになっています。

さいごに

いかがでしたでしょうか。エラーハンドリングから、そのエラー内容をログに出力させ、そしてログファイルをローテーションさせるまで一気に説明させていただきました。これをベースにもう少し使い勝手をよくしたり、また必要なログ情報を付け加えたりしていこうと思います。また、logrus から zapに切り替えるなどもやってみたいと思います。

なお、Go言語に関してはこちらでも多くの記事を紹介していますので、ぜひご覧ください。

おすすめ書籍

スターティングGo言語 (CodeZine BOOKS) Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ みんなのGo言語[現場で使える実践テクニック] Go言語による並行処理

blog-page_footer_336




blog-page_footer_336




-BackEnd
-,

執筆者:


comment

メールアドレスが公開されることはありません。

CAPTCHA


関連記事

Go言語

Go言語の基礎〜基本構文その2〜

1 はじめに2 配列3 スライス3.1 スライスの宣言3.2 len3.3 append3.4 range3.5 値の切り出し3.6 可変長引数4 map4.1 宣言と初期化4.2 マップの操作4.3 ...

RSpecの個人的Tips集〜その1〜

1 はじめに2 テストコードの実行をスキップする3 共通のテストコードを用意する4 外部APIの返却値をスタブにする5 さいごに はじめに みなさん、テストコードは書かれているでしょうか? 私も極力書 ...

icon

PHP、Ruby、Pythonのfor構文を比較してみた

1 はじめに1.1 環境2 ドキュメント2.1 PHP2.2 Ruby2.3 Python3 1から100まで出力してみる3.1 PHP3.2 Ruby3.3 Python4 リスト型(配列)の出力4 ...

laravel logo

Laravelでの署名付きURL生成

1 はじめに2 今回のサンプル3 ビュー4 ルーティング5 コントローラー5.1 署名付きURLの生成5.2 期限ありの署名付きURLの生成5.3 署名のチェック6 おまけ6.1 署名の仕組7 さいご ...

laravel logo

Laravelでテストコードを書くには? Featureテスト/Unitテスト

1 はじめに2 FeatureとUnitの使い分け3 テスト用データベースの準備4 Featureテスト4.1 テスト対象のコード4.2 テストコードの実装4.3 テスト結果の検証4.3.1 ステータ ...

フォロー

blog-page_side_responsive

2018年11月
 123
45678910
11121314151617
18192021222324
252627282930  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。