slog: implement scopes and groups for TextHandler
This initial implementation is slow and allocates too much.
Future CLs will optimize it.
Change-Id: Ic06f7e4c0d51ea12f320675b3a1b770122d0a786
Reviewed-on: https://go-review.googlesource.com/c/exp/+/438998
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/slog/handler.go b/slog/handler.go
index ffd746c..06131a4 100644
--- a/slog/handler.go
+++ b/slog/handler.go
@@ -179,9 +179,11 @@
for _, a := range as {
state.appendAttr(a)
}
- // Remember how many open scopes are in preformattedAttrs,
- // so we don't open them again when we handle a Record.
- h2.nOpenScopes = len(h2.scopes)
+ if h2.json {
+ // Remember how many open scopes are in preformattedAttrs,
+ // so we don't open them again when we handle a Record.
+ h2.nOpenScopes = len(h2.scopes)
+ }
return h2
}
@@ -281,9 +283,10 @@
// The initial value of sep determines whether to emit a separator
// before the next key, after which it stays true.
type handleState struct {
- h *commonHandler
- buf *buffer.Buffer
- sep string // separator to write before next key
+ h *commonHandler
+ buf *buffer.Buffer
+ sep string // separator to write before next key
+ prefix string // for text: key prefix
}
func (s *handleState) openScopes() {
@@ -307,7 +310,8 @@
s.buf.WriteByte('{')
s.sep = ""
} else {
- panic("unimplemented")
+ // TODO: fix escaping to make it easy to recover the original.
+ s.prefix += escapeDots(name) + "."
}
}
@@ -316,7 +320,7 @@
if s.h.json {
s.buf.WriteByte('}')
} else {
- panic("unimplemented -- but it will use name, I assure you")
+ s.prefix = s.prefix[:len(s.prefix)-len(name)-1]
}
s.sep = s.h.attrSep()
}
@@ -352,6 +356,8 @@
func (s *handleState) appendKey(key string) {
s.buf.WriteString(s.sep)
+ // TODO: make sure the entire prefix+key is quoted if any part of it needs to be.
+ s.buf.WriteString(s.prefix)
s.appendString(key)
if s.h.json {
s.buf.WriteByte(':')
diff --git a/slog/handler_test.go b/slog/handler_test.go
index d4c7a43..5a0ada7 100644
--- a/slog/handler_test.go
+++ b/slog/handler_test.go
@@ -160,25 +160,25 @@
Int("d", 4)),
Int("e", 5),
},
- wantText: "SKIP",
+ wantText: "msg=message a=1 g.b=2 g.h.c=3 g.d=4 e=5",
wantJSON: `{"msg":"message","a":1,"g":{"b":2,"h":{"c":3},"d":4},"e":5}`,
},
{
name: "empty group",
replace: removeKeys(timeKey, levelKey),
attrs: []Attr{Group("g"), Group("h", Int("a", 1))},
- wantText: "SKIP",
+ wantText: "msg=message h.a=1",
wantJSON: `{"msg":"message","g":{},"h":{"a":1}}`,
},
{
- name: "Marshaler",
+ name: "LogValuer",
replace: removeKeys(timeKey, levelKey),
attrs: []Attr{
Int("a", 1),
- Any("name", marshalName{"Ren", "Hoek"}),
+ Any("name", logValueName{"Ren", "Hoek"}),
Int("b", 2),
},
- wantText: "SKIP",
+ wantText: "msg=message a=1 name.first=Ren name.last=Hoek b=2",
wantJSON: `{"msg":"message","a":1,"name":{"first":"Ren","last":"Hoek"},"b":2}`,
},
{
@@ -186,7 +186,7 @@
replace: removeKeys(timeKey, levelKey),
with: func(h Handler) Handler { return h.With(preAttrs).WithScope("s") },
attrs: attrs,
- wantText: "SKIP",
+ wantText: "msg=message pre=3 x=y s.a=one s.b=2",
wantJSON: `{"msg":"message","pre":3,"x":"y","s":{"a":"one","b":2}}`,
},
{
@@ -199,7 +199,7 @@
WithScope("s2")
},
attrs: attrs,
- wantText: "SKIP",
+ wantText: "msg=message p1=1 s1.p2=2 s1.s2.a=one s1.s2.b=2",
wantJSON: `{"msg":"message","p1":1,"s1":{"p2":2,"s2":{"a":"one","b":2}}}`,
},
{
@@ -211,7 +211,7 @@
WithScope("s2")
},
attrs: attrs,
- wantText: "SKIP",
+ wantText: "msg=message p1=1 s1.s2.a=one s1.s2.b=2",
wantJSON: `{"msg":"message","p1":1,"s1":{"s2":{"a":"one","b":2}}}`,
},
} {
@@ -229,9 +229,6 @@
{"json", opts.NewJSONHandler(&buf), test.wantJSON},
} {
t.Run(handler.name, func(t *testing.T) {
- if handler.want == "SKIP" {
- t.Skip("feature unimplemented")
- }
h := handler.h
if test.with != nil {
h = test.with(h)
@@ -255,11 +252,11 @@
return a
}
-type marshalName struct {
+type logValueName struct {
first, last string
}
-func (n marshalName) LogValue() Value {
+func (n logValueName) LogValue() Value {
return GroupValue(
String("first", n.first),
String("last", n.last))
diff --git a/slog/text_handler.go b/slog/text_handler.go
index b5daab7..2268f76 100644
--- a/slog/text_handler.go
+++ b/slog/text_handler.go
@@ -8,6 +8,7 @@
"encoding"
"fmt"
"io"
+ "strings"
"unicode"
"unicode/utf8"
)
@@ -99,6 +100,13 @@
return nil
}
+// escapeDots replaces all '.' runes in s with the equivalent hex escape code.
+// This allows the scope/group structure to be retrieved from the dot-separated
+// components of a key.
+func escapeDots(s string) string {
+ return strings.ReplaceAll(s, ".", `\x2E`)
+}
+
func needsQuoting(s string) bool {
for i := 0; i < len(s); {
b := s[i]