264 lines
8.3 KiB
Markdown
264 lines
8.3 KiB
Markdown
# 13.4 日誌和配置設計
|
||
|
||
## 日誌和配置的重要性
|
||
前面已經介紹過日誌在我們程式開發中起著很重要的作用,透過日誌我們可以記錄除錯我們的資訊,當初介紹過一個日誌系統 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
|
||
* [目錄](<preface.md>)
|
||
* 上一節:[controller 設計](<13.3.md>)
|
||
* 下一節:[實現部落格的增刪改](<13.5.md>)
|