1
2 package clog
3
4 import (
5 "bytes"
6 "encoding"
7 "fmt"
8 "io"
9 "reflect"
10 "sort"
11 "strconv"
12 "strings"
13 "text/tabwriter"
14
15 "github.com/go-logr/logr"
16 "github.com/wojas/genericr"
17 )
18
19
20 type MessageClass int
21
22 const (
23
24 None MessageClass = iota
25
26 All
27
28 Info
29
30 Error
31 )
32
33
34 func New(opts ...Option) logr.Logger {
35 return logr.New(newSink(opts...))
36 }
37
38
39 func newSink(opts ...Option) logr.LogSink {
40 o := makeOptions(opts...)
41
42 l := &clog{
43 logCaller: o.logCaller,
44 out: o.dest,
45 tabWidth: 2,
46 }
47
48 g := genericr.New(l.logFn).
49 WithVerbosity(o.lvl)
50
51 if o.logCaller != None {
52 g = g.WithCaller(true)
53 }
54
55 return g
56 }
57
58 type clog struct {
59 logCaller MessageClass
60 out io.Writer
61 tabWidth int
62 }
63
64 func (l *clog) logFn(e genericr.Entry) {
65 log := ""
66 if e.Error != nil {
67 log += "[error] "
68 }
69 if e.Name != "" {
70 log += strings.Join(e.NameParts, ":") + ": "
71 }
72 log += l.caller(e)
73
74 log += e.Message
75 multilineVals := ""
76 for i := 0; i < len(e.Fields); i += 2 {
77 k, ok := e.Fields[i].(string)
78 if !ok {
79 k = fmt.Sprintf("!(%#v)", e.Fields[i])
80 }
81
82 var v any
83 if i+1 < len(e.Fields) {
84 v = e.Fields[i+1]
85 }
86 valstr := l.pretty(v)
87
88 if valstr != "" {
89
90
91
92
93
94
95
96 if strings.HasPrefix(valstr, "\n") {
97
98
99 multilineVals += "\n" + indent(l.tabWidth, 1) + k + "=" + valstr
100 continue
101 }
102 log += " " + k + "=" + valstr
103 }
104 }
105
106 if multilineVals != "" {
107 log += multilineVals
108 }
109
110
111 if e.Error != nil {
112 log += "\n" + indent(l.tabWidth, 2) + "err=" + l.pretty(e.Error)
113 }
114
115 fmt.Fprintln(l.out, log)
116 }
117
118 func (l *clog) caller(e genericr.Entry) string {
119 switch l.logCaller {
120 case None:
121 return ""
122 case All:
123 return fmtCaller(e)
124 case Info:
125 if e.Error != nil {
126 return ""
127 }
128 return fmtCaller(e)
129 case Error:
130 if e.Error == nil {
131 return ""
132 }
133 return fmtCaller(e)
134 default:
135 return ""
136 }
137 }
138
139
140 func fmtCaller(e genericr.Entry) string {
141 return "<" + e.Caller.File + ":" + strconv.Itoa(e.Caller.Line) + "> "
142 }
143
144
145
146
147
148
149
150
151
152
153
154
155
156 func (l *clog) pretty(v any) (ret string) {
157
158 defer func() {
159 if r := recover(); r != nil {
160 ret = fmt.Sprintf("<panic: %s>", r)
161 }
162 }()
163
164
165 if m, ok := v.(logr.Marshaler); ok {
166
167
168 v = m.MarshalLog()
169 }
170
171
172 switch value := v.(type) {
173 case fmt.Stringer:
174 v = value.String()
175 case error:
176 v = value.Error()
177 }
178
179
180 switch v := v.(type) {
181 case bool:
182 return strconv.FormatBool(v)
183 case string:
184 return prettyString(v)
185 case int:
186 return strconv.FormatInt(int64(v), 10)
187 case int8:
188 return strconv.FormatInt(int64(v), 10)
189 case int16:
190 return strconv.FormatInt(int64(v), 10)
191 case int32:
192 return strconv.FormatInt(int64(v), 10)
193 case int64:
194 return strconv.FormatInt(v, 10)
195 case uint:
196 return strconv.FormatUint(uint64(v), 10)
197 case uint8:
198 return strconv.FormatUint(uint64(v), 10)
199 case uint16:
200 return strconv.FormatUint(uint64(v), 10)
201 case uint32:
202 return strconv.FormatUint(uint64(v), 10)
203 case uint64:
204 return strconv.FormatUint(v, 10)
205 case uintptr:
206 return strconv.FormatUint(uint64(v), 10)
207 case float32:
208 return strconv.FormatFloat(float64(v), 'f', -1, 32)
209 case float64:
210 return strconv.FormatFloat(v, 'f', -1, 64)
211 case complex64:
212 return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
213 case complex128:
214 return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
215 }
216
217 t := reflect.TypeOf(v)
218 if t == nil {
219 return "null"
220 }
221 val := reflect.ValueOf(v)
222
223
224 switch t.Kind() {
225 case reflect.Bool:
226 return strconv.FormatBool(val.Bool())
227 case reflect.String:
228 return prettyString(val.String())
229 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
230 return strconv.FormatInt(val.Int(), 10)
231 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
232 return strconv.FormatUint(val.Uint(), 10)
233 case reflect.Float32:
234 return strconv.FormatFloat(val.Float(), 'f', -1, 32)
235 case reflect.Float64:
236 return strconv.FormatFloat(val.Float(), 'f', -1, 64)
237 }
238
239
240
241 buf := bytes.NewBuffer(make([]byte, 0, 256))
242 switch t.Kind() {
243 case reflect.Slice, reflect.Array:
244 if val.Len() == 0 {
245 return "[]"
246 }
247
248 buf.WriteByte('\n')
249 for i := 0; i < val.Len(); i++ {
250 buf.WriteString(indent(l.tabWidth, 2))
251 buf.WriteString(l.pretty(val.Index(i).Interface()))
252
253 if i < val.Len()-1 {
254 buf.WriteByte('\n')
255 }
256 }
257 return buf.String()
258 case reflect.Map:
259 if val.Len() == 0 {
260 return "{}"
261 }
262
263
264 keys := make([]string, 0, val.Len())
265 vals := make(map[string]string, val.Len())
266
267 buf.WriteByte('\n')
268 it := val.MapRange()
269 i := 0
270 for it.Next() {
271 valstr := l.pretty(it.Value().Interface())
272
273 if valstr == "" {
274 i++
275 continue
276 }
277
278 keystr := ""
279
280 if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
281 txt, err := m.MarshalText()
282 if err != nil {
283 keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
284 } else {
285 keystr = string(txt)
286 }
287 keystr = prettyString(keystr)
288 } else {
289
290 keystr = l.pretty(it.Key().Interface())
291 }
292 keys = append(keys, keystr)
293 vals[keystr] = valstr
294 i++
295 }
296
297 tw := tabwriter.NewWriter(buf, 2, l.tabWidth, 2, ' ', 0)
298 sort.Strings(keys)
299 for i, k := range keys {
300 fmt.Fprintf(tw, "%s%s\t%s", indent(l.tabWidth, 2), k, vals[k])
301
302 if i < len(keys)-1 {
303 fmt.Fprint(tw, "\n")
304 }
305 }
306 tw.Flush()
307 return buf.String()
308 }
309
310 return fmt.Sprintf("%+v", v)
311 }
312
313 func prettyString(s string) string {
314
315 if needsEscape(s) {
316 return strconv.Quote(s)
317 }
318 return s
319 }
320
321
322
323 func needsEscape(s string) bool {
324 for _, r := range s {
325 if !strconv.IsPrint(r) || r == '\\' || r == '"' {
326 return true
327 }
328 }
329 return false
330 }
331
332
333
334 func indent(t int, n int) string {
335 str := ""
336 for i := 0; i < n; i++ {
337 for ii := 0; ii < t; ii++ {
338 str += " "
339 }
340 }
341 return str
342 }
343
View as plain text