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  }