github.com/xwi88/log4go@v0.0.6/file_writer.go (about)

     1  package log4go
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"log"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  )
    17  
    18  var pathVariableTable map[byte]func(*time.Time) int
    19  
    20  // FileWriter file writer for log record deal
    21  type FileWriter struct {
    22  	// write log order by order and atomic incr
    23  	// maxLinesCurLines and maxSizeCurSize
    24  	level        int
    25  	lock         sync.RWMutex
    26  	initFileOnce sync.Once // init once
    27  
    28  	rotatePerm os.FileMode // real used
    29  	perm       string      // input
    30  	// input filename
    31  	filename string
    32  	// The opened file
    33  	file          *os.File
    34  	fileBufWriter *bufio.Writer
    35  	// like "xwi88.log", xwi88 is filenameOnly and .log is suffix
    36  	filenameOnly, suffix string
    37  
    38  	pathFmt   string // Rotate when, use actions
    39  	actions   []func(*time.Time) int
    40  	variables []interface{}
    41  
    42  	// // Rotate at file lines
    43  	// maxLines         int // Rotate at line
    44  	// maxLinesCurLines int
    45  
    46  	// // Rotate at size
    47  	// maxSize        int
    48  	// maxSizeCurSize int
    49  
    50  	lastWriteTime time.Time
    51  
    52  	initFileOk bool
    53  	rotate     bool
    54  	// Rotate daily
    55  	daily bool
    56  	// Rotate hourly
    57  	hourly bool
    58  	// Rotate minutely
    59  	minutely bool
    60  
    61  	maxDays       int
    62  	dailyOpenDate int
    63  	dailyOpenTime time.Time
    64  
    65  	// Rotate hourly
    66  	maxHours       int
    67  	hourlyOpenDate int
    68  	hourlyOpenTime time.Time
    69  
    70  	// Rotate minutely
    71  	maxMinutes       int
    72  	minutelyOpenDate int
    73  	minutelyOpenTime time.Time
    74  }
    75  
    76  // FileWriterOptions file writer options
    77  type FileWriterOptions struct {
    78  	Level    string `json:"level" mapstructure:"level"`
    79  	Filename string `json:"filename" mapstructure:"filename"`
    80  	Enable   bool   `json:"enable" mapstructure:"enable"`
    81  
    82  	Rotate bool `json:"rotate" mapstructure:"rotate"`
    83  	// Rotate daily
    84  	Daily bool `json:"daily" mapstructure:"daily"`
    85  	// Rotate hourly
    86  	Hourly bool `json:"hourly" mapstructure:"hourly"`
    87  	// Rotate minutely
    88  	Minutely bool `json:"minutely" mapstructure:"minutely"`
    89  
    90  	MaxDays    int `json:"max_days" mapstructure:"max_days"`
    91  	MaxHours   int `json:"max_hours" mapstructure:"max_hours"`
    92  	MaxMinutes int `json:"max_minutes" mapstructure:"max_minutes"`
    93  }
    94  
    95  // NewFileWriter create new file writer
    96  func NewFileWriter() *FileWriter {
    97  	return &FileWriter{}
    98  }
    99  
   100  // NewFileWriterWithOptions create new file writer with options
   101  func NewFileWriterWithOptions(options FileWriterOptions) *FileWriter {
   102  	defaultLevel := DEBUG
   103  	if len(options.Level) > 0 {
   104  		defaultLevel = getLevelDefault(options.Level, defaultLevel, "")
   105  	}
   106  	fileWriter := &FileWriter{
   107  		level:      defaultLevel,
   108  		filename:   options.Filename,
   109  		rotate:     options.Rotate,
   110  		daily:      options.Daily,
   111  		maxDays:    options.MaxDays,
   112  		hourly:     options.Hourly,
   113  		maxHours:   options.MaxHours,
   114  		minutely:   options.Minutely,
   115  		maxMinutes: options.MaxMinutes,
   116  	}
   117  	if err := fileWriter.SetPathPattern(options.Filename); err != nil {
   118  		log.Printf("[log4go] file writer init err: %v", err.Error())
   119  	}
   120  	return fileWriter
   121  }
   122  
   123  // Write file write
   124  func (w *FileWriter) Write(r *Record) error {
   125  	if r.level > w.level {
   126  		return nil
   127  	}
   128  	if w.fileBufWriter == nil {
   129  		return errors.New("fileWriter no opened file: " + w.filename)
   130  	}
   131  	_, err := w.fileBufWriter.WriteString(r.String())
   132  	return err
   133  }
   134  
   135  // Init file writer init
   136  func (w *FileWriter) Init() error {
   137  	filename := w.filename
   138  	defaultPerm := "0755"
   139  	if len(filename) != 0 {
   140  		w.suffix = filepath.Ext(filename)
   141  		w.filenameOnly = strings.TrimSuffix(filename, w.suffix)
   142  		w.filename = filename
   143  		if w.suffix == "" {
   144  			w.suffix = ".log"
   145  		}
   146  	}
   147  	if w.perm == "" {
   148  		w.perm = defaultPerm
   149  	}
   150  
   151  	perm, err := strconv.ParseInt(w.perm, 8, 64)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	w.rotatePerm = os.FileMode(perm)
   156  
   157  	if w.rotate {
   158  		if w.daily && w.maxDays <= 0 {
   159  			w.maxDays = 60
   160  		}
   161  		if w.hourly && w.maxHours <= 0 {
   162  			w.maxHours = 12
   163  		}
   164  		if w.minutely && w.maxMinutes <= 0 {
   165  			w.maxMinutes = 1
   166  		}
   167  	}
   168  
   169  	return w.Rotate()
   170  }
   171  
   172  // Flush writes any buffered data to file
   173  func (w *FileWriter) Flush() error {
   174  	if w.fileBufWriter != nil {
   175  		return w.fileBufWriter.Flush()
   176  	}
   177  	return nil
   178  }
   179  
   180  // SetPathPattern for file writer
   181  func (w *FileWriter) SetPathPattern(pattern string) error {
   182  	n := 0
   183  	for _, c := range pattern {
   184  		if c == '%' {
   185  			n++
   186  		}
   187  	}
   188  
   189  	if n == 0 {
   190  		w.pathFmt = pattern
   191  		return nil
   192  	}
   193  
   194  	w.actions = make([]func(*time.Time) int, 0, n)
   195  	w.variables = make([]interface{}, n, n)
   196  	tmp := []byte(pattern)
   197  
   198  	variable := 0
   199  	for _, c := range tmp {
   200  		if variable == 1 {
   201  			act, ok := pathVariableTable[c]
   202  			if !ok {
   203  				return errors.New("invalid rotate pattern (" + pattern + ")")
   204  			}
   205  			w.actions = append(w.actions, act)
   206  			variable = 0
   207  			continue
   208  		}
   209  		if c == '%' {
   210  			variable = 1
   211  		}
   212  	}
   213  
   214  	w.pathFmt = convertPatternToFmt(tmp)
   215  
   216  	return nil
   217  }
   218  
   219  func (w *FileWriter) initFile() {
   220  	w.lock.Lock()
   221  	defer w.lock.Unlock()
   222  	w.initFileOk = true
   223  }
   224  
   225  // Rotate file writer rotate
   226  func (w *FileWriter) Rotate() error {
   227  	now := time.Now()
   228  	v := 0
   229  	rotate := false
   230  	for i, act := range w.actions {
   231  		v = act(&now)
   232  		if v != w.variables[i] {
   233  			if !w.initFileOk {
   234  				w.variables[i] = v
   235  				rotate = true
   236  			} else {
   237  				// only exec except the first round
   238  				switch i {
   239  				case 2:
   240  					if w.daily {
   241  						w.dailyOpenDate = v
   242  						w.dailyOpenTime = now
   243  						_, _, d := w.lastWriteTime.AddDate(0, 0, w.maxDays).Date()
   244  						if v == d {
   245  							rotate = true
   246  							w.variables[i] = v
   247  						}
   248  					}
   249  				case 3:
   250  					if w.hourly {
   251  						w.hourlyOpenDate = v
   252  						w.hourlyOpenTime = now
   253  						h := w.lastWriteTime.Add(time.Hour * time.Duration(w.maxHours)).Hour()
   254  						if v == h {
   255  							rotate = true
   256  							w.variables[i] = v
   257  						}
   258  					}
   259  				case 4:
   260  					if w.minutely {
   261  						w.minutelyOpenDate = v
   262  						w.minutelyOpenTime = now
   263  						m := w.lastWriteTime.Add(time.Minute * time.Duration(w.maxMinutes)).Minute()
   264  						if v == m {
   265  							rotate = true
   266  							w.variables[i] = v
   267  						}
   268  					}
   269  				}
   270  			}
   271  		}
   272  	}
   273  	// must init file first!
   274  	if rotate == false {
   275  		return nil
   276  	}
   277  	w.initFileOnce.Do(w.initFile)
   278  	w.lastWriteTime = now
   279  
   280  	if w.fileBufWriter != nil {
   281  		if err := w.fileBufWriter.Flush(); err != nil {
   282  			return err
   283  		}
   284  	}
   285  
   286  	if w.file != nil {
   287  		if err := w.file.Close(); err != nil {
   288  			return err
   289  		}
   290  	}
   291  
   292  	filePath := fmt.Sprintf(w.pathFmt, w.variables...)
   293  
   294  	if err := os.MkdirAll(path.Dir(filePath), w.rotatePerm); err != nil {
   295  		if !os.IsExist(err) {
   296  			return err
   297  		}
   298  	}
   299  
   300  	if file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, w.rotatePerm); err == nil {
   301  		w.file = file
   302  	} else {
   303  		return err
   304  	}
   305  
   306  	if w.fileBufWriter = bufio.NewWriterSize(w.file, 8192); w.fileBufWriter == nil {
   307  		return errors.New("fileWriter new fileBufWriter failed")
   308  	}
   309  	w.suffix = filepath.Ext(filePath)
   310  	w.filenameOnly = strings.TrimSuffix(filePath, w.suffix)
   311  	return nil
   312  }
   313  
   314  func getYear(now *time.Time) int {
   315  	return now.Year()
   316  }
   317  
   318  func getMonth(now *time.Time) int {
   319  	return int(now.Month())
   320  }
   321  
   322  func getDay(now *time.Time) int {
   323  	return now.Day()
   324  }
   325  
   326  func getHour(now *time.Time) int {
   327  	return now.Hour()
   328  }
   329  
   330  func getMin(now *time.Time) int {
   331  	return now.Minute()
   332  }
   333  
   334  func convertPatternToFmt(pattern []byte) string {
   335  	pattern = bytes.Replace(pattern, []byte("%Y"), []byte("%d"), -1)
   336  	pattern = bytes.Replace(pattern, []byte("%M"), []byte("%02d"), -1)
   337  	pattern = bytes.Replace(pattern, []byte("%D"), []byte("%02d"), -1)
   338  	pattern = bytes.Replace(pattern, []byte("%H"), []byte("%02d"), -1)
   339  	pattern = bytes.Replace(pattern, []byte("%m"), []byte("%02d"), -1)
   340  	return string(pattern)
   341  }
   342  
   343  func init() {
   344  	pathVariableTable = make(map[byte]func(*time.Time) int, 5)
   345  	pathVariableTable['Y'] = getYear
   346  	pathVariableTable['M'] = getMonth
   347  	pathVariableTable['D'] = getDay
   348  	pathVariableTable['H'] = getHour
   349  	pathVariableTable['m'] = getMin
   350  }