🦁

Go の Panic と log.Fatal() って何が違うの?

に公開

はじめに

Go で config ファイルを作成するときに panic を利用するか、log.Fatal を利用するか議論になったの

前提としてGoの技術書の中でも panic の扱いについて記述されているので 「panic は使いたくない」ぐらいの意見しかなかったけど調べてみると Config ファイルにて panic なのか log.Ftal なのかというブログなどがあったので何となく調べてみた

log.Fatalf とは

log.Fatalf() はエラーメッセージをフォーマット化して出力し、その後に os.Exit(1) を呼び出してアプリケーションを強制的に終了させます。

内部実装の詳細:

// log.Fatalf の内部実装(簡略化)
// 参考: https://pkg.go.dev/log#Fatalf
func Fatalf(format string, v ...interface{}) {
    log.Printf(format, v...)  // エラーメッセージを出力
    os.Exit(1)                // ここで終了させてる
}

参考: Go公式ドキュメント - log package

重要な特徴:

  • os.Exit(1) を直接呼び出すため、defer文は実行されません
  • プロセス全体が即座に終了するため、クリーンアップ処理が実行されません
  • 戻り値はありません(プロセスが終了するため)

panic との最大の違いは、defer文が実行されないことです。
また panic した時に recover で疲労こともできますが、log.Fatal は即時に終了させます

package main

import (
	"fmt"
	"log"
	"os"
)

func main() {

	db := os.Getenv("DB_HOST")

	if db == "" {
		log.Fatalf("環境変数が読み込まれてません")
	}

	fmt.Println("ここまで辿り着きません")
}

出力結果:

2009/11/10 23:00:00 環境変数が読み込まれてません

念の為、defer が呼ばれないことも実装していきましょう
defer は関数が終了時に実行されます

ファイル操作をする関数などで os.Open と Close を利用するケースの際に Close を defer で実行すると全ての処理が完了した時に Close が呼び出されます

package main

import (
	"fmt"
	"log"
)

func main() {
	defer fmt.Println("これは実行されない")
	fmt.Println("start")

	log.Fatalf("致命的エラーで即終了")
}

出力結果:

start
2009/11/10 23:00:00 致命的エラーで即終了

panic とは

こういうことです

システムが終了するのでその後の処理が実行されません

package main

import (
	"fmt"
)

func main() {
	defer fmt.Println("defer が実行されます")
	fmt.Println("start")

	panic("パニックが発生しました")
	
	fmt.Println("ここは実行されません")
}

出力結果:

start
defer が実行されます
panic: パニックが発生しました

recover によるキャッチの違い

panic は recover でキャッチ可能:

package main

import (
    "fmt"
)

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("panic をキャッチしました: %v\n", r)
        }
    }()
    
    fmt.Println("start")
    panic("パニックが発生しました")
    fmt.Println("ここは実行されません")
}

出力結果:

start
panic をキャッチしました: パニックが発生しました

Config ファイル処理における推奨事項

log.Fatal を使用すべき場面(AI)

  • 必須の設定ファイルが見つからない場合
  • 環境変数が設定されていない場合
  • 設定値の形式が不正な場合
  • データベース接続情報が不正な場合

感想

panic と log.Fatal の場合は log.Fatal の方がいいような気もするけど、開発環境においてそもそも毎回システム終了させるの面倒な気もするので log.Errorf で出す方がいいなと思ってそういう実装をしちゃってます
どうするのが正解なんだろうね、難しいね

Discussion