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
9package log
10
11import (
12	"bytes"
13	"flag"
14	"fmt"
15	golog "log"
16	"sync"
17	"time"
18)
19
20var (
21	flagV        = flag.Int("v", 0, "verbosity")
22	mu           sync.Mutex
23	cacheMem     int
24	cacheMaxMem  int
25	cachePos     int
26	cacheEntries []string
27	prependTime  = true // for testing
28)
29
30// EnableCaching enables in memory caching of log output.
31// Caches up to maxLines, but no more than maxMem bytes.
32// Cached output can later be queried with CachedOutput.
33func EnableLogCaching(maxLines, maxMem int) {
34	mu.Lock()
35	defer mu.Unlock()
36	if cacheEntries != nil {
37		Fatalf("log caching is already enabled")
38	}
39	if maxLines < 1 || maxMem < 1 {
40		panic("invalid maxLines/maxMem")
41	}
42	cacheMaxMem = maxMem
43	cacheEntries = make([]string, maxLines)
44}
45
46// Retrieves cached log output.
47func CachedLogOutput() string {
48	mu.Lock()
49	defer mu.Unlock()
50	buf := new(bytes.Buffer)
51	for i := range cacheEntries {
52		pos := (cachePos + i) % len(cacheEntries)
53		if cacheEntries[pos] == "" {
54			continue
55		}
56		buf.WriteString(cacheEntries[pos])
57		buf.Write([]byte{'\n'})
58	}
59	return buf.String()
60}
61
62func Logf(v int, msg string, args ...interface{}) {
63	mu.Lock()
64	doLog := v <= *flagV
65	if cacheEntries != nil && v <= 1 {
66		cacheMem -= len(cacheEntries[cachePos])
67		if cacheMem < 0 {
68			panic("log cache size underflow")
69		}
70		timeStr := ""
71		if prependTime {
72			timeStr = time.Now().Format("2006/01/02 15:04:05 ")
73		}
74		cacheEntries[cachePos] = fmt.Sprintf(timeStr+msg, args...)
75		cacheMem += len(cacheEntries[cachePos])
76		cachePos++
77		if cachePos == len(cacheEntries) {
78			cachePos = 0
79		}
80		for i := 0; i < len(cacheEntries)-1 && cacheMem > cacheMaxMem; i++ {
81			pos := (cachePos + i) % len(cacheEntries)
82			cacheMem -= len(cacheEntries[pos])
83			cacheEntries[pos] = ""
84		}
85		if cacheMem < 0 {
86			panic("log cache size underflow")
87		}
88	}
89	mu.Unlock()
90
91	if doLog {
92		golog.Printf(msg, args...)
93	}
94}
95
96func Fatal(err error) {
97	golog.Fatal(err)
98}
99
100func Fatalf(msg string, args ...interface{}) {
101	golog.Fatalf(msg, args...)
102}
103