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