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  }