# 13.4 日志和配置设计

## 日志和配置的重要性
前面已经介绍过日志在我们程序开发中起着很重要的作用，通过日志我们可以记录调试我们的信息，当初介绍过一个日志系统seelog，根据不同的level输出不同的日志，这个对于程序开发和程序部署来说至关重要。我们可以在程序开发中设置level低一点，部署的时候把level设置高，这样我们开发中的调试信息可以屏蔽掉。

配置模块对于应用部署牵涉到服务器不同的一些配置信息非常有用，例如一些数据库配置信息、监听端口、监听地址等都是可以通过配置文件来配置，这样我们的应用程序就具有很强的灵活性，可以通过配置文件的配置部署在不同的机器上，可以连接不同的数据库之类的。

## beego的日志设计
beego的日志设计部署思路来自于seelog，根据不同的level来记录日志，但是beego设计的日志系统比较轻量级，采用了系统的log.Logger接口，默认输出到os.Stdout,用户可以实现这个接口然后通过beego.SetLogger设置自定义的输出，详细的实现如下所示：

	
	// 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的接口输出。这里面实现了六个函数：

- 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"
	

## links
   * [目录](<preface.md>)
   * 上一章: [controller设计](<13.3.md>)
   * 下一节: [实现博客的增删改](<13.5.md>)