123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- package sshlog
- import (
- "bufio"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "sync"
- "time"
- "github.com/cloudflare/cloudflared/logger"
- )
- const (
- logTimeFormat = "2006-01-02T15-04-05.000"
- megabyte = 1024 * 1024
- defaultFileSizeLimit = 100 * megabyte
- )
- // Logger will buffer and write events to disk
- type Logger struct {
- sync.Mutex
- filename string
- file *os.File
- writeBuffer *bufio.Writer
- logger logger.Service
- flushInterval time.Duration
- maxFileSize int64
- done chan struct{}
- once sync.Once
- }
- // NewLogger creates a Logger instance. A buffer is created that needs to be
- // drained and closed when the caller is finished, so instances should call
- // Close when finished with this Logger instance. Writes will be flushed to disk
- // every second (fsync). filename is the name of the logfile to be created. The
- // logger variable is a logger service that will log all i/o, filesystem error etc, that
- // that shouldn't end execution of the logger, but are useful to report to the
- // caller.
- func NewLogger(filename string, logger logger.Service, flushInterval time.Duration, maxFileSize int64) (*Logger, error) {
- if logger == nil {
- return nil, errors.New("logger can't be nil")
- }
- f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
- if err != nil {
- return nil, err
- }
- l := &Logger{filename: filename,
- file: f,
- writeBuffer: bufio.NewWriter(f),
- logger: logger,
- flushInterval: flushInterval,
- maxFileSize: maxFileSize,
- done: make(chan struct{}),
- }
- go l.writer()
- return l, nil
- }
- // Writes to a log buffer. Implements the io.Writer interface.
- func (l *Logger) Write(p []byte) (n int, err error) {
- l.Lock()
- defer l.Unlock()
- return l.writeBuffer.Write(p)
- }
- // Close drains anything left in the buffer and cleans up any resources still
- // in use.
- func (l *Logger) Close() error {
- l.once.Do(func() {
- close(l.done)
- })
- if err := l.write(); err != nil {
- return err
- }
- return l.file.Close()
- }
- // writer is the run loop that handles draining the write buffer and syncing
- // data to disk.
- func (l *Logger) writer() {
- ticker := time.NewTicker(l.flushInterval)
- defer ticker.Stop()
- for {
- select {
- case <-ticker.C:
- if err := l.write(); err != nil {
- l.logger.Errorf("%s", err)
- }
- case <-l.done:
- return
- }
- }
- }
- // write does the actual system write calls to disk and does a rotation if the
- // file size limit has been reached. Since the rotation happens at the end,
- // the rotation is a soft limit (aka the file can be bigger than the max limit
- // because of the final buffer flush)
- func (l *Logger) write() error {
- l.Lock()
- defer l.Unlock()
- if l.writeBuffer.Buffered() <= 0 {
- return nil
- }
- if err := l.writeBuffer.Flush(); err != nil {
- return err
- }
- if err := l.file.Sync(); err != nil {
- return err
- }
- if l.shouldRotate() {
- return l.rotate()
- }
- return nil
- }
- // shouldRotate checks to see if the current file should be rotated to a new
- // logfile.
- func (l *Logger) shouldRotate() bool {
- info, err := l.file.Stat()
- if err != nil {
- return false
- }
- return info.Size() >= l.maxFileSize
- }
- // rotate creates a new logfile with the existing filename and renames the
- // existing file with a current timestamp.
- func (l *Logger) rotate() error {
- if err := l.file.Close(); err != nil {
- return err
- }
- // move the existing file
- newname := rotationName(l.filename)
- if err := os.Rename(l.filename, newname); err != nil {
- return fmt.Errorf("can't rename log file: %s", err)
- }
- f, err := os.OpenFile(l.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
- if err != nil {
- return fmt.Errorf("failed to open new logfile %s", err)
- }
- l.file = f
- l.writeBuffer = bufio.NewWriter(f)
- return nil
- }
- // rotationName creates a new filename from the given name, inserting a timestamp
- // between the filename and the extension.
- func rotationName(name string) string {
- dir := filepath.Dir(name)
- filename := filepath.Base(name)
- ext := filepath.Ext(filename)
- prefix := filename[:len(filename)-len(ext)]
- t := time.Now()
- timestamp := t.Format(logTimeFormat)
- return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext))
- }
|