pkg.re/essentialkaos/ek.v11@v12.41.0+incompatible/log/log.go (about) 1 // Package log provides an improved logger 2 package log 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "bufio" 13 "errors" 14 "fmt" 15 "io" 16 "os" 17 "strings" 18 "sync" 19 "time" 20 21 "pkg.re/essentialkaos/ek.v12/fmtc" 22 ) 23 24 // ////////////////////////////////////////////////////////////////////////////////// // 25 26 const ( 27 DEBUG uint8 = 0 // DEBUG debug messages 28 INFO = 1 // INFO info messages 29 WARN = 2 // WARN warning messages 30 ERROR = 3 // ERROR error messages 31 CRIT = 4 // CRIT critical error messages 32 AUX = 99 // AUX unskipable messages (separators, headers, etc...) 33 ) 34 35 // ////////////////////////////////////////////////////////////////////////////////// // 36 37 // Logger is a basic logger struct 38 type Logger struct { 39 PrefixDebug bool // Prefix for debug messages 40 PrefixInfo bool // Prefix for info messages 41 PrefixWarn bool // Prefix for warning messages 42 PrefixError bool // Prefix for error messages 43 PrefixCrit bool // Prefix for critical error messages 44 45 UseColors bool // Enable ANSI escape codes for colors in output 46 47 file string 48 fd *os.File 49 w *bufio.Writer 50 mu *sync.Mutex 51 minLevel uint8 52 perms os.FileMode 53 useBufIO bool 54 } 55 56 // ////////////////////////////////////////////////////////////////////////////////// // 57 58 // Global is global logger struct 59 var Global = &Logger{ 60 PrefixWarn: true, 61 PrefixError: true, 62 PrefixCrit: true, 63 64 minLevel: INFO, 65 mu: &sync.Mutex{}, 66 } 67 68 // ////////////////////////////////////////////////////////////////////////////////// // 69 70 // PrefixMap is map with messages prefixes 71 var PrefixMap = map[uint8]string{ 72 DEBUG: "[DEBUG]", 73 INFO: "[INFO]", 74 WARN: "[WARNING]", 75 ERROR: "[ERROR]", 76 CRIT: "[CRITICAL]", 77 } 78 79 // Colors colors is map with fmtc color tags for every level 80 var Colors = map[uint8]string{ 81 DEBUG: "{s-}", 82 INFO: "", 83 WARN: "{y}", 84 ERROR: "{r}", 85 CRIT: "{m}", 86 } 87 88 // TimeFormat contains format string for time in logs 89 var TimeFormat = "2006/01/02 15:04:05.000" 90 91 // ////////////////////////////////////////////////////////////////////////////////// // 92 93 // Errors 94 var ( 95 // ErrLoggerIsNil is returned by Logger struct methods if struct is nil 96 ErrLoggerIsNil = errors.New("Logger is nil or not created properly") 97 98 // ErrUnexpectedLevel is returned by the MinLevel method if given level is unknown 99 ErrUnexpectedLevel = errors.New("Unexpected level type") 100 101 // ErrOutputNotSet is returned by the Reopen method if output file is not set 102 ErrOutputNotSet = errors.New("Output file is not set") 103 ) 104 105 // ////////////////////////////////////////////////////////////////////////////////// // 106 107 var logLevelsNames = map[string]uint8{ 108 "debug": 0, 109 "info": 1, 110 "warn": 2, 111 "warning": 2, 112 "error": 3, 113 "crit": 4, 114 "critical": 4, 115 } 116 117 // ////////////////////////////////////////////////////////////////////////////////// // 118 119 // New creates new logger struct 120 func New(file string, perms os.FileMode) (*Logger, error) { 121 logger := &Logger{ 122 PrefixWarn: true, 123 PrefixCrit: true, 124 PrefixError: true, 125 126 minLevel: INFO, 127 mu: &sync.Mutex{}, 128 } 129 130 err := logger.Set(file, perms) 131 132 if err != nil { 133 return nil, err 134 } 135 136 return logger, nil 137 } 138 139 // Reopen close file descriptor for global logger and open it again 140 // Useful for log rotation 141 func Reopen() error { 142 return Global.Reopen() 143 } 144 145 // MinLevel defines minimal logging level 146 func MinLevel(level interface{}) error { 147 return Global.MinLevel(level) 148 } 149 150 // Set change global logger output target 151 func Set(file string, perms os.FileMode) error { 152 return Global.Set(file, perms) 153 } 154 155 // EnableBufIO enable buffered I/O 156 func EnableBufIO(interval time.Duration) { 157 Global.EnableBufIO(interval) 158 } 159 160 // Flush write buffered data to file 161 func Flush() error { 162 return Global.Flush() 163 } 164 165 // Print write message to global logger output 166 func Print(level uint8, f string, a ...interface{}) error { 167 return Global.Print(level, f, a...) 168 } 169 170 // Debug write debug message to global logger output 171 func Debug(f string, a ...interface{}) error { 172 return Global.Debug(f, a...) 173 } 174 175 // Info write info message to global logger output 176 func Info(f string, a ...interface{}) error { 177 return Global.Info(f, a...) 178 } 179 180 // Warn write warning message to global logger output 181 func Warn(f string, a ...interface{}) error { 182 return Global.Warn(f, a...) 183 } 184 185 // Error write error message to global logger output 186 func Error(f string, a ...interface{}) error { 187 return Global.Error(f, a...) 188 } 189 190 // Crit write critical message to global logger output 191 func Crit(f string, a ...interface{}) error { 192 return Global.Crit(f, a...) 193 } 194 195 // Aux write unskippable message (for separators/headers) 196 func Aux(f string, a ...interface{}) error { 197 return Global.Aux(f, a...) 198 } 199 200 // ////////////////////////////////////////////////////////////////////////////////// // 201 202 // Reopen close file descriptor and open again 203 // Useful for log rotation 204 func (l *Logger) Reopen() error { 205 if l == nil || l.mu == nil { 206 return ErrLoggerIsNil 207 } 208 209 l.mu.Lock() 210 211 if l.fd == nil { 212 l.mu.Unlock() 213 return ErrOutputNotSet 214 } 215 216 if l.w != nil { 217 l.w.Flush() 218 } 219 220 l.fd.Close() 221 l.mu.Unlock() 222 223 return l.Set(l.file, l.perms) 224 } 225 226 // MinLevel defines minimal logging level 227 func (l *Logger) MinLevel(level interface{}) error { 228 if l == nil || l.mu == nil { 229 return ErrLoggerIsNil 230 } 231 232 l.mu.Lock() 233 defer l.mu.Unlock() 234 235 levelCode, err := convertMinLevelValue(level) 236 237 if err != nil { 238 return err 239 } 240 241 if levelCode > CRIT { 242 levelCode = CRIT 243 } 244 245 l.minLevel = levelCode 246 247 return nil 248 } 249 250 // EnableBufIO enable buffered I/O support 251 func (l *Logger) EnableBufIO(interval time.Duration) { 252 if l == nil || l.mu == nil { 253 return 254 } 255 256 l.mu.Lock() 257 defer l.mu.Unlock() 258 259 l.useBufIO = true 260 261 if l.fd != nil { 262 l.w = bufio.NewWriter(l.fd) 263 } 264 265 go l.flushDaemon(interval) 266 } 267 268 // Set change logger output target 269 func (l *Logger) Set(file string, perms os.FileMode) error { 270 if l == nil || l.mu == nil { 271 return ErrLoggerIsNil 272 } 273 274 l.mu.Lock() 275 defer l.mu.Unlock() 276 277 fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, perms) 278 279 if err != nil { 280 return err 281 } 282 283 // Flush data if writer exist 284 if l.w != nil { 285 l.w.Flush() 286 l.w = nil 287 } 288 289 if l.fd != nil { 290 l.fd.Close() 291 l.fd = nil 292 } 293 294 l.fd, l.file, l.perms = fd, file, perms 295 296 if l.useBufIO { 297 l.w = bufio.NewWriter(l.fd) 298 } 299 300 return nil 301 } 302 303 // Print write message to logger output 304 func (l *Logger) Print(level uint8, f string, a ...interface{}) error { 305 if l == nil || l.mu == nil { 306 return ErrLoggerIsNil 307 } 308 309 l.mu.Lock() 310 defer l.mu.Unlock() 311 312 if l.minLevel > level { 313 return nil 314 } 315 316 w := l.getWritter(level) 317 showPrefix := l.showPrefix(level) 318 319 if f == "" || f[len(f)-1:] != "\n" { 320 f += "\n" 321 } 322 323 var err error 324 325 switch { 326 case l.UseColors && showPrefix: 327 _, err = fmtc.Fprintf(w, "{s-}%s{!} "+Colors[level]+"%s %s{!}", getTime(), PrefixMap[level], fmt.Sprintf(f, a...)) 328 case l.UseColors && !showPrefix: 329 _, err = fmtc.Fprintf(w, "{s-}%s{!} "+Colors[level]+"%s{!}", getTime(), fmt.Sprintf(f, a...)) 330 case !l.UseColors && showPrefix: 331 _, err = fmt.Fprintf(w, "%s %s %s", getTime(), PrefixMap[level], fmt.Sprintf(f, a...)) 332 case !l.UseColors && !showPrefix: 333 _, err = fmt.Fprintf(w, "%s %s", getTime(), fmt.Sprintf(f, a...)) 334 } 335 336 return err 337 } 338 339 // Flush write buffered data to file 340 func (l *Logger) Flush() error { 341 if l == nil || l.mu == nil { 342 return ErrLoggerIsNil 343 } 344 345 l.mu.Lock() 346 347 if l.w == nil { 348 l.mu.Unlock() 349 return nil 350 } 351 352 err := l.w.Flush() 353 354 l.mu.Unlock() 355 356 return err 357 } 358 359 // Debug write debug message to logger output 360 func (l *Logger) Debug(f string, a ...interface{}) error { 361 if l == nil || l.mu == nil { 362 return ErrLoggerIsNil 363 } 364 365 return l.Print(DEBUG, f, a...) 366 } 367 368 // Info write info message to logger output 369 func (l *Logger) Info(f string, a ...interface{}) error { 370 if l == nil || l.mu == nil { 371 return ErrLoggerIsNil 372 } 373 374 return l.Print(INFO, f, a...) 375 } 376 377 // Warn write warning message to logger output 378 func (l *Logger) Warn(f string, a ...interface{}) error { 379 if l == nil || l.mu == nil { 380 return ErrLoggerIsNil 381 } 382 383 return l.Print(WARN, f, a...) 384 } 385 386 // Error write error message to logger output 387 func (l *Logger) Error(f string, a ...interface{}) error { 388 if l == nil || l.mu == nil { 389 return ErrLoggerIsNil 390 } 391 392 return l.Print(ERROR, f, a...) 393 } 394 395 // Crit write critical message to logger output 396 func (l *Logger) Crit(f string, a ...interface{}) error { 397 if l == nil || l.mu == nil { 398 return ErrLoggerIsNil 399 } 400 401 return l.Print(CRIT, f, a...) 402 } 403 404 // Aux write unfiltered message (for separators/headers) to logger output 405 func (l *Logger) Aux(f string, a ...interface{}) error { 406 if l == nil || l.mu == nil { 407 return ErrLoggerIsNil 408 } 409 410 return l.Print(AUX, f, a...) 411 } 412 413 // ////////////////////////////////////////////////////////////////////////////////// // 414 415 func (l *Logger) getWritter(level uint8) io.Writer { 416 var w io.Writer 417 418 if l.fd == nil { 419 switch level { 420 case ERROR, CRIT: 421 w = os.Stderr 422 default: 423 w = os.Stdout 424 } 425 } else { 426 if l.w != nil { 427 w = l.w 428 } else { 429 w = l.fd 430 } 431 } 432 433 return w 434 } 435 436 func (l *Logger) showPrefix(level uint8) bool { 437 switch { 438 case level == DEBUG && l.PrefixDebug, 439 level == INFO && l.PrefixInfo, 440 level == WARN && l.PrefixWarn, 441 level == ERROR && l.PrefixError, 442 level == CRIT && l.PrefixCrit: 443 return true 444 } 445 446 return false 447 } 448 449 func (l *Logger) flushDaemon(interval time.Duration) { 450 for range time.NewTicker(interval).C { 451 l.Flush() 452 } 453 } 454 455 // ////////////////////////////////////////////////////////////////////////////////// // 456 457 func getTime() string { 458 return "[ " + time.Now().Format(TimeFormat) + " ]" 459 } 460 461 func convertMinLevelValue(level interface{}) (uint8, error) { 462 switch u := level.(type) { 463 464 case int: 465 return uint8(u), nil 466 467 case int8: 468 return uint8(u), nil 469 470 case int16: 471 return uint8(u), nil 472 473 case int32: 474 return uint8(u), nil 475 476 case int64: 477 return uint8(u), nil 478 479 case uint: 480 return uint8(u), nil 481 482 case uint8: 483 return uint8(u), nil 484 485 case uint16: 486 return uint8(u), nil 487 488 case uint32: 489 return uint8(u), nil 490 491 case uint64: 492 return uint8(u), nil 493 494 case float32: 495 return uint8(u), nil 496 497 case float64: 498 return uint8(u), nil 499 500 case string: 501 code, ok := logLevelsNames[strings.ToLower(level.(string))] 502 503 if !ok { 504 return 255, errors.New("Unknown level " + level.(string)) 505 } 506 507 return code, nil 508 } 509 510 return 255, ErrUnexpectedLevel 511 }