13.4 ログとコンフィグ設計

ログとコンフィグの重要性

ログがわれわわれの開発において非常に重要な作用を持つことは前にご紹介しました。ログを通じて我々の情報をデバッグすることができます。当初seelogというログシステムをご紹介しました。異なるlevelに基いて異なるログを出力します。これはプログラムの開発とデプロイにおいて非常に重要となります。プログラムを開発中はlevelを低く設定しておき、デプロイ時にlevelを上げることで開発中に行っていた情報を隠蔽することができます。

コンフィグモジュールはアプリケーションのデプロイからサーバの異なる設定情報まで非常に有用です。例えばデータベースの設定情報、ポートの監視、アドレスの監視などは設定ファイルによって設定を行うことができます。このようにアプリケーションは非常に強力な柔軟性を持ちますので、異なるマシン上に設定ファイルを用意することで違うデータベースにアクセスするといったことができるようになります。

beegoのログ設計

beegoのログ設計デプロイ思想はseelogより来ています。異なるlevelによってログに記録しますが、beegoが設計するログシステムは比較的ライトウェイトです。システムのlog.Loggerインターフェースを採用し、デフォルトでos.Stdoutに出力します。ユーザはこのインターフェースを実装することで、beego.SetLoggrを通してカスタム定義の出力を設定することができます。詳しい実装を以下に示します:

// Log levels to control the logging output.
const (
    LevelTrace = iota
    LevelDebug
    LevelInfo
    LevelWarning
    LevelError
    LevelCritical
)

// logLevel controls the global log level used by the logger.
var level = LevelTrace

// LogLevel returns the global log level and can be used in
// own implementations of the logger interface.
func Level() int {
    return level
}

// SetLogLevel sets the global log level used by the simple
// logger.
func SetLevel(l int) {
    level = l
}

上ではログシステムのログレベルを実装しています。デフォルトのレベルはTraceで、ユーザはSetLevelによって異なるレベルを設定することができます。

// logger references the used application logger.
var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)

// SetLogger sets a new logger.
func SetLogger(l *log.Logger) {
    BeeLogger = l
}

// Trace logs a message at trace level.
func Trace(v ...interface{}) {
    if level <= LevelTrace {
        BeeLogger.Printf("[T] %v\n", v)
    }
}

// Debug logs a message at debug level.
func Debug(v ...interface{}) {
    if level <= LevelDebug {
        BeeLogger.Printf("[D] %v\n", v)
    }
}

// Info logs a message at info level.
func Info(v ...interface{}) {
    if level <= LevelInfo {
        BeeLogger.Printf("[I] %v\n", v)
    }
}

// Warning logs a message at warning level.
func Warn(v ...interface{}) {
    if level <= LevelWarning {
        BeeLogger.Printf("[W] %v\n", v)
    }
}

// Error logs a message at error level.
func Error(v ...interface{}) {
    if level <= LevelError {
        BeeLogger.Printf("[E] %v\n", v)
    }
}

// Critical logs a message at critical level.
func Critical(v ...interface{}) {
    if level <= LevelCritical {
        BeeLogger.Printf("[C] %v\n", v)
    }
}

上のコードはデフォルトでBeeLoggerオブジェクトを初期化し、デフォルトでos.Stdoutに出力します。ユーザはbeego.SetLoggerによってloggerのインターフェース出力を実装することができます。ここでは6つの関数を実装しています。

  • Trace(一般的な情報の記録、例:)
    • "Entered parse function validation block"
    • "Validation: entered second 'if'"
    • "Dictionary 'Dict' is empty. Using default value"
  • Debug(デバッグ情報、例:)
    • "Web page requested: http://somesite.com Params='...'"
    • "Response generated. Response size: 10000. Sending."
    • "New file received. Type:PNG Size:20000"
  • Info(プリント情報、例:)
    • "Web server restarted"
    • "Hourly statistics: Requested pages: 12345 Errors: 123 ..."
    • "Service paused. Waiting for 'resume' call"
  • Warn(警告情報、例:)
    • "Cache corrupted for file='test.file'. Reading from back-end"
    • "Database 192.168.0.7/DB not responding. Using backup 192.168.0.8/DB"
    • "No response from statistics server. Statistics not sent"
  • Error(エラー情報、例:)
    • "Internal error. Cannot process request #12345 Error:...."
    • "Cannot perform login: credentials DB not responding"
  • Critical(致命的なエラー、例:)
    • "Critical panic received: .... Shutting down"
    • "Fatal error: ... App is shutting down to prevent data corruption or loss"

どの関数にもlevelに対する判断があるのがおわかりいただけるかと思います、ですのでもし我々がデプロイ時にlevel=LevelWarningを設置すると、Trace、Debug、Infoのみっつの関数は何も出力しなくなります。

beegoのコンフィグ設計

設定情報のパースにおいて、beegoはkey=valueの設定ファイルの読み込みを実装しています。ini設定ファイルのフォーマットに似ていて、あるファイルをパースするものです。その後パースしたデータをmapに保存し、最後にコールする際にいくつかのstring、intといった対応する値を関数が返します。具体的な実装は以下を御覧ください:

まずini設定ファイルのグローバルな定数をいくつか定義します:

var (
    bComment = []byte{'#'}
    bEmpty   = []byte{}
    bEqual   = []byte{'='}
    bDQuote  = []byte{'"'}
)

定義された設定ファイルのフォーマット:

// A Config represents the configuration.
type Config struct {
    filename string
    comment  map[int][]string  // id: []{comment, key...}; id 1 is for main comment.
    data     map[string]string // key: value
    offset   map[string]int64  // key: offset; for editing.
    sync.RWMutex
}

パースファイルの関数を定義したら、まずファイルを開きます。その後一行一行読み取り、コメント、空行およびkey=valueのデータをパースします:

// ParseFile creates a new Config and parses the file configuration from the
// named file.
func LoadConfig(name string) (*Config, error) {
    file, err := os.Open(name)
    if err != nil {
        return nil, err
    }

    cfg := &Config{
        file.Name(),
        make(map[int][]string),
        make(map[string]string),
        make(map[string]int64),
        sync.RWMutex{},
    }
    cfg.Lock()
    defer cfg.Unlock()
    defer file.Close()

    var comment bytes.Buffer
    buf := bufio.NewReader(file)

    for nComment, off := 0, int64(1); ; {
        line, _, err := buf.ReadLine()
        if err == io.EOF {
            break
        }
        if bytes.Equal(line, bEmpty) {
            continue
        }

        off += int64(len(line))

        if bytes.HasPrefix(line, bComment) {
            line = bytes.TrimLeft(line, "#")
            line = bytes.TrimLeftFunc(line, unicode.IsSpace)
            comment.Write(line)
            comment.WriteByte('\n')
            continue
        }
        if comment.Len() != 0 {
            cfg.comment[nComment] = []string{comment.String()}
            comment.Reset()
            nComment++
        }

        val := bytes.SplitN(line, bEqual, 2)
        if bytes.HasPrefix(val[1], bDQuote) {
            val[1] = bytes.Trim(val[1], `"`)
        }

        key := strings.TrimSpace(string(val[0]))
        cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key)
        cfg.data[key] = strings.TrimSpace(string(val[1]))
        cfg.offset[key] = off
    }
    return cfg, nil
}

下面实现了一些读取配置文件的函数,返回的值确定为bool、int、float64或string:

// Bool returns the boolean value for a given key.
func (c *Config) Bool(key string) (bool, error) {
    return strconv.ParseBool(c.data[key])
}

// Int returns the integer value for a given key.
func (c *Config) Int(key string) (int, error) {
    return strconv.Atoi(c.data[key])
}

// Float returns the float value for a given key.
func (c *Config) Float(key string) (float64, error) {
    return strconv.ParseFloat(c.data[key], 64)
}

// String returns the string value for a given key.
func (c *Config) String(key string) string {
    return c.data[key]
}

応用

以下の関数はあるアプリケーションでの例です。リモートurlアドレスのjsonデータを取得するのに用います。実装は以下のとおり:

func GetJson() {
    resp, err := http.Get(beego.AppConfig.String("url"))
    if err != nil {
        beego.Critical("http get info error")
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    err = json.Unmarshal(body, &AllInfo)
    if err != nil {
        beego.Critical("error:", err)
    }
}

関数において、フレームワークのログ関数であるbeego.Critical関数をコールすることでエラーを発生させています。beego.AppConfig.String("url")をコールし、設定ファイルの情報を取得します。設定ファイルの情報は以下のとおり(app.conf):

appname = hs
url ="http://www.api.com/api.html"

results matching ""

    No results matching ""