github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/nlog/nlog.go (about) 1 // Package nlog - aistore logger, provides buffering, timestamping, writing, and 2 // flushing/syncing/rotating 3 /* 4 * Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package nlog 7 8 import ( 9 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strconv" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "time" 19 20 "github.com/NVIDIA/aistore/cmn/mono" 21 ) 22 23 const ( 24 fixedSize = 64 * 1024 25 extraSize = 32 * 1024 // via mem pool 26 maxLineSize = 2 * 1024 27 ) 28 29 type severity int 30 31 const ( 32 sevInfo severity = iota 33 sevWarn 34 sevErr 35 ) 36 37 type ( 38 nlog struct { 39 file *os.File 40 pw, buf1, buf2 *fixed 41 line fixed 42 toFlush []*fixed 43 last atomic.Int64 44 written atomic.Int64 45 sev severity 46 oob atomic.Bool 47 erred atomic.Bool 48 mw sync.Mutex 49 } 50 ) 51 52 // main function 53 func log(sev severity, depth int, format string, args ...any) { 54 onceInitFiles.Do(initFiles) 55 56 switch { 57 case !flag.Parsed(): 58 os.Stderr.WriteString("Error: logging before flag.Parse: ") 59 fallthrough 60 case LogToStderr: 61 fb := alloc() 62 sprintf(sev, depth, format, fb, args...) 63 fb.flush(os.Stderr) 64 free(fb) 65 case sev >= sevWarn: 66 fb := alloc() 67 sprintf(sev, depth, format, fb, args...) 68 if sev >= sevErr { 69 fb.flush(os.Stderr) 70 } 71 if sev >= sevWarn { 72 nlog := nlogs[sevErr] 73 nlog.mw.Lock() 74 nlog.write(fb) 75 nlog.mw.Unlock() 76 } 77 nlog := nlogs[sevInfo] 78 nlog.mw.Lock() 79 nlog.write(fb) 80 nlog.mw.Unlock() 81 free(fb) 82 default: 83 // fast path 84 nlogs[sevInfo].printf(sev, depth, format, args...) 85 } 86 } 87 88 // 89 // nlog 90 // 91 92 func newNlog(sev severity) *nlog { 93 nlog := &nlog{ 94 sev: sev, 95 buf1: &fixed{buf: make([]byte, fixedSize)}, 96 buf2: &fixed{buf: make([]byte, fixedSize)}, 97 line: fixed{buf: make([]byte, maxLineSize)}, 98 toFlush: make([]*fixed, 0, 4), 99 } 100 nlog.pw = nlog.buf1 101 return nlog 102 } 103 104 func (nlog *nlog) since(now int64) time.Duration { return time.Duration(now - nlog.last.Load()) } 105 106 func (nlog *nlog) printf(sev severity, depth int, format string, args ...any) { 107 nlog.mw.Lock() 108 nlog.line.reset() 109 sprintf(sev, depth+1, format, &nlog.line, args...) 110 nlog.write(&nlog.line) 111 nlog.mw.Unlock() 112 } 113 114 // under mw-lock 115 func (nlog *nlog) write(line *fixed) { 116 buf := line.buf[:line.woff] 117 nlog.pw.Write(buf) 118 119 if nlog.pw.avail() > maxLineSize { 120 return 121 } 122 123 nlog.toFlush = append(nlog.toFlush, nlog.pw) 124 nlog.oob.Store(true) 125 nlog.get() 126 } 127 128 func (nlog *nlog) get() { 129 prev := nlog.pw 130 assert(prev == nlog.toFlush[len(nlog.toFlush)-1]) 131 switch { 132 case prev == nlog.buf1: 133 if nlog.buf2 != nil { 134 nlog.pw = nlog.buf2 135 } else { 136 nlog.pw = alloc() 137 } 138 nlog.buf1 = nil 139 case prev == nlog.buf2: 140 if nlog.buf1 != nil { 141 nlog.pw = nlog.buf1 142 } else { 143 nlog.pw = alloc() 144 } 145 nlog.buf2 = nil 146 default: // prev was alloc-ed 147 if nlog.buf1 != nil { 148 nlog.pw = nlog.buf1 149 } else if nlog.buf2 != nil { 150 nlog.pw = nlog.buf2 151 } else { 152 nlog.pw = alloc() 153 } 154 } 155 } 156 157 func (nlog *nlog) put(pw *fixed /* to reuse */) { 158 nlog.mw.Lock() 159 if nlog.buf1 == nil { 160 nlog.buf1 = pw 161 } else if nlog.buf2 == nil { 162 nlog.buf2 = pw 163 } else { 164 assert(nlog.buf1 == pw || nlog.buf2 == pw) // via Flush(true) 165 } 166 nlog.mw.Unlock() 167 } 168 169 func (nlog *nlog) flush() { 170 for { 171 nlog.mw.Lock() 172 if len(nlog.toFlush) == 0 { 173 nlog.oob.Store(false) 174 nlog.mw.Unlock() 175 break 176 } 177 pw := nlog.toFlush[0] 178 copy(nlog.toFlush, nlog.toFlush[1:]) 179 nlog.toFlush = nlog.toFlush[:len(nlog.toFlush)-1] 180 nlog.mw.Unlock() 181 182 nlog.do(pw) 183 } 184 } 185 186 func (nlog *nlog) do(pw *fixed) { 187 // write 188 if nlog.erred.Load() { 189 if Stopping() { 190 _whileStopping(pw.buf[:pw.woff]) 191 } else { 192 os.Stderr.Write(pw.buf[:pw.woff]) 193 } 194 } else { 195 n, err := pw.flush(nlog.file) 196 if err != nil { 197 nlog.erred.Store(true) 198 } 199 nlog.written.Add(int64(n)) 200 nlog.last.Store(mono.NanoTime()) 201 } 202 203 // recycle buf 204 pw.reset() 205 if pw.size() == extraSize { 206 free(pw) 207 } else { 208 assert(pw.size() == fixedSize) 209 nlog.put(pw) 210 } 211 212 // rotate 213 if nlog.written.Load() >= MaxSize { 214 err := nlog.file.Close() 215 assert(err == nil) 216 nlog.rotate(time.Now()) 217 } 218 } 219 220 func (nlog *nlog) rotate(now time.Time) (err error) { 221 var ( 222 line1 string 223 s = fmt.Sprintf("host %s, %s for %s/%s\n", host, runtime.Version(), runtime.GOOS, runtime.GOARCH) 224 snow = now.Format("2006/01/02 15:04:05") 225 ) 226 if nlog.file, _, err = fcreate(sevText[nlog.sev], now); err != nil { 227 nlog.erred.Store(true) 228 return 229 } 230 231 nlog.written.Store(0) 232 nlog.erred.Store(false) 233 if title == "" { 234 line1 = "Started up at " + snow + ", " + s 235 _, err = nlog.file.WriteString(line1) 236 } else { 237 line1 = "Rotated at " + snow + ", " + s 238 nlog.file.WriteString(line1) 239 _, err = nlog.file.WriteString(title) 240 } 241 return 242 } 243 244 // 245 // utils 246 // 247 248 func logfname(tag string, t time.Time) (name, link string) { 249 s := sname() 250 name = fmt.Sprintf("%s.%s.%s.%02d%02d-%02d%02d%02d.%d", 251 s, 252 host, 253 tag, 254 t.Month(), 255 t.Day(), 256 t.Hour(), 257 t.Minute(), 258 t.Second(), 259 pid) 260 return name, s + "." + tag 261 } 262 263 func formatHdr(s severity, depth int, fb *fixed) { 264 const char = "IWE" 265 _, fn, ln, ok := runtime.Caller(3 + depth) 266 if !ok { 267 return 268 } 269 idx := strings.LastIndexByte(fn, filepath.Separator) 270 if idx > 0 { 271 fn = fn[idx+1:] 272 } 273 if l := len(fn); l > 3 { 274 fn = fn[:l-3] 275 } 276 fb.writeByte(char[s]) 277 fb.writeByte(' ') 278 now := time.Now() 279 fb.writeString(now.Format("15:04:05.000000")) 280 281 fb.writeByte(' ') 282 if _, redact := redactFnames[fn]; redact { 283 return 284 } 285 fb.writeString(fn) 286 fb.writeByte(':') 287 fb.writeString(strconv.Itoa(ln)) 288 fb.writeByte(' ') 289 } 290 291 func sprintf(sev severity, depth int, format string, fb *fixed, args ...any) { 292 formatHdr(sev, depth+1, fb) 293 if format == "" { 294 fmt.Fprintln(fb, args...) 295 } else { 296 fmt.Fprintf(fb, format, args...) 297 fb.eol() 298 } 299 } 300 301 const wstag = "[while stopping:] " 302 303 func _whileStopping(p []byte) { 304 os.Stderr.WriteString(wstag) 305 os.Stderr.Write(p) 306 os.Stderr.WriteString("\n") 307 } 308 309 // mem pool of additional buffers 310 // usage: 311 // - none of the "fixed" ones available 312 // - alsoToStderr 313 314 func alloc() (fb *fixed) { 315 if v := pool.Get(); v != nil { 316 fb = v.(*fixed) 317 fb.reset() 318 } else { 319 fb = &fixed{buf: make([]byte, extraSize)} 320 } 321 return 322 } 323 324 func free(fb *fixed) { 325 assert(fb.size() == extraSize) 326 pool.Put(fb) 327 }