> For the complete documentation index, see [llms.txt](https://willh.gitbook.io/build-web-application-with-golang-zhtw/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://willh.gitbook.io/build-web-application-with-golang-zhtw/13.0/13.4.md).

# 日誌和配置設計

## 日誌和配置的重要性

前面已經介紹過日誌在我們程式開發中起著很重要的作用，透過日誌我們可以記錄除錯我們的資訊，當初介紹過一個日誌系統 seelog，根據不同的 level 輸出不同的日誌，這個對於程式開發和程式部署來說至關重要。我們可以在程式開發中設定 level 低一點，部署的時候把 level 設定高，這樣我們開發中的除錯資訊可以遮蔽掉。

配置模組對於應用部署牽涉到伺服器不同的一些配置資訊非常有用，例如一些資料庫配置資訊、監聽埠、監聽地址等都是可以透過配置檔案來配置，這樣我們的應用程式就具有很強的靈活性，可以透過配置檔案的配置部署在不同的機器上，可以連線不同的資料庫之類別的。

## beego 的日誌設計

beego 的日誌設計部署思路來自於 seelog，根據不同的 level 來記錄日誌，但是 beego 設計的日誌系統比較輕量級，採用了系統的 log.Logger 介面，預設輸出到 os.Stdout，使用者可以實現這個介面然後透過 beego.SetLogger 設定自訂的輸出，詳細的實現如下所示：

```go
// 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 可以設定不同的分級。

```go
// 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 的介面輸出。這裡面實現了六個函式：

* 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 配置檔案的一些全域常數：

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

定義了配置檔案的格式：

```go
// 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 資料：

```go
// 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：

```go
// 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 資料，實現如下：

```go
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)：

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

## links

* [目錄](https://github.com/doggy8088/build-web-application-with-golang-zhtw/tree/4cbbaa31bdfc3678915eb91f23db2f1bad554a20/preface.md)
* 上一章: [controller 設計](/build-web-application-with-golang-zhtw/13.0/13.3.md)
* 下一節: [實現部落格的增刪改](/build-web-application-with-golang-zhtw/13.0/13.5.md)
