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