Skip to content

Commit c007029

Browse files
committed
play with logfmt
1 parent fbe4576 commit c007029

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

internal/entryhuman/logfmt/encoder.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package logfmt
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"reflect"
7+
"strconv"
8+
"unicode"
9+
)
10+
11+
type Encoder struct {
12+
w io.Writer
13+
FormatKey func(key string) string
14+
// FormatPrimitiveValue is used to format primitive values (strings, ints,
15+
// floats, etc). It is not used for arrays or objects.
16+
FormatPrimitiveValue func(value interface{}) string
17+
}
18+
19+
func NewEncoder(w io.Writer) *Encoder {
20+
return &Encoder{
21+
FormatKey: func(key string) string { return key },
22+
FormatPrimitiveValue: func(value interface{}) string { return fmt.Sprintf("%+v", value) },
23+
w: w,
24+
}
25+
}
26+
27+
func isPrimitive(typ reflect.Type) bool {
28+
switch typ.Kind() {
29+
case reflect.Bool:
30+
return true
31+
case reflect.String:
32+
return true
33+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
34+
return true
35+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
36+
return true
37+
case reflect.Float32, reflect.Float64:
38+
return true
39+
case reflect.Complex64, reflect.Complex128:
40+
return true
41+
default:
42+
return false
43+
}
44+
}
45+
46+
// Encode encodes the given message to the writer. For flat objects, the
47+
// output resembles key=value pairs. For nested objects, a surrounding { } is
48+
// used. For arrays, a surrounding [ ] is used.
49+
func (e *Encoder) Encode(m interface{}) error {
50+
typ := reflect.TypeOf(m)
51+
if typ.Kind() == reflect.Ptr {
52+
typ = typ.Elem()
53+
}
54+
55+
if isPrimitive(typ) {
56+
e.w.Write([]byte(e.FormatPrimitiveValue(m)))
57+
return nil
58+
}
59+
60+
switch typ.Kind() {
61+
case reflect.Struct:
62+
v := reflect.ValueOf(m)
63+
for i := 0; i < typ.NumField(); i++ {
64+
field := typ.Field(i)
65+
value := v.Field(i)
66+
if !value.CanInterface() {
67+
continue
68+
}
69+
if value.IsZero() {
70+
continue
71+
}
72+
if field.Anonymous {
73+
if err := e.Encode(value.Interface()); err != nil {
74+
return err
75+
}
76+
continue
77+
}
78+
if e.FormatKey != nil {
79+
e.w.Write([]byte(e.FormatKey(field.Name)))
80+
} else {
81+
e.w.Write([]byte(field.Name))
82+
}
83+
e.w.Write([]byte("="))
84+
if e.FormatPrimitiveValue != nil {
85+
e.w.Write([]byte(e.FormatPrimitiveValue(value.Interface())))
86+
} else {
87+
e.w.Write([]byte(formatValue(value.Interface())))
88+
}
89+
default:
90+
return fmt.Errorf("unsupported type %T", m)
91+
}
92+
}
93+
94+
// quotes quotes a string so that it is suitable
95+
// as a key for a map or in general some output that
96+
// cannot span multiple lines or have weird characters.
97+
func quote(key string) string {
98+
// strconv.Quote does not quote an empty string so we need this.
99+
if key == "" {
100+
return `""`
101+
}
102+
103+
var hasSpace bool
104+
for _, r := range key {
105+
if unicode.IsSpace(r) {
106+
hasSpace = true
107+
break
108+
}
109+
}
110+
quoted := strconv.Quote(key)
111+
// If the key doesn't need to be quoted, don't quote it.
112+
// We do not use strconv.CanBackquote because it doesn't
113+
// account tabs.
114+
if !hasSpace && quoted[1:len(quoted)-1] == key {
115+
return key
116+
}
117+
return quoted
118+
}

0 commit comments

Comments
 (0)