github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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 Logf(v int, msg string, args ...interface{}) {
    79  	writeMessage(v, "", msg, args...)
    80  }
    81  
    82  func Errorf(msg string, args ...interface{}) {
    83  	writeMessage(0, "ERROR", msg, args...)
    84  }
    85  
    86  func Fatal(err error) {
    87  	Fatalf("%v", err)
    88  }
    89  
    90  func Fatalf(msg string, args ...interface{}) {
    91  	golog.Fatalf(message("FATAL", msg, args...))
    92  }
    93  
    94  // SyzFatalf-reported errors are parsed by syzkaller as if they were kernel bugs.
    95  func SyzFatalf(msg string, args ...interface{}) {
    96  	golog.Fatalf("SYZFATAL: "+msg, args...)
    97  }
    98  
    99  func SyzFatal(err error) {
   100  	SyzFatalf("%v", err)
   101  }
   102  
   103  func message(severity, msg string, args ...interface{}) string {
   104  	var sb strings.Builder
   105  	if severity != "" {
   106  		fmt.Fprintf(&sb, "[%s] ", severity)
   107  	}
   108  	if instanceName != "" {
   109  		fmt.Fprintf(&sb, "%s: ", instanceName)
   110  	}
   111  	fmt.Fprintf(&sb, msg, args...)
   112  	return sb.String()
   113  }
   114  
   115  func writeMessage(v int, severity, msg string, args ...interface{}) {
   116  	cache := v <= 1 && cachingEnabled.Load()
   117  	if !V(v) && !cache {
   118  		return
   119  	}
   120  	text := message(severity, msg, args...)
   121  	if V(v) {
   122  		golog.Print(text)
   123  	}
   124  	if !cache {
   125  		return
   126  	}
   127  	mu.Lock()
   128  	defer mu.Unlock()
   129  	cacheMem -= len(cacheEntries[cachePos])
   130  	if cacheMem < 0 {
   131  		panic("log cache size underflow")
   132  	}
   133  	timeStr := ""
   134  	if prependTime {
   135  		timeStr = time.Now().Format("2006/01/02 15:04:05 ")
   136  	}
   137  	cacheEntries[cachePos] = timeStr + text
   138  	cacheMem += len(cacheEntries[cachePos])
   139  	cachePos++
   140  	if cachePos == len(cacheEntries) {
   141  		cachePos = 0
   142  	}
   143  	for i := 0; i < len(cacheEntries)-1 && cacheMem > cacheMaxMem; i++ {
   144  		pos := (cachePos + i) % len(cacheEntries)
   145  		cacheMem -= len(cacheEntries[pos])
   146  		cacheEntries[pos] = ""
   147  	}
   148  	if cacheMem < 0 {
   149  		panic("log cache size underflow")
   150  	}
   151  }
   152  
   153  type VerboseWriter int
   154  
   155  func (w VerboseWriter) Write(data []byte) (int, error) {
   156  	Logf(int(w), "%s", data)
   157  	return len(data), nil
   158  }