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