github.com/avicd/go-utilx@v0.1.0/logx/file.go (about)

     1  package logx
     2  
     3  import (
     4  	"github.com/avicd/go-utilx/conv"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  const dayMilliSecs = 86400000
    14  const Levels = 6
    15  
    16  type Split = [Levels]bool
    17  
    18  type RollCycle int
    19  
    20  const (
    21  	Day RollCycle = iota
    22  	Minute
    23  	Hour
    24  	Week
    25  	Month
    26  	Year
    27  	Second
    28  )
    29  
    30  var Layouts = []string{
    31  	Year:   "2006",
    32  	Month:  "2006-01",
    33  	Day:    "2006-01-02",
    34  	Hour:   "2006-01-02 15:00",
    35  	Minute: "2006-01-02 15:04",
    36  	Second: "2006-01-02 15:04:05",
    37  }
    38  
    39  type FileAppender struct {
    40  	loggers    [Levels]*log.Logger
    41  	files      [Levels]*os.File
    42  	cycleIds   [Levels]int
    43  	mutex      [Levels]*sync.Mutex
    44  	locker     sync.Mutex
    45  	TimeLayout string      // layout for time formatting
    46  	MaxSize    int64       // max size of log file
    47  	Name       string      // name of log file
    48  	FileFlag   int         // FileFlag to open log file
    49  	FileMode   os.FileMode // FileMode to open log file
    50  	Prefix     string      // prefix of messages
    51  	CycleOff   bool        // do not use the period cycle
    52  	Cycle      RollCycle   // rolling cycle
    53  	OutDir     string      // output directory cycle
    54  	Split      Split       // split different level into different log file
    55  }
    56  
    57  func (it *FileAppender) getKey(level Level) Level {
    58  	if it.Split[level] {
    59  		return level
    60  	}
    61  	return ALL
    62  }
    63  
    64  func (it *FileAppender) getCycleId() (int, time.Time) {
    65  	timeRef := time.Now()
    66  	var id int
    67  	switch it.Cycle {
    68  	case Second:
    69  		id = timeRef.Second()
    70  	case Minute:
    71  		id = timeRef.Minute()
    72  	case Hour:
    73  		id = timeRef.Hour()
    74  	case Day:
    75  		id = timeRef.Day()
    76  	case Week:
    77  		_, id = timeRef.ISOWeek()
    78  	case Month:
    79  		id = int(timeRef.Month())
    80  	case Year:
    81  		id = timeRef.Year()
    82  	}
    83  	return id, timeRef
    84  }
    85  
    86  func (it *FileAppender) init(key Level) {
    87  	if it.mutex[key] == nil {
    88  		it.locker.Lock()
    89  		if it.mutex[key] == nil {
    90  			it.mutex[key] = &sync.Mutex{}
    91  		}
    92  		it.locker.Unlock()
    93  	}
    94  }
    95  
    96  func (it *FileAppender) sizeOver(file *os.File) bool {
    97  	if it.MaxSize > 0 {
    98  		info, _ := file.Stat()
    99  		if info.Size() > it.MaxSize {
   100  			return true
   101  		}
   102  	}
   103  	return false
   104  }
   105  
   106  func (it *FileAppender) fileName(level Level) string {
   107  	name := it.Name
   108  	if name == "" {
   109  		exeFile, _ := os.Executable()
   110  		name = filepath.Base(exeFile)
   111  		name = strings.TrimSuffix(name, filepath.Ext(name))
   112  		it.Name = name
   113  	}
   114  	if it.Split[level] {
   115  		name = conv.Append(name, strings.ToLower(Labels[level]), "_")
   116  	}
   117  	return name
   118  }
   119  
   120  func (it *FileAppender) rollLogFile(level Level) {
   121  	key := it.getKey(level)
   122  	var shortName string
   123  	var cycleId int
   124  	logFile := it.files[key]
   125  	if !it.CycleOff {
   126  		var timeRef time.Time
   127  		cycleId, timeRef = it.getCycleId()
   128  		if logFile != nil {
   129  			if it.cycleIds[key] == cycleId {
   130  				if !it.sizeOver(it.files[key]) {
   131  					return
   132  				}
   133  			}
   134  		}
   135  		var cycleName string
   136  		if it.Cycle == Week {
   137  			year := time.Date(timeRef.Year(), 1, 1, 0, 0, 0, 0, time.Local)
   138  			start := time.UnixMilli(year.UnixMilli() + int64(cycleId-1)*7*dayMilliSecs)
   139  			end := time.UnixMilli(year.UnixMilli() + (int64(cycleId)*7-1)*dayMilliSecs)
   140  			cycleName = start.Format(Layouts[Day]) + "--" + end.Format(Layouts[Day])
   141  		} else {
   142  			cycleName = timeRef.Format(Layouts[it.Cycle])
   143  		}
   144  		shortName = conv.Append(it.fileName(level), cycleName, "_")
   145  	} else if logFile != nil && !it.sizeOver(logFile) {
   146  		return
   147  	} else {
   148  		shortName = it.fileName(level)
   149  	}
   150  	it.mutex[key].Lock()
   151  	if it.files[key] == logFile {
   152  		if logFile != nil {
   153  			logFile.Close()
   154  		}
   155  	} else {
   156  		it.mutex[key].Unlock()
   157  		return
   158  	}
   159  	index := 0
   160  	fileFlag := it.FileFlag
   161  	if fileFlag == 0 {
   162  		fileFlag = os.O_WRONLY | os.O_APPEND | os.O_CREATE
   163  	}
   164  	fileMode := it.FileMode
   165  	if fileMode == 0 {
   166  		fileMode = 0666
   167  	}
   168  	for {
   169  		fileName := filepath.Join(it.OutDir, shortName)
   170  		if index > 0 {
   171  			fileName = conv.Append(fileName, index, "_")
   172  		}
   173  		fileName += ".log"
   174  		index++
   175  		file, err := os.OpenFile(fileName, fileFlag, fileMode)
   176  		if err != nil {
   177  			panic(err)
   178  		}
   179  		if it.sizeOver(file) {
   180  			continue
   181  		}
   182  		it.files[key] = file
   183  		if !it.CycleOff {
   184  			it.cycleIds[key] = cycleId
   185  		}
   186  		break
   187  	}
   188  	it.loggers[key] = log.New(it.files[key], it.Prefix, 0)
   189  	it.mutex[key].Unlock()
   190  }
   191  
   192  func (it *FileAppender) Write(level Level, args ...any) {
   193  	key := it.getKey(level)
   194  	it.init(key)
   195  	it.rollLogFile(level)
   196  	var dest []any
   197  	dest = append(dest, timeNow(it.TimeLayout))
   198  	label := Labels[level] + " "
   199  	dest = append(dest, label)
   200  	dest = append(dest, args...)
   201  	it.loggers[key].Print(dest...)
   202  }