一個日誌檢視功能實現-seelog原始碼閱讀
最近被後臺日誌弄的很煩,看到有個專案簡簡單單,又能滿足需要,順便試下看看效果,做下記錄。只是記錄下一部分內容,就不全部讀了,關於原始碼可以去https://github.com/xmge/seelog。
結構設計
// websocket客戶端 type client struct { id string socket *websocket.Conn send chan []byte } // 客戶端管理 type clientManager struct { clients map[*client]bool broadcast chan []byte register chan *client unregister chan *client }
WebSocket 是 HTML5 開始提供的一種在單個 TCP 連線上進行全雙工通訊的協議。
WebSocket 使得客戶端和伺服器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料。在 WebSocket API 中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。
在 WebSocket API 中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。
程式使用管道作為通訊基礎
- clients 用來儲存當前全部的Websocket
- broadcast 作為廣播使用的管道,當收到訊息,向所有的clients中的websocket進行傳輸資訊
- register 當新的連結建立,將client指標放入註冊管道
- unregister 當連結斷開,將斷開的連結物件放入取消管道
- client結構體內的send管道,當broadcast收到,將資訊發到每個client的send管道中
func (manager *clientManager) start() { defer func() { if err := recover(); err != nil { log.Printf("[seelog] error:%+v", err) } }() for { select { case conn := <-manager.register: manager.clients[conn] = true case conn := <-manager.unregister: if _, ok := manager.clients[conn]; ok { close(conn.send) delete(manager.clients, conn) } case message := <-manager.broadcast: for conn := range manager.clients { select { case conn.send <- message: default: close(conn.send) delete(manager.clients, conn) } } } } }
使用select-case進行管道的資料處理,外部加一個for迴圈保持輪詢的狀態。
func (c *client) write() {
defer func() {
manager.unregister <- c
c.socket.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
c.socket.WriteClose(1)
return
}
c.socket.Write(message)
}
}
}
這個是在每個websocket啟動的時候使用,每個socket保持一個for迴圈,使用defer用於關閉操作,當for被打斷(即關閉網頁之類的操作),socket被關閉,則會插入到取消管道中,clients鍵值對會刪除這個連線的資訊。
監控檔案的內容變化
通過os.Stat獲取檔案資訊,返回值為fileInfo的介面
fileInfo, err = os.Stat(filePath)
func (f *File) Stat() (FileInfo, error)
type FileInfo interface {
Name() string // base name of the file 檔名
Size() int64 // length in bytes for regular files; system-dependent for others 檔案大小(byte長度)
Mode() FileMode // file mode bits 檔案模式(只讀、只寫之類)
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir() 是否是目錄
Sys() interface{} // underlying data source (can return nil) 基礎資料來源(可以返回nil)
}
獲取當前的檔案的截止位置
offset := fileInfo.Size()
獲取新的檔案大小,然後根據檔案大小和之前的區別,構建一個新的byte陣列,大小為新的位元組數減去舊的位元組數
msg := make([]byte, newOffset-offset)
使用Open方法開啟一個檔案,Open方法是以只讀的方式讀取資料
file, err := os.Open(filePath)
func Open(name string) (*File, error)
可以將檔案讀取的起點設定到某個位置,在seelog中,將讀取起點設定到檔案末尾,當檔案的大小發生變化,則檔案從上個起點開始讀取檔案內容
_, err = file.Seek(offset, 0)
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
whence 存在3個引數
0:檔案頭的絕對位置偏移offset的距離
1:檔案的相對位置,即當前位置偏移offset的距離
2:檔案末尾的絕對位置偏移offset的距離
這個特性當檔案以O_APPEND的模式開啟是沒有效果的
msg是之前構造的位元組陣列,將新增的內容讀取到位元組陣列中
_, err = file.Read(msg)
使用管道作為訊息傳輸的方式,manager在這裡是一個全域性的manager,當管道收到訊息,就列印處理
manager.broadcast <- msg
最後記得將檔案關閉,否則下次開啟會出錯
file.Close()