【GO】logrusでわかりやすいログ生活を


ログ基盤

ログについてメモしていきます。

ログ設計基本事項

主に下記を参考にしています。
下記記事をみておけば、かなり理解が進みます!
ログ設計指針 - Qiita
開発者が運用を経験すべき一つの理由 | Developers.IO
PSR-3: Logger Interface - PHP-FIG

気をつけること

DBの情報は記載しない

ログには「氏名」「住所」など特定の個人を識別する情報や「電話番号」や「メールアドレス」「SNSのアカウント情報」などインターネットにおいて個人と連絡を

FATALとERRORの違い

FATAL - アプリケーションを異常終了させるような非常に深刻なイベントを指定します。
ERROR - アプリケーションの稼働が継続できる程度のエラーを指定します。

違いは、システム全体が継続できるかどうかかつシステムの部分が継続できるかどうかだと思っています。

logrus

Goでは、logrusという便利なmoduleを使用して実装をしました。
GitHub - sirupsen/logrus: Structured, pluggable logging for Go.
【Go×ログ】logrusの使い方を簡単に分かりやすくまとめてみた - Qiita


package logger

import (
    "os"

    "github.com/sirupsen/logrus"
)

var LOGGING *logrus.Entry

func init() {
    logrus.SetOutput(os.Stdout)
    logrus.SetFormatter(&logrus.TextFormatter{
        // ログをカラーで出力する
        ForceColors:   true,
        FullTimestamp: true,
    })

    switch os.Getenv("TEST_ENV") {
    case "production", "staging":
        logrus.SetLevel(logrus.InfoLevel)
    default:
        logrus.SetLevel(logrus.DebugLevel)
    }

    LOGGING = logrus.WithFields(logrus.Fields{})
}

func Fatal(args ...interface{}) {
    logrus.Fatal(args...)
}

func Fatalf(format string, args ...interface{}) {
    logrus.Fatalf(format, args...)
}

func Error(args ...interface{}) {
    logrus.Error(args...)
}

func Errorf(format string, args ...interface{}) {
    logrus.Errorf(format, args...)
}

func Warn(args ...interface{}) {
    logrus.Warn(args...)
}

func Warnf(format string, args ...interface{}) {
    logrus.Warnf(format, args...)
}

func Info(args ...interface{}) {
    logrus.Info(args...)
}

func Infof(format string, args ...interface{}) {
    logrus.Infof(format, args...)
}

func Debug(args ...interface{}) {
    logrus.Debug(args...)
}

func Debugf(format string, args ...interface{}) {
    logrus.Debugf(format, args...)
}

Wrap

毎回logを吐く実装をするのも、きもわるいので、errorに内包しようと思いました。
使用例は下記の通りです。

// Sprintfで変数と文字列を結合する
if err != nil {
  return nil, errors.Wrap(err, fmt.Sprintf("Failed to Get account test: %v", test))
}

Go1.13のError wrappingを触ってみる - 逆さまにした
fmt.Printfなんかこわくない - Qiita

runtime

どこのファイルでかつどこのラインでエラーが出ているのかわかるようにしたいです。
そんな時はruntimeが非常に便利です。

下記のようなファイルを作成して、callerとして実行できるようにしました。

package caller

import (
    "runtime"
)

// ファイル名を取得するメソッド
func GetCurrentFile() string {
    _, file, _, _ := runtime.Caller(1)

    return file
}

// 行数を取得するメソッド
func GetCurrentFileLine() int {
    _, _, line, _ := runtime.Caller(1)

    return line
}

下記のように呼び出します。


if err != nil {
  return nil, errors.Wrap(err, fmt.Sprintf("Failed to Get account test: %v, file:%s line:%v", 
    test, caller.GetCurrentFile(), caller.GetCurrentFileLine()))
}

[Go] ファイル名、行数、関数名、スタックトレースをランタイム時に取得する - YoheiM .NET
runtime.Caller(1)をなめて扱ったら危険かもしれない - Qiita
実行時の関数名やファイルパス、行数を取得する【Go】 - 技術向上

その他

例外処理

「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件 - Qiita

fluentd仕組み

BufferedOutput pluginの代表的なoptionについて - Qiita
Fluentd ソースコード完全解説 (v0.10向け) · GitHub

GCPでのfluentd

loggerには、fluentdからログが送られている。
配置は、daemonsetで全てのnodeに配置している。

fluentdを導入時にまず知っておいたほうがよさそうなこと(インストール、監視、HA構成、チューニングなど) - Qiita
GKEではStackDriver Loggingにどうやってログを送っているか - Speaker Deck
Fluentdとはどのようなソフトウェアなのか - たごもりすメモ