package accesslog import ( "bytes" "errors" "io" ) // BackScanner provides an interface to read a file backward line by line. type BackScanner struct { file supportRotate size int64 chunkSize int chunkBuf []byte offset int64 chunk []byte line []byte err error } // NewBackScanner creates a new Scanner to read the file backward. // chunkSize determines the size of each read chunk from the end of the file. func NewBackScanner(file supportRotate, chunkSize int) *BackScanner { size, err := file.Seek(0, io.SeekEnd) if err != nil { return &BackScanner{err: err} } return newBackScanner(file, size, make([]byte, chunkSize)) } func newBackScanner(file supportRotate, fileSize int64, buf []byte) *BackScanner { return &BackScanner{ file: file, size: fileSize, offset: fileSize, chunkSize: len(buf), chunkBuf: buf, } } // Scan advances the scanner to the previous line, which will then be available // via the Bytes method. It returns false when there are no more lines. func (s *BackScanner) Scan() bool { if s.err != nil { return false } // Read chunks until a newline is found or the file is fully read for { // Check if there's a line in the buffer if idx := bytes.LastIndexByte(s.chunk, '\n'); idx >= 0 { s.line = s.chunk[idx+1:] s.chunk = s.chunk[:idx] if len(s.line) > 0 { return true } continue } for { if s.offset <= 0 { // No more data to read; check remaining buffer if len(s.chunk) > 0 { s.line = s.chunk s.chunk = nil return true } return false } newOffset := max(0, s.offset-int64(s.chunkSize)) chunkSize := s.offset - newOffset chunk := s.chunkBuf[:chunkSize] n, err := s.file.ReadAt(chunk, newOffset) if err != nil { if !errors.Is(err, io.EOF) { s.err = err } return false } else if n == 0 { return false } // Prepend the chunk to the buffer clone := append([]byte{}, chunk[:n]...) s.chunk = append(clone, s.chunk...) s.offset = newOffset // Check for newline in the updated buffer if idx := bytes.LastIndexByte(s.chunk, '\n'); idx >= 0 { s.line = s.chunk[idx+1:] s.chunk = s.chunk[:idx] if len(s.line) > 0 { return true } break } } } } // Bytes returns the most recent line generated by a call to Scan. func (s *BackScanner) Bytes() []byte { return s.line } // FileSize returns the size of the file. func (s *BackScanner) FileSize() int64 { return s.size } // Err returns the first non-EOF error encountered by the scanner. func (s *BackScanner) Err() error { return s.err } func (s *BackScanner) Reset() error { _, err := s.file.Seek(0, io.SeekStart) if err != nil { return err } *s = *newBackScanner(s.file, s.size, s.chunkBuf) return nil }