github.com/yinchengtsinghua/golang-Eos-dpos-Ethereum@v0.0.0-20190121132951-92cc4225ed8e/dashboard/log.go (about) 1 2 //此源码被清华学神尹成大魔王专业翻译分析并修改 3 //尹成QQ77025077 4 //尹成微信18510341407 5 //尹成所在QQ群721929980 6 //尹成邮箱 yinc13@mails.tsinghua.edu.cn 7 //尹成毕业于清华大学,微软区块链领域全球最有价值专家 8 //https://mvp.microsoft.com/zh-cn/PublicProfile/4033620 9 //版权所有2018 Go Ethereum作者 10 //此文件是Go以太坊库的一部分。 11 // 12 //Go-Ethereum库是免费软件:您可以重新分发它和/或修改 13 //根据GNU发布的较低通用公共许可证的条款 14 //自由软件基金会,或者许可证的第3版,或者 15 //(由您选择)任何更高版本。 16 // 17 //Go以太坊图书馆的发行目的是希望它会有用, 18 //但没有任何保证;甚至没有 19 //适销性或特定用途的适用性。见 20 //GNU较低的通用公共许可证,了解更多详细信息。 21 // 22 //你应该收到一份GNU较低级别的公共许可证副本 23 //以及Go以太坊图书馆。如果没有,请参见<http://www.gnu.org/licenses/>。 24 25 package dashboard 26 27 import ( 28 "bytes" 29 "encoding/json" 30 "io/ioutil" 31 "os" 32 "path/filepath" 33 "regexp" 34 "sort" 35 "time" 36 37 "github.com/ethereum/go-ethereum/log" 38 "github.com/mohae/deepcopy" 39 "github.com/rjeczalik/notify" 40 ) 41 42 var emptyChunk = json.RawMessage("[]") 43 44 //preplogs从给定的日志记录缓冲区创建一个JSON数组。 45 //返回准备好的数组和最后一个'\n’的位置 46 //原始缓冲区中的字符,如果不包含任何字符,则为-1。 47 func prepLogs(buf []byte) (json.RawMessage, int) { 48 b := make(json.RawMessage, 1, len(buf)+1) 49 b[0] = '[' 50 b = append(b, buf...) 51 last := -1 52 for i := 1; i < len(b); i++ { 53 if b[i] == '\n' { 54 b[i] = ',' 55 last = i 56 } 57 } 58 if last < 0 { 59 return emptyChunk, -1 60 } 61 b[last] = ']' 62 return b[:last+1], last - 1 63 } 64 65 //handleLogRequest搜索由 66 //请求,从中创建一个JSON数组并将其发送到请求客户机。 67 func (db *Dashboard) handleLogRequest(r *LogsRequest, c *client) { 68 files, err := ioutil.ReadDir(db.logdir) 69 if err != nil { 70 log.Warn("Failed to open logdir", "path", db.logdir, "err", err) 71 return 72 } 73 re := regexp.MustCompile(`\.log$`) 74 fileNames := make([]string, 0, len(files)) 75 for _, f := range files { 76 if f.Mode().IsRegular() && re.MatchString(f.Name()) { 77 fileNames = append(fileNames, f.Name()) 78 } 79 } 80 if len(fileNames) < 1 { 81 log.Warn("No log files in logdir", "path", db.logdir) 82 return 83 } 84 idx := sort.Search(len(fileNames), func(idx int) bool { 85 //返回最小的索引,如文件名[idx]>=r.name, 86 //如果没有这样的索引,则返回n。 87 return fileNames[idx] >= r.Name 88 }) 89 90 switch { 91 case idx < 0: 92 return 93 case idx == 0 && r.Past: 94 return 95 case idx >= len(fileNames): 96 return 97 case r.Past: 98 idx-- 99 case idx == len(fileNames)-1 && fileNames[idx] == r.Name: 100 return 101 case idx == len(fileNames)-1 || (idx == len(fileNames)-2 && fileNames[idx] == r.Name): 102 //最后一个文件会不断更新,其块会被流式处理, 103 //因此,为了避免在客户机端复制日志记录,需要 104 //处理方式不同。它的实际内容总是保存在历史记录中。 105 db.lock.Lock() 106 if db.history.Logs != nil { 107 c.msg <- &Message{ 108 Logs: db.history.Logs, 109 } 110 } 111 db.lock.Unlock() 112 return 113 case fileNames[idx] == r.Name: 114 idx++ 115 } 116 117 path := filepath.Join(db.logdir, fileNames[idx]) 118 var buf []byte 119 if buf, err = ioutil.ReadFile(path); err != nil { 120 log.Warn("Failed to read file", "path", path, "err", err) 121 return 122 } 123 chunk, end := prepLogs(buf) 124 if end < 0 { 125 log.Warn("The file doesn't contain valid logs", "path", path) 126 return 127 } 128 c.msg <- &Message{ 129 Logs: &LogsMessage{ 130 Source: &LogFile{ 131 Name: fileNames[idx], 132 Last: r.Past && idx == 0, 133 }, 134 Chunk: chunk, 135 }, 136 } 137 } 138 139 //streamlogs监视文件系统,并在记录器写入时 140 //新的日志记录到文件中,收集它们,然后 141 //从中取出JSON数组并将其发送到客户机。 142 func (db *Dashboard) streamLogs() { 143 defer db.wg.Done() 144 var ( 145 err error 146 errc chan error 147 ) 148 defer func() { 149 if errc == nil { 150 errc = <-db.quit 151 } 152 errc <- err 153 }() 154 155 files, err := ioutil.ReadDir(db.logdir) 156 if err != nil { 157 log.Warn("Failed to open logdir", "path", db.logdir, "err", err) 158 return 159 } 160 var ( 161 opened *os.File //打开的活动日志文件的文件描述符。 162 buf []byte //包含最近写入的日志块,但尚未发送到客户端。 163 ) 164 165 //由于时间戳的存在,日志记录总是按字母顺序写入最后一个文件。 166 re := regexp.MustCompile(`\.log$`) 167 i := len(files) - 1 168 for i >= 0 && (!files[i].Mode().IsRegular() || !re.MatchString(files[i].Name())) { 169 i-- 170 } 171 if i < 0 { 172 log.Warn("No log files in logdir", "path", db.logdir) 173 return 174 } 175 if opened, err = os.OpenFile(filepath.Join(db.logdir, files[i].Name()), os.O_RDONLY, 0600); err != nil { 176 log.Warn("Failed to open file", "name", files[i].Name(), "err", err) 177 return 178 } 179 defer opened.Close() //关闭最后打开的文件。 180 fi, err := opened.Stat() 181 if err != nil { 182 log.Warn("Problem with file", "name", opened.Name(), "err", err) 183 return 184 } 185 db.lock.Lock() 186 db.history.Logs = &LogsMessage{ 187 Source: &LogFile{ 188 Name: fi.Name(), 189 Last: true, 190 }, 191 Chunk: emptyChunk, 192 } 193 db.lock.Unlock() 194 195 watcher := make(chan notify.EventInfo, 10) 196 if err := notify.Watch(db.logdir, watcher, notify.Create); err != nil { 197 log.Warn("Failed to create file system watcher", "err", err) 198 return 199 } 200 defer notify.Stop(watcher) 201 202 ticker := time.NewTicker(db.config.Refresh) 203 defer ticker.Stop() 204 205 loop: 206 for err == nil || errc == nil { 207 select { 208 case event := <-watcher: 209 //确保创建了新的日志文件。 210 if !re.Match([]byte(event.Path())) { 211 break 212 } 213 if opened == nil { 214 log.Warn("The last log file is not opened") 215 break loop 216 } 217 //新日志文件的名称总是更大, 218 //因为它是使用实际日志记录的时间创建的。 219 if opened.Name() >= event.Path() { 220 break 221 } 222 //读取以前打开的文件的其余部分。 223 chunk, err := ioutil.ReadAll(opened) 224 if err != nil { 225 log.Warn("Failed to read file", "name", opened.Name(), "err", err) 226 break loop 227 } 228 buf = append(buf, chunk...) 229 opened.Close() 230 231 if chunk, last := prepLogs(buf); last >= 0 { 232 //发送以前打开的文件的其余部分。 233 db.sendToAll(&Message{ 234 Logs: &LogsMessage{ 235 Chunk: chunk, 236 }, 237 }) 238 } 239 if opened, err = os.OpenFile(event.Path(), os.O_RDONLY, 0644); err != nil { 240 log.Warn("Failed to open file", "name", event.Path(), "err", err) 241 break loop 242 } 243 buf = buf[:0] 244 245 //更改历史记录中的最后一个文件。 246 fi, err := opened.Stat() 247 if err != nil { 248 log.Warn("Problem with file", "name", opened.Name(), "err", err) 249 break loop 250 } 251 db.lock.Lock() 252 db.history.Logs.Source.Name = fi.Name() 253 db.history.Logs.Chunk = emptyChunk 254 db.lock.Unlock() 255 case <-ticker.C: //向客户端发送日志更新。 256 if opened == nil { 257 log.Warn("The last log file is not opened") 258 break loop 259 } 260 //读取自上次读取以来创建的新日志。 261 chunk, err := ioutil.ReadAll(opened) 262 if err != nil { 263 log.Warn("Failed to read file", "name", opened.Name(), "err", err) 264 break loop 265 } 266 b := append(buf, chunk...) 267 268 chunk, last := prepLogs(b) 269 if last < 0 { 270 break 271 } 272 //只保留缓冲区的无效部分,该部分在下次读取后才有效。 273 buf = b[last+1:] 274 275 var l *LogsMessage 276 //更新历史记录。 277 db.lock.Lock() 278 if bytes.Equal(db.history.Logs.Chunk, emptyChunk) { 279 db.history.Logs.Chunk = chunk 280 l = deepcopy.Copy(db.history.Logs).(*LogsMessage) 281 } else { 282 b = make([]byte, len(db.history.Logs.Chunk)+len(chunk)-1) 283 copy(b, db.history.Logs.Chunk) 284 b[len(db.history.Logs.Chunk)-1] = ',' 285 copy(b[len(db.history.Logs.Chunk):], chunk[1:]) 286 db.history.Logs.Chunk = b 287 l = &LogsMessage{Chunk: chunk} 288 } 289 db.lock.Unlock() 290 291 db.sendToAll(&Message{Logs: l}) 292 case errc = <-db.quit: 293 break loop 294 } 295 } 296 }