lol.mx raw

   1  // Package lol (log of location) formats log entries with microsecond
   2  // timestamps and source locations. Entries are sent as []byte on a channel
   3  // to the logger domain - no direct I/O happens here.
   4  package lol
   5  
   6  import (
   7  	"fmt"
   8  	"runtime"
   9  	"time"
  10  )
  11  
  12  const (
  13  	Off = iota
  14  	Fatal
  15  	Error
  16  	Warn
  17  	Info
  18  	Debug
  19  	Trace
  20  )
  21  
  22  var LevelNames [][]byte
  23  var LevelTags [][]byte
  24  
  25  type (
  26  	// Ln prints items with spaces between them.
  27  	Ln func(a ...fmt.Stringer)
  28  	// F prints formatted output.
  29  	F func(format []byte, a ...fmt.Stringer)
  30  	// C accepts a closure to defer computation if level is suppressed.
  31  	C func(closure func() []byte)
  32  	// Chk logs an error if non-nil and returns whether it was non-nil.
  33  	Chk func(e error) bool
  34  	// Err formats and returns an error, also logging it.
  35  	Err func(format []byte, a ...fmt.Stringer) error
  36  
  37  	// LevelPrinter is the full set of printers for one log level.
  38  	LevelPrinter struct {
  39  		Ln
  40  		F
  41  		C
  42  		Chk
  43  		Err
  44  	}
  45  )
  46  
  47  // Log holds printers for all levels.
  48  type Log struct {
  49  	F, E, W, I, D, T LevelPrinter
  50  }
  51  
  52  // Check holds error checkers for all levels.
  53  type Check struct {
  54  	F, E, W, I, D, T Chk
  55  }
  56  
  57  // Errorf holds error-returning loggers for all levels.
  58  type Errorf struct {
  59  	F, E, W, I, D, T Err
  60  }
  61  
  62  // Logger bundles Log, Check, and Errorf.
  63  type Logger struct {
  64  	*Log
  65  	*Check
  66  	*Errorf
  67  }
  68  
  69  // Main is the default logger. It writes to a discard channel until
  70  // Init is called with a real log channel.
  71  var Main *Logger
  72  
  73  // level is the current log level for this domain. Held in a self-mutating
  74  // type so SetLevel can change it through a method instead of writing a
  75  // package global directly. Plain int32 - single cooperative thread, no
  76  // atomic needed.
  77  type logLevel struct{ n int32 }
  78  
  79  func (lv *logLevel) Set(n int32) { lv.n = n }
  80  
  81  var lvl logLevel
  82  
  83  func init() {
  84  	LevelNames = [][]byte{
  85  		[]byte("off"),
  86  		[]byte("fatal"),
  87  		[]byte("error"),
  88  		[]byte("warn"),
  89  		[]byte("info"),
  90  		[]byte("debug"),
  91  		[]byte("trace"),
  92  	}
  93  	LevelTags = [][]byte{
  94  		[]byte("  "),
  95  		[]byte("FTL"),
  96  		[]byte("ERR"),
  97  		[]byte("WRN"),
  98  		[]byte("INF"),
  99  		[]byte("DBG"),
 100  		[]byte("TRC"),
 101  	}
 102  	lvl.Set(Info)
 103  	Main = &Logger{}
 104  	Main.Log, Main.Check, Main.Errorf = newPrinters(nil, 2)
 105  }
 106  
 107  // Init wires the logger to a channel. Must be called once per domain
 108  // at startup before any logging. Passing nil gives a local stderr
 109  // fallback (useful for the main domain before logger domain is spawned).
 110  func Init(ch chan<- []byte) {
 111  	Main.Log, Main.Check, Main.Errorf = newPrinters(ch, 2)
 112  }
 113  
 114  // SetLevel sets the log level for this domain.
 115  func SetLevel(l int32) { lvl.Set(l) }
 116  
 117  // GetLevel returns the current log level.
 118  func GetLevel() (n int32) { return lvl.n }
 119  
 120  // SetLevelByName sets the log level by name.
 121  func SetLevelByName(name []byte) {
 122  	for i := range LevelNames {
 123  		if bytesEqual(name, LevelNames[i]) {
 124  			lvl.Set(int32(i))
 125  			return
 126  		}
 127  	}
 128  	lvl.Set(Info)
 129  }
 130  
 131  // GetLevelByName returns the level number for a name.
 132  func GetLevelByName(name []byte) (n int32) {
 133  	for i := range LevelNames {
 134  		if bytesEqual(name, LevelNames[i]) {
 135  			return int32(i)
 136  		}
 137  	}
 138  	return Info
 139  }
 140  
 141  func bytesEqual(a, b []byte) (ok bool) {
 142  	if len(a) != len(b) {
 143  		return false
 144  	}
 145  	for i := range a {
 146  		if a[i] != b[i] {
 147  			return false
 148  		}
 149  	}
 150  	return true
 151  }
 152  
 153  // emit sends a formatted log entry on the channel, or prints to stderr
 154  // if no channel is configured.
 155  func emit(ch chan<- []byte, entry []byte) {
 156  	if ch != nil {
 157  		ch <- entry
 158  		return
 159  	}
 160  	// Fallback: direct print (before logger domain is up, or in tests)
 161  	print(string(entry))
 162  }
 163  
 164  // formatEntry builds a log line: "timestamp TAG message location\n"
 165  func formatEntry(l int32, msg []byte, skip int32) (buf []byte) {
 166  	ts := time.Now().UnixMicro()
 167  	_, file, line, _ := runtime.Caller(skip)
 168  	return []byte(nil) | fmt.Sprintf("%d %s %s %s:%d\n",
 169  		ts, LevelTags[l], msg, file, line)
 170  }
 171  
 172  // getPrinter returns a LevelPrinter for one level that sends on ch.
 173  func getPrinter(l int32, ch chan<- []byte, skip int32) (l2 LevelPrinter) {
 174  	return LevelPrinter{
 175  		Ln: func(a ...fmt.Stringer) {
 176  			if lvl.n < l {
 177  				return
 178  			}
 179  			msg := []byte(nil) | fmt.Sprint(a...)
 180  			emit(ch, formatEntry(l, msg, skip))
 181  		},
 182  		F: func(format []byte, a ...fmt.Stringer) {
 183  			if lvl.n < l {
 184  				return
 185  			}
 186  			msg := []byte(nil) | fmt.Sprintf(string(format), a...)
 187  			emit(ch, formatEntry(l, msg, skip))
 188  		},
 189  		C: func(closure func() []byte) {
 190  			if lvl.n < l {
 191  				return
 192  			}
 193  			emit(ch, formatEntry(l, closure(), skip))
 194  		},
 195  		Chk: func(e error) bool {
 196  			if e == nil {
 197  				return false
 198  			}
 199  			if lvl.n >= l {
 200  				msg := []byte(e.Error())
 201  				emit(ch, formatEntry(l, msg, skip))
 202  			}
 203  			return true
 204  		},
 205  		Err: func(format []byte, a ...fmt.Stringer) error {
 206  			err := fmt.Errorf(string(format), a...)
 207  			if lvl.n >= l {
 208  				msg := []byte(err.Error())
 209  				emit(ch, formatEntry(l, msg, skip))
 210  			}
 211  			return err
 212  		},
 213  	}
 214  }
 215  
 216  // getNullPrinter returns a no-op printer.
 217  func getNullPrinter() (l LevelPrinter) {
 218  	return LevelPrinter{
 219  		Ln:  func(a ...fmt.Stringer) {},
 220  		F:   func(format []byte, a ...fmt.Stringer) {},
 221  		C:   func(closure func() []byte) {},
 222  		Chk: func(e error) bool { return e != nil },
 223  		Err: func(format []byte, a ...fmt.Stringer) error {
 224  			return fmt.Errorf(string(format), a...)
 225  		},
 226  	}
 227  }
 228  
 229  // newPrinters creates a full set of Log, Check, Errorf for all levels.
 230  func newPrinters(ch chan<- []byte, skip int32) (lg *Log, ck *Check, errs *Errorf) {
 231  	l := &Log{
 232  		T: getPrinter(Trace, ch, skip),
 233  		D: getPrinter(Debug, ch, skip),
 234  		I: getPrinter(Info, ch, skip),
 235  		W: getPrinter(Warn, ch, skip),
 236  		E: getPrinter(Error, ch, skip),
 237  		F: getPrinter(Fatal, ch, skip),
 238  	}
 239  	c := &Check{
 240  		F: l.F.Chk, E: l.E.Chk, W: l.W.Chk,
 241  		I: l.I.Chk, D: l.D.Chk, T: l.T.Chk,
 242  	}
 243  	ef := &Errorf{
 244  		F: l.F.Err, E: l.E.Err, W: l.W.Err,
 245  		I: l.I.Err, D: l.D.Err, T: l.T.Err,
 246  	}
 247  	return l, c, ef
 248  }
 249