今日はなにの日。

気になったこと勉強になったことのメモ。

今日は、Go言語でLogging実装したの日。

目次

とある日

Goの勉強を行うため、Programの問題を解いていた。

当たり前のようにfmt.Printデバッグをしていた。

そこで思った。

「めんどくさいな」

そう、fmt.Printデバッグだと色々とめんどくさい。

あ、Pythonにはloggingがある。

ってことで探したら、デフォルトであるみたい。

golang.jp

でも使い勝手が悪い、なので作ります。

Logging

Goは勉強し始めて三ヶ月ほどなのでまだまだ至らないところがありますがご了承ください。

現在はinterface{}という便利な型の勉強をしているので一部完成していない機能もあります。

ちなみに、Python3loggingを参考にして作ってます。

概要

Python 用ロギング機能から拝借。

logging は、あるソフトウェアが実行されているときに起こったイベントを追跡するための手段です。ソフトウェアの開発者は、特定のイベントが発生したことを示す logging の呼び出しをコードに加えます。イベントは、メッセージで記述され、これに変数データ (すなわち、イベントが起こる度に異なるかもしれないデータ) を加えることもできます。イベントには、開発者がそのイベントに定めた重要性も含まれます。重要性は、レベル (level)重大度 (severity) とも呼ばれます。

  • ロガーは、アプリケーションコードが直接使うインタフェースを公開します。
  • ハンドラは、(ロガーによって生成された) ログ記録を適切な送信先に送ります。
  • フィルタは、どのログ記録を出力するかを決定する、きめ細かい機能を提供します。
  • フォーマッタは、ログ記録が最終的に出力されるレイアウトを指定します。

要約するとデバッグに役に立つ機能です。

ソースコード

全体を乗せると長くなるので、この記事では要所要所しか載せないです。

全体を閲覧したい方は、GitHubで公開していますのでご覧ください。

ドキュメントはないので、コードを読み解いて行ってもらうしかないです。

今後追加したいとは思っています。

github.com

色々と省略しています。

type Logging struct {
    format        string
    formatTime    bool
    formatFunName bool
    formatRunLine bool
    level         int
    DEBUG         int
    INFO          int
    WARRING       int
    ERROR         int
    CRITICAL      int
    levelList     [5]string
    formatList    map[string]string
    formatLenMax  int
    printMode     []string
    funcLog       []string
}

func NewLogging() *Logging {
    logging := new(Logging)
    logging.InitializeLevel()
    logging.InitializeLevelList()
    logging.InitializeFormatList()
    logging.InitializeFormatMode()
    logging.GetMaxLenghtForFormat()
    logging.level = logging.DEBUG
    logging.format = "%#v\n"
    return logging
}
func main() {
    logging := NewLogging()
    logging.Debug(1)
}

作成していく中で知って驚いたのが、Goにはコンストラクターたるものが存在しないみたいです。

なので、初期値を設定するNewLogging関数を作成しています。

main関数logging.Debug(1)で出力をしています。

余分な機能を除去して出力すると現在はこんな感じです。

func main() {
    logging := NewLogging()
    logging.Debug(1)
}
// >>1

機能

自分がこだわって作成している機能を3つに絞って解説したいと思います。

関数名出力

自分が過去にPython3loggingを使用していたときの使い方として、特定の関数が実行されたときに処理順を記録するために関数名を出力してました。

出力部分は、loggingで行っていたのですが、関数名の抽出は独自で実装しました。

他にも、過去にimportしてある別ファイルのlogging出力があり原因究明に時間がかかったことがあります。

という経験からデフォルトでそういったことを行ってくれる機能を実装しました。

使用例

l.formatFunNameで出力のオンオフを切り替えます。

現在はソースを直接操作して変更することにしています。

func (l *Logging) FormatConfig() {
      var formatTemplate = fmt.Sprintf("-%ds", l.formatLenMax+1)
    if l.formatFunName {
        pc, _, _, _ := runtime.Caller(3)
        f := runtime.FuncForPC(pc)
        var temp = fmt.Sprintf("%"+formatTemplate, l.formatList["formatFunName"])
        fmt.Printf(temp+"=> %s\n", f.Name())
    }
}

//Test temp
func Test() {
    logging := NewLogging()
    logging.Debug(1)
}

func main() {
    Test()
}

//Function Names  => main.Test
//1

Function Names => main.Testがこの機能の該当行になります。

Test関数から実行されていることがわかるので、どこのなんのための出力なのかがわかる。

行番号出力

関数名出力機能と同じ構想のもと実装しました。

関数名だけでは、同じ関数内で複数回実行していた場合どこの出力なのか判別しづらい問題があると思うのでそれに対応するためプログラムソース上の行番号を出力する機能を実装しました。

使用例

l.formatRunLineで出力のオンオフを切り替えます。

現在はソースを直接操作して変更することにしています。

func (l *Logging) FormatConfig() {
    var formatTemplate = fmt.Sprintf("-%ds", l.formatLenMax+1)
    if l.formatRunLine {
        var temp = fmt.Sprintf("%"+formatTemplate, l.formatList["formatRunLine"])
        _, _, line, _ := runtime.Caller(3)
        fmt.Printf(temp+"=> %d\n", line)
    }
}
//Test temp
func Test() {
    logging := NewLogging()
    logging.Debug(1)
}

func main() {
    Test()
}
//Run Line number => 186
//1

ソースコードをそのまま載せていないのでわからないと思いますが、

logging.Debug(1)の行は、プログラムソース上だと186行目となっています。

指定レベル出力(未完成)

本家のloggingでは、指定レベル以上のものを出力する形式があるのですが、自分は特定のレベルだけの出力もしたい時がありました。

なので、それを実装する予定です。

構想案

func Test() {
    logging := NewLogging()
    logging.printMode = "Error"
    logging.Debug(0)
    logging.Info(1)
    logging.Warning(2)
    logging.Error(3)
    logging.Critical(4)
}

func main() {
    Test()
}

//>>3

本家の場合だとErrorだけでなく、Criticalも出力されるのですがこの機能を使用すると特定のレベルだけの出力になる。

一応、本家の同様特定のレベル以上の出力も機能として実装しますがこの機能は別物として実装します。

特定レベルと特定レベル以上の出力優先度とかの設定もできたらいいなと思っています。

基本的機能の実装は完成しました。

Goに慣れていないので、あまりきれいな記述方法や正しくはないと思いますが今後改変しなが実装しようと思っています。

今後追加したい機能などもあるので、Goに慣れつつ頑張ります。

今は他にも、Pythonpprintを作成中です。

そこで、型について躓いているので作業難航中です。