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 }