github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/log/log.go (about)

     1  // Copyright 2016 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  // Package log provides functionality similar to standard log package with some extensions:
     5  //   - verbosity levels
     6  //   - global verbosity setting that can be used by multiple packages
     7  //   - ability to disable all output
     8  //   - ability to cache recent output in memory
     9  package log
    10  
    11  import (
    12  	"bytes"
    13  	"flag"
    14  	"fmt"
    15  	golog "log"
    16  	"strings"
    17  	"sync"
    18  	"sync/atomic"
    19  	"time"
    20  )
    21  
    22  var (
    23  	flagV          = flag.Int("vv", 0, "verbosity")
    24  	mu             sync.Mutex
    25  	cacheMem       int
    26  	cacheMaxMem    int
    27  	cachePos       int
    28  	cacheEntries   []string
    29  	cachingEnabled atomic.Bool
    30  	instanceName   string
    31  	prependTime    = true // for testing
    32  )
    33  
    34  // EnableLogCaching enables in memory caching of log output.
    35  // Caches up to maxLines, but no more than maxMem bytes.
    36  // Cached output can later be queried with CachedOutput.
    37  func EnableLogCaching(maxLines, maxMem int) {
    38  	mu.Lock()
    39  	defer mu.Unlock()
    40  	if cacheEntries != nil {
    41  		Fatalf("log caching is already enabled")
    42  	}
    43  	if maxLines < 1 || maxMem < 1 {
    44  		panic("invalid maxLines/maxMem")
    45  	}
    46  	cacheMaxMem = maxMem
    47  	cacheEntries = make([]string, maxLines)
    48  	cachingEnabled.Store(true)
    49  }
    50  
    51  // Retrieves cached log output.
    52  func CachedLogOutput() string {
    53  	mu.Lock()
    54  	defer mu.Unlock()
    55  	buf := new(bytes.Buffer)
    56  	for i := range cacheEntries {
    57  		pos := (cachePos + i) % len(cacheEntries)
    58  		if cacheEntries[pos] == "" {
    59  			continue
    60  		}
    61  		buf.WriteString(cacheEntries[pos])
    62  		buf.Write([]byte{'\n'})
    63  	}
    64  	return buf.String()
    65  }
    66  
    67  // If the name is set, it will be displayed for all logs.
    68  func SetName(name string) {
    69  	instanceName = name
    70  }
    71  
    72  // V reports whether verbosity at the call site is at least the requested level.
    73  // See https://pkg.go.dev/github.com/golang/glog#V for details.
    74  func V(level int) bool {
    75  	return level <= *flagV
    76  }
    77  
    78  func Log(v int, msg string) {
    79  	Logf(v, "%v", msg)
    80  }
    81  
    82  func Logf(v int, msg string, args ...interface{}) {
    83  	writeMessage(v, "", msg, args...)
    84  }
    85  
    86  func Error(err error) {
    87  	Errorf("%v", err)
    88  }
    89  
    90  func Errorf(msg string, args ...interface{}) {
    91  	writeMessage(0, "ERROR", msg, args...)
    92  }
    93  
    94  func Fatal(err error) {
    95  	Fatalf("%v", err)
    96  }
    97  
    98  func Fatalf(msg string, args ...interface{}) {
    99  	golog.Fatal(message("FATAL", msg, args...))
   100  }
   101  
   102  func message(severity, msg string, args ...interface{}) string {
   103  	var sb strings.Builder
   104  	if severity != "" {
   105  		fmt.Fprintf(&sb, "[%s] ", severity)
   106  	}
   107  	if instanceName != "" {
   108  		fmt.Fprintf(&sb, "%s: ", instanceName)
   109  	}
   110  	fmt.Fprintf(&sb, msg, args...)
   111  	return sb.String()
   112  }
   113  
   114  func writeMessage(v int, severity, msg string, args ...interface{}) {
   115  	cache := v <= 1 && cachingEnabled.Load()
   116  	if !V(v) && !cache {
   117  		return
   118  	}
   119  	text := message(severity, msg, args...)
   120  	if V(v) {
   121  		golog.Print(text)
   122  	}
   123  	if !cache {
   124  		return
   125  	}
   126  	mu.Lock()
   127  	defer mu.Unlock()
   128  	cacheMem -= len(cacheEntries[cachePos])
   129  	if cacheMem < 0 {
   130  		panic("log cache size underflow")
   131  	}
   132  	timeStr := ""
   133  	if prependTime {
   134  		timeStr = time.Now().Format("2006/01/02 15:04:05 ")
   135  	}
   136  	cacheEntries[cachePos] = timeStr + text
   137  	cacheMem += len(cacheEntries[cachePos])
   138  	cachePos++
   139  	if cachePos == len(cacheEntries) {
   140  		cachePos = 0
   141  	}
   142  	for i := 0; i < len(cacheEntries)-1 && cacheMem > cacheMaxMem; i++ {
   143  		pos := (cachePos + i) % len(cacheEntries)
   144  		cacheMem -= len(cacheEntries[pos])
   145  		cacheEntries[pos] = ""
   146  	}
   147  	if cacheMem < 0 {
   148  		panic("log cache size underflow")
   149  	}
   150  }
   151  
   152  type VerboseWriter int
   153  
   154  func (w VerboseWriter) Write(data []byte) (int, error) {
   155  	Logf(int(w), "%s", data)
   156  	return len(data), nil
   157  }