1
17
18
19
20 package genericr
21
22 import (
23 "bytes"
24 "encoding/json"
25 "fmt"
26 "path/filepath"
27 "regexp"
28 "runtime"
29 "sort"
30 "strconv"
31
32 "github.com/go-logr/logr"
33 )
34
35
36 type Entry struct {
37 Level int
38 Name string
39 NameParts []string
40 Message string
41 Error error
42 Fields []interface{}
43
44
45 Caller runtime.Frame
46 CallerDepth int
47 }
48
49
50
51
52
53 func (e Entry) String() string {
54 buf := bytes.NewBuffer(make([]byte, 0, 160))
55 buf.WriteByte('[')
56 buf.WriteString(strconv.Itoa(e.Level))
57 buf.WriteByte(']')
58 buf.WriteByte(' ')
59 if e.Caller.File != "" || e.Caller.Line != 0 {
60 buf.WriteString(e.CallerShort())
61 buf.WriteByte(' ')
62 }
63 buf.WriteString(e.Name)
64 buf.WriteByte(' ')
65 buf.WriteString(pretty(e.Message))
66 if e.Error != nil {
67 buf.WriteString(" error=")
68 buf.WriteString(pretty(e.Error.Error()))
69 }
70 if len(e.Fields) > 0 {
71 buf.WriteByte(' ')
72 buf.WriteString(flatten(e.Fields...))
73 }
74 return buf.String()
75 }
76
77
78
79 func (e Entry) FieldsMap() map[string]interface{} {
80 return fieldsMap(e.Fields)
81 }
82
83
84 func (e Entry) CallerShort() string {
85 if e.Caller.File == "" && e.Caller.Line == 0 {
86 return ""
87 }
88 _, fname := filepath.Split(e.Caller.File)
89 return fmt.Sprintf("%s:%d", fname, e.Caller.Line)
90 }
91
92
93 type LogFunc func(e Entry)
94
95
96 func New(f LogFunc) LogSink {
97 log := LogSink{
98 f: f,
99 verbosity: 1000,
100 }
101 return log
102 }
103
104
105
106 type LogSink struct {
107 f LogFunc
108 level int
109 verbosity int
110 nameParts []string
111 name string
112 values []interface{}
113 caller bool
114 depth int
115 }
116
117
118
119 func (l LogSink) WithVerbosity(level int) LogSink {
120 l.verbosity = level
121 return l
122 }
123
124
125
126
127
128
129 func (l LogSink) WithCaller(enabled bool) LogSink {
130 l.caller = enabled
131 return l
132 }
133
134
135 func (l LogSink) WithCallDepth(depth int) logr.LogSink {
136 l.depth += depth
137 return l
138 }
139
140 func (l LogSink) Init(info logr.RuntimeInfo) {
141 }
142
143 func (l LogSink) Info(level int, msg string, kvList ...interface{}) {
144 l.logMessage(level, nil, msg, kvList)
145 }
146
147 func (l LogSink) Enabled(level int) bool {
148 return l.verbosity >= level
149 }
150
151 func (l LogSink) Error(err error, msg string, kvList ...interface{}) {
152 l.logMessage(0, err, msg, kvList)
153 }
154
155 func (l LogSink) WithName(name string) logr.LogSink {
156
157
158
159 if len(l.nameParts) == 0 {
160 l.nameParts = []string{name}
161 l.name = name
162 } else {
163 n := len(l.nameParts)
164 l.nameParts = append(l.nameParts[:n:n], name)
165 l.name += "." + name
166 }
167 return l
168 }
169
170 func (l LogSink) WithValues(kvList ...interface{}) logr.LogSink {
171 if len(kvList) == 0 {
172 return l
173 }
174 if len(kvList)%2 == 1 {
175
176 kvList = append(kvList, nil)
177 }
178 if len(l.values) == 0 {
179 l.values = kvList
180 } else {
181 n := len(l.values)
182 l.values = append(l.values[:n:n], kvList...)
183 }
184 return l
185 }
186
187
188 func (l LogSink) logMessage(level int, err error, msg string, kvList []interface{}) {
189 var out []interface{}
190 if len(l.values) == 0 && len(kvList) > 0 {
191 out = kvList
192 } else if len(l.values) > 0 && len(kvList) == 0 {
193 out = l.values
194 } else {
195 out = make([]interface{}, len(l.values)+len(kvList))
196 copy(out, l.values)
197 copy(out[len(l.values):], kvList)
198 }
199
200 calldepth := 3 + l.depth
201 var caller runtime.Frame
202 if l.caller {
203 pc := make([]uintptr, 1)
204 if n := runtime.Callers(calldepth+1, pc[:]); n >= 1 {
205 caller, _ = runtime.CallersFrames(pc).Next()
206 }
207 }
208
209 l.f(Entry{
210 Level: level,
211 Name: l.name,
212 NameParts: l.nameParts,
213 Message: msg,
214 Error: err,
215 Fields: out,
216 Caller: caller,
217 CallerDepth: calldepth + 1,
218 })
219 }
220
221
222 var _ logr.LogSink = LogSink{}
223 var _ logr.CallDepthLogSink = LogSink{}
224
225
226
227 var safeString = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
228
229 func prettyKey(v string) string {
230 if safeString.MatchString(v) {
231 return v
232 } else {
233 return pretty(v)
234 }
235 }
236
237 func pretty(value interface{}) string {
238 switch v := value.(type) {
239 case error:
240 value = v.Error()
241 case []byte:
242 return fmt.Sprintf(`"% x"`, v)
243 }
244 jb, err := json.Marshal(value)
245 if err != nil {
246 jb, _ = json.Marshal(fmt.Sprintf("%q", value))
247 }
248 return string(jb)
249 }
250
251
252 func flatten(kvList ...interface{}) string {
253 vals := fieldsMap(kvList)
254 keys := make([]string, 0, len(vals))
255 for k := range vals {
256 keys = append(keys, k)
257 }
258 sort.Strings(keys)
259 buf := bytes.Buffer{}
260 for i, k := range keys {
261 v := vals[k]
262 if i > 0 {
263 buf.WriteRune(' ')
264 }
265 buf.WriteString(prettyKey(k))
266 buf.WriteString("=")
267 buf.WriteString(pretty(v))
268 }
269 return buf.String()
270 }
271
272
273 func fieldsMap(fields []interface{}) map[string]interface{} {
274 m := make(map[string]interface{}, len(fields))
275 for i := 0; i < len(fields); i += 2 {
276 k, ok := fields[i].(string)
277 if !ok {
278 k = fmt.Sprintf("!(%#v)", fields[i])
279 }
280 var v interface{}
281 if i+1 < len(fields) {
282 v = fields[i+1]
283 }
284 m[k] = v
285 }
286 return m
287 }
288
View as plain text