1. 程式人生 > >一個日誌檢視功能實現-seelog原始碼閱讀

一個日誌檢視功能實現-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 中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。

程式使用管道作為通訊基礎

  1. clients 用來儲存當前全部的Websocket
  2. broadcast 作為廣播使用的管道,當收到訊息,向所有的clients中的websocket進行傳輸資訊
  3. register 當新的連結建立,將client指標放入註冊管道
  4. unregister 當連結斷開,將斷開的連結物件放入取消管道
  5. 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()