github.com/coyove/common@v0.0.0-20240403014525-f70e643f9de8/logg/logg.go (about) 1 package logg 2 3 import ( 4 "bytes" 5 "encoding/csv" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync" 14 "syscall" 15 "time" 16 ) 17 18 type _Level int 19 20 const ( 21 LvDebug0 _Level = iota - 2 22 LvDebug 23 LvLog 24 LvInfo 25 LvWarning 26 LvError 27 LvFatal 28 LvOff 29 ) 30 31 type _Format byte 32 33 const ( 34 FmtLongTime _Format = 1 + iota 35 FmtLongTimeUTC 36 FmtShortTime 37 FmtShortTimeSec 38 FmtElapsedTime 39 FmtElapsedTimeSec 40 FmtLongFile 41 FmtShortFile 42 FmtLevel 43 FmtGoroutine 44 FmtVoid 45 ) 46 47 type Logger struct { 48 Writer io.Writer 49 logLevel _Level 50 ignoreLevels []_Level 51 logPath string 52 formats []_Format 53 logFile *os.File 54 logFileTmp bytes.Buffer 55 logFileSize int64 56 lastFlush int64 57 start int64 58 sync.Mutex 59 } 60 61 var ( 62 LvTexts = []string{" FATAL ", " ERROR ", " WARNING ", " INFO ", " LOG ", " DEBUG ", " DEBUG0 "} 63 lvlookup = map[string]_Level{"dbg0": LvDebug0, "dbg": LvDebug, "info": LvInfo, "log": LvLog, "warn": LvWarning, "err": LvError, "fatal": LvFatal, "off": LvOff} 64 ) 65 66 func (l *Logger) SetLevel(lv string) _Level { 67 l.ignoreLevels = nil 68 for i, lv := range strings.Split(lv, "^") { 69 n, ok := lvlookup[lv] 70 if !ok { 71 panic("unexpected log level: " + lv) 72 } 73 74 if i == 0 { 75 l.logLevel = n 76 } else { 77 if l.ignoreLevels == nil { 78 l.ignoreLevels = []_Level{n} 79 } else { 80 l.ignoreLevels = append(l.ignoreLevels, n) 81 } 82 } 83 } 84 85 return l.logLevel 86 } 87 88 func NewLogger(config string) *Logger { 89 l := &Logger{} 90 l.formats = []_Format{FmtLongTime, FmtShortFile, FmtLevel} 91 l.start = time.Now().UnixNano() 92 93 parts := strings.Split(config, ",") 94 if len(parts) == 0 { 95 return l 96 } 97 98 x := parts[0] 99 if strings.Contains(x, ":") { 100 fn := x[strings.Index(x, ":")+1:] 101 x = x[:len(x)-len(fn)-1] 102 if parts := strings.Split(fn, "+"); len(parts) == 2 { 103 if rs, err := strconv.Atoi(parts[0]); err == nil { 104 l.LogFile(parts[1], int64(rs)) 105 } 106 } else { 107 l.LogFile(fn, 1024*1024) 108 } 109 } 110 l.SetLevel(x) 111 112 r := csv.NewReader(strings.NewReader(config)) 113 parts, _ = r.Read() 114 formats := make([]_Format, 0, len(parts)) 115 116 for i := 1; i < len(parts); i++ { 117 switch x := parts[i]; x { 118 case "longtime", "lt": 119 formats = append(formats, FmtLongTime) 120 case "longtimeutc", "ltu": 121 formats = append(formats, FmtLongTimeUTC) 122 case "shorttime", "st": 123 formats = append(formats, FmtShortTime) 124 case "shorttimesec", "sts": 125 formats = append(formats, FmtShortTimeSec) 126 case "elapsedtime", "et": 127 formats = append(formats, FmtElapsedTime) 128 case "elapsedtimesec", "ets": 129 formats = append(formats, FmtElapsedTimeSec) 130 case "shortfile", "sf": 131 formats = append(formats, FmtShortFile) 132 case "longfile", "lf": 133 formats = append(formats, FmtLongFile) 134 case "level", "lv", "l": 135 formats = append(formats, FmtLevel) 136 case "goroutine", "go", "g": 137 formats = append(formats, FmtGoroutine) 138 case "void": 139 formats = append(formats, FmtVoid) 140 } 141 } 142 143 if len(formats) > 0 { 144 l.formats = formats 145 } 146 147 return l 148 } 149 150 func (l *Logger) GetLevel() _Level { 151 return l.logLevel 152 } 153 154 func (l *Logger) LogFile(fn string, rotateSize int64) { 155 if l.logFile != nil { 156 l.logFile.Sync() 157 l.logFile.Close() 158 } 159 160 l.logPath = fn 161 fn += "." + time.Now().UTC().Format("2006-01-02_15-04-05.000") 162 163 var err error 164 l.logFile, err = os.Create(fn) 165 if err != nil { 166 panic(err) 167 } 168 l.logFileSize = rotateSize 169 l.logFileTmp.Reset() 170 } 171 172 func trunc(fn string) string { 173 idx := strings.LastIndex(fn, "/") 174 if idx == -1 { 175 idx = strings.LastIndex(fn, "\\") 176 } 177 return fn[idx+1:] 178 } 179 180 // Widnows WSA error messages are way too long to print 181 // ex: An established connection was aborted by the software in your host machine.write tcp 127.0.0.1:8100->127.0.0.1:52466: wsasend: An established connection was aborted by the software in your host machine. 182 func tryShortenWSAError(err interface{}) (ret string) { 183 defer func() { 184 if recover() != nil { 185 ret = fmt.Sprintf("%v", err) 186 } 187 }() 188 189 if e, sysok := err.(*net.OpError).Err.(*os.SyscallError); sysok { 190 errno := e.Err.(syscall.Errno) 191 if msg, ok := WSAErrno[int(errno)]; ok { 192 ret = msg 193 } else { 194 // messages on linux are short enough 195 ret = fmt.Sprintf("C%d, %s", uintptr(errno), e.Error()) 196 } 197 198 return 199 } 200 201 ret = err.(*net.OpError).Err.Error() 202 return 203 } 204 205 func (l *Logger) print(lvs string, format string, params ...interface{}) { 206 _, fn, line, _ := runtime.Caller(3) 207 now := time.Now() 208 m := csvbuffer{} 209 for _, f := range l.formats { 210 switch f { 211 case FmtLongTime: 212 m.Write(now.Format("2006-01-02 15:04:05.000 MST")) 213 case FmtLongTimeUTC: 214 m.Write(now.UTC().Format("2006-01-02 15:04:05.000")) 215 case FmtLongFile: 216 m.Write(fn + ":" + strconv.Itoa(line)) 217 case FmtShortFile: 218 m.Write(trunc(fn) + ":" + strconv.Itoa(line)) 219 case FmtShortTime: 220 now = now.UTC() 221 m.Write(fmt.Sprintf("%d%02d%02d%02d%02d%02d.%03d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), (now.UnixNano()%1e9)/1e6)) 222 case FmtShortTimeSec: 223 now = now.UTC() 224 m.Write(fmt.Sprintf("%d%02d%02d%02d%02d%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())) 225 case FmtElapsedTime: 226 m.Write(strconv.FormatFloat(float64(time.Now().UnixNano()-l.start)/1e9, 'f', 6, 64)) 227 case FmtElapsedTimeSec: 228 m.Write(strconv.FormatInt((time.Now().UnixNano()-l.start)/1e9, 10)) 229 case FmtLevel: 230 m.Write(lvs) 231 case FmtGoroutine: 232 buf := [32]byte{} 233 runtime.Stack(buf[:], false) 234 startidx := bytes.Index(buf[:], []byte(" ")) 235 endidx := bytes.Index(buf[:], []byte("[")) 236 m.Write(string(buf[startidx+1 : endidx-1])) 237 } 238 } 239 240 for i := 0; i < len(params); i++ { 241 p := params[i] 242 x := "" 243 switch op := p.(type) { 244 case *net.OpError: 245 if op.Source == nil && op.Addr == nil { 246 x = fmt.Sprintf("%s, %s", op.Op, tryShortenWSAError(p)) 247 } else { 248 x = fmt.Sprintf("%s %v, %s", op.Op, op.Addr, tryShortenWSAError(p)) 249 } 250 params[i] = x 251 case *net.DNSError: 252 x = fmt.Sprintf("DNS lookup failed: %v", op) 253 params[i] = x 254 default: 255 if format == "" { 256 x = fmt.Sprintf("%v", op) 257 } 258 } 259 260 if format == "" { 261 m.Write(x) 262 } 263 } 264 265 if format != "" { 266 m.Write(fmt.Sprintf(format, params...)) 267 } 268 m.NewLine() 269 270 if l.logFile != nil { 271 l.logFileTmp.Write(m.Bytes()) 272 l.flush(lvs == LvTexts[0]) 273 } else if l.Writer != nil { 274 l.Writer.Write(m.Bytes()) 275 } else { 276 os.Stderr.Write(m.Bytes()) 277 } 278 } 279 280 func (l *Logger) flush(force bool) { 281 l.Lock() 282 now := time.Now().UnixNano() 283 if l.logFileTmp.Len() > 4096 || now-l.lastFlush > 1e9 || force { 284 if l.logFile != nil { 285 l.logFile.Write(l.logFileTmp.Bytes()) 286 l.lastFlush = now 287 l.logFileTmp.Reset() 288 289 if st, _ := l.logFile.Stat(); st.Size() > l.logFileSize { 290 l.LogFile(l.logPath, l.logFileSize) 291 } 292 } 293 } 294 l.Unlock() 295 } 296 297 func (l *Logger) If(b bool) *Logger { 298 if b { 299 return l 300 } 301 return nil 302 } 303 304 func (l *Logger) level(lv _Level, format string, params ...interface{}) *Logger { 305 if l == nil { 306 return nil 307 } 308 for _, n := range l.ignoreLevels { 309 if n == lv { 310 return l 311 } 312 } 313 if l.logLevel <= lv { 314 l.print(LvTexts[LvOff-lv-1], format, params...) 315 } 316 if lv == LvFatal { 317 os.Exit(1) 318 } 319 return l 320 } 321 322 func (l *Logger) Dbg0f(f string, a ...interface{}) *Logger { return l.level(LvDebug0, f, a...) } 323 func (l *Logger) Dbg0(a ...interface{}) *Logger { return l.level(LvDebug0, "", a...) } 324 func (l *Logger) Dbgf(f string, a ...interface{}) *Logger { return l.level(LvDebug, f, a...) } 325 func (l *Logger) Dbg(a ...interface{}) *Logger { return l.level(LvDebug, "", a...) } 326 func (l *Logger) Logf(f string, a ...interface{}) *Logger { return l.level(LvLog, f, a...) } 327 func (l *Logger) Log(a ...interface{}) *Logger { return l.level(LvLog, "", a...) } 328 func (l *Logger) Infof(f string, a ...interface{}) *Logger { return l.level(LvInfo, f, a...) } 329 func (l *Logger) Info(a ...interface{}) *Logger { return l.level(LvInfo, "", a...) } 330 func (l *Logger) Warnf(f string, a ...interface{}) *Logger { return l.level(LvWarning, f, a...) } 331 func (l *Logger) Warn(a ...interface{}) *Logger { return l.level(LvWarning, "", a...) } 332 func (l *Logger) Errorf(f string, a ...interface{}) *Logger { return l.level(LvError, f, a...) } 333 func (l *Logger) Error(a ...interface{}) *Logger { return l.level(LvError, "", a...) } 334 func (l *Logger) Fatalf(f string, a ...interface{}) *Logger { return l.level(LvFatal, f, a...) } 335 func (l *Logger) Fatal(a ...interface{}) *Logger { return l.level(LvFatal, "", a...) }