基於go開發日誌處理包

最近在本身開發的go語言web框架 Bingo 中須要一個日誌處理功能 , 看了看標準庫的log包, 發現功能過於簡單,因此想從新造個輪子,單獨抽出來做爲一個模塊,輔助框架進行開發git

[bingo-log] 是爲了完成 bingo 的日誌功能而開發的一個第三方包,不依賴框架,可單獨在其餘項目中使用,github

Github地址: bingo-loggolang

安裝和使用在 README.md 中已經寫的很清楚了,這裏再也不贅述,主要記錄開發流程。web

1. 預期效果

我但願這個包包含的功能:框架

  1. 支持多種報錯級別
  2. 日誌自定義配置並自動分割
  3. 可異步輸出日誌

2. 實現思路

準備使該日誌包支持(FATAL,ERROR,WARNING,DEBUG,INFO) 5種報錯級別,異步

寫一個日誌結構體做爲基礎,在其中設置一個接口類型的數據,將容許自定義的方法放在這個接口中,這樣全部實現該接口的對象均可以做爲參數傳入日誌結構體中函數

如何實現異步功能?ui

爲了能夠限制資源消耗,使用協程鏈接池將每一個輸出放入協程池中,達到異步的效果,spa

鏈接池我就不重複造輪子了,使用一個現成的github項目: grpool.net

開始開發

  1. 構建最基礎的底:日誌結構體

首先聲明兩個常量,用來標記同步輸出仍是異步輸出

const (
	LogSyncMode = iota
	LogPoolMode
)
複製代碼

構建結構體

type Log struct {
	Connector                    // 內嵌鏈接器,用來定製化功能
	sync.Mutex
	initialized     bool         // 該日誌對象是否初始化
	mode            int          // 日誌記錄模式 同步記錄 or 協程池記錄
	pool            *grpool.Pool // 協程池
	poolExpiredTime int          // 協程池模式下,每一個空閒協程的存活時間(秒)
	poolWorkerNum   int          // 協程池模式下,容許的最高協程數
}
複製代碼
  1. 構建鏈接器接口

咱們但願使用鏈接器來設定每種輸出,因此這個接口應該實現以下幾種方法

type Connector interface {
	Fatal(message ...interface{})
	Error(message ...interface{})
	Warning(message ...interface{})
	Debug(message ...interface{})
	Info(message ...interface{})                           // 打印
	Output(message string)                                 // 將信息輸出到文件中
	GetMessage(degree int, message ...interface{}) string // 將輸入的信息添加擡頭(例如添加打印時間等)
	GetFile(config map[string]string) *os.File             // 當前日誌要輸出到的文件位置,傳入一個map 表明配置
}
複製代碼

上面5種方法是5種報錯級別要作的事情,主要作的事情,就是將要輸出的日誌,先調用 GetMessage() 將信息進行包裝,包裝成咱們但願的結構,再在控制檯打印輸出,而後再調用Output方法,將日誌打印到日誌文件中一份

Output() 方法中要調用 GetFile() 方法獲得要輸出的文件指針,咱們能夠在GetFile() 方法中設置分割文件的方式,若是須要動態分割,那麼其中的map參數就是外部傳進來的參數

  1. Log 結構體添加方法:
  • 先寫如何建立一個日誌對象:

    func NewLog(mode int) *Log {
      	l := &Log{}
      	l.SetMode(mode)
      	l.initialize()  // 這裏對結構體中的數據作初始化
      	return l
      }
    複製代碼
  • 而後加載鏈接器

    // 加載鏈接器
        func (l *Log) LoadConnector(conn Connector) {
        	l.Connector = conn  // 全部實現了鏈接器接口的對象均可以做爲參數傳入
        }
    複製代碼
  • 而後寫5種報錯級別:

    // 重寫5種日誌級別的打印函數
       func (l *Log) Fatal(message string) {
       	// 根據模式
       	l.exec(l.Connector.Fatal, message)
       }
       
       func (l *Log) Error(message string) {
       	l.exec(l.Connector.Error, message)
       }
       
       func (l *Log) Warning(message string) {
       	l.exec(l.Connector.Warning, message)
       }
       
       func (l *Log) Debug(message string) {
       	l.exec(l.Connector.Debug, message)
       }
       
       func (l *Log) Info(message string) {
       	l.exec(l.Connector.Info, message)
       }
    
    複製代碼
  • 上方的 exec 方法就是根據輸出模式選擇直接輸出,仍是使用協程池輸出:

    func (l *Log) exec(f func(message ...interface{}), message string) {
       	// 同步模式
       	if l.mode == LogSyncMode {
       		l.Lock()
       		defer l.Unlock()
       		f(message)
       	} else if l.mode == LogPoolMode { // 協程池異步模式
       		l.initialize() // 先初始化
       		l.Lock()
       		defer l.Unlock()
       		l.AddWaitCount(1)  // 向池中添加計數器,能夠計算池中有多少協程正在被使用
       		l.pool.JobQueue <- func() {
       			f(message)
       			defer l.pool.JobDone()
       		}
       	}
       }
    複製代碼

從上面的代碼能夠看出,Log 結構體只是負責同步仍是異步執行,最重要的地方是鏈接器Connector, 我實現了兩種ConnectorBaseConnectorKirinConnector)那麼咱們就實現一個基礎鏈接器BaseConnector:

  • 建立一個結構體

    type BaseConnector struct {
      	sync.Mutex  // 這裏是由於有用到map的地方須要加鎖
      }
    複製代碼
  • 實現鏈接器接口:

    1. 先實現GetFile接口,實際就是在當前路徑下建立bingo.log文件,並返回文件指針:
    // 返回一個文件句柄,用來寫入數據
      func (b BaseConnector) GetFile(config map[string]string) *os.File { // 默認狀況下,輸出到當前路徑下的bingo.log文件中
      	dir, err := os.Getwd()
      	if err != nil {
      		panic(err)
      	}
      	path := dir + "/bingo.log" // 真實要保存的文件位置
      	// 判斷文件是否存在
      	if _, err := os.Stat(path); err != nil {
      		// 文件不存在,建立
      		f, err := os.Create(path)
      		//defer f.Close() // 關閉操做要放在調用位置
      		if err != nil {
      			panic(err)
      		}
      		return f
      	}
      	// 打開該文件,追加模式
      	f, err := os.OpenFile(path, os.O_WRONLY, os.ModeAppend)
      
      	if err != nil {
      		panic(err)
      	}
      
      	return f
      }
    複製代碼
    1. 實現Output方法:
    func (b BaseConnector) Output(message string) {
          // 獲取到要輸出的文件路徑
          file := b.GetFile(make(map[string]string))
          defer file.Close()
          n, _ := file.Seek(0, os.SEEK_END)  // 向文件末尾追加數據
          // 寫入數據
          file.WriteAt([]byte(message), n)
      }
    
    複製代碼
    1. 實現GetMessage 方法,這裏是將要輸出的日誌包裝成 指望的格式:
    // 輸出格式爲 [日誌級別][時間][日誌內容] 
          func (b BaseConnector) GetMessage(degree int, message ...interface{}) string {
              var title string
              switch degree {
              case FATAL:
                  title = "[FATAL] "
              case ERROR:
                  title = "[ERROR] "
              case WARNING:
                  title = "[WARNING]"
              case DEBUG:
                  title = "[DEBUG] "
              case INFO:
                  title = "[INFO]"
              default:
                  title = "[UNKNOWN]"
              }
              // 將傳入的信息擴展一下
              // 默認添加當前時間
              return title + "[" + time.Now().Format("2006-01-02 15:04:05") + "] " + fmt.Sprint(message...) + "\n"
          }
    複製代碼
    1. 實現5種日誌級別:
    func (b BaseConnector) Info(message ...interface{}) {
       	// 綠色輸出在控制檯
       	m := b.GetMessage(INFO, message...)
       	fmt.Print(clcolor.Green(m))
       	// 輸出在文件中
       	b.Output(m)
       }
    複製代碼

    爲了在控制檯中達到以不一樣的顏色輸出不一樣級別的日誌,咱們要在打印函數中加上顏色,具體方式在這裏給終端來點彩色(c語言和Golang版)

    我這裏直接使用了一個別人寫好的第三方包xcltapestry/xclpkg

    直接使用clcolor.Green() 便可

    這樣,一個基本的鏈接器就製做好了,咱們能夠隨時自行擴展

小結

使用方式相似於:

log := bingo_log.NewLog(bingo_log.LogSyncMode)

  conn := new(bingo_log.BaseConnector)

  log.LoadConnector(conn)

  log.Info("testing")
  log.Debug("testing")
  log.Warning("testing")
  log.Error("testing")
  log.Fatal("testing")
複製代碼

接口是golang種極其強大的特性,咱們能夠利用接口完成不少動態結構

最後再推薦一下本身的 WEB 框架 Bingo,求 star,求 PR ~~~

相關文章
相關標籤/搜索