event: add Bytes value type and simplify the printer

Change-Id: Ia10aaf070ea545c70683d657aca010b754b63aed
Reviewed-on: https://go-review.googlesource.com/c/exp/+/324149
Trust: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/event/adapter/logfmt/logfmt.go b/event/adapter/logfmt/logfmt.go
index 40741cf..1a4a8ed 100644
--- a/event/adapter/logfmt/logfmt.go
+++ b/event/adapter/logfmt/logfmt.go
@@ -20,7 +20,8 @@
 	io.Writer
 	io.StringWriter
 
-	buf [bufCap]byte
+	buf     [bufCap]byte
+	needSep bool
 }
 
 type stringWriter struct {
@@ -41,67 +42,58 @@
 
 func (p *Printer) Log(ctx context.Context, ev *event.Event) {
 	p.Event("log", ev)
-	p.WriteString("\n")
 }
 
 func (p *Printer) Metric(ctx context.Context, ev *event.Event) {
 	p.Event("metric", ev)
-	p.WriteString("\n")
 }
 
 func (p *Printer) Annotate(ctx context.Context, ev *event.Event) {
 	p.Event("annotate", ev)
-	p.WriteString("\n")
 }
 
 func (p *Printer) Start(ctx context.Context, ev *event.Event) context.Context {
 	p.Event("start", ev)
-	p.WriteString("\n")
 	return ctx
 }
 
 func (p *Printer) End(ctx context.Context, ev *event.Event) {
 	p.Event("end", ev)
-	p.WriteString("\n")
 }
 
 func (p *Printer) Event(kind string, ev *event.Event) {
 	const timeFormat = "2006-01-02T15:04:05"
+	p.needSep = false
 	if !ev.At.IsZero() {
-		p.WriteString("time=")
-		p.Write(ev.At.AppendFormat(p.buf[:0], timeFormat))
-		p.WriteString(" ")
+		p.label("time", event.BytesOf(ev.At.AppendFormat(p.buf[:0], timeFormat)))
 	}
 
-	p.WriteString("id=")
-	p.Write(strconv.AppendUint(p.buf[:0], ev.ID, 10))
+	p.label("id", event.BytesOf(strconv.AppendUint(p.buf[:0], ev.ID, 10)))
 	if ev.Parent != 0 {
-		p.WriteString(" span=")
-		p.Write(strconv.AppendUint(p.buf[:0], ev.Parent, 10))
+		p.label("span", event.BytesOf(strconv.AppendUint(p.buf[:0], ev.Parent, 10)))
 	}
 
-	p.WriteString(" kind=")
-	p.WriteString(kind)
+	p.label("kind", event.StringOf(kind))
 
 	for _, l := range ev.Labels {
 		if l.Name == "" {
 			continue
 		}
-		p.WriteString(" ")
-		p.Label(&l)
+		p.Label(l)
 	}
+	p.WriteString("\n")
 }
 
-func (p *Printer) Label(l *event.Label) {
-	p.Ident(l.Name)
-	p.WriteString("=")
-	p.Value(&l.Value)
+func (p *Printer) Label(l event.Label) {
+	p.label(l.Name, l.Value)
 }
 
-func (p *Printer) Value(v *event.Value) {
+func (p *Printer) Value(v event.Value) {
 	switch {
 	case v.IsString():
 		p.Quote(v.String())
+	case v.IsBytes():
+		p.Bytes(v.Bytes())
 	case v.IsInt64():
 		p.Write(strconv.AppendInt(p.buf[:0], v.Int64(), 10))
 	case v.IsUint64():
@@ -151,6 +143,27 @@
 	p.WriteString(`"`)
 }
 
+// Bytes writes a byte array in string form to the printer.
+func (p *Printer) Bytes(buf []byte) {
+	//TODO: non asci chars need escaping
+	p.Write(buf)
+}
+
+func (p *Printer) label(name string, value event.Value) {
+	if name == "" {
+		return
+	}
+	if p.needSep {
+		p.WriteString(" ")
+	}
+	p.needSep = true
+	p.Ident(name)
+	if value.HasValue() {
+		p.WriteString("=")
+		p.Value(value)
+	}
+}
+
 func needQuote(s string) bool {
 	for _, r := range s {
 		if len(quoteRune(r)) > 0 {
diff --git a/event/label.go b/event/label.go
index 736b3f5..a867310 100644
--- a/event/label.go
+++ b/event/label.go
@@ -33,6 +33,9 @@
 // stringptr is used in untyped when the Value is a string
 type stringptr unsafe.Pointer
 
+// bytesptr is used in untyped when the Value is a byte slice
+type bytesptr unsafe.Pointer
+
 // int64Kind is used in untyped when the Value is a signed integer
 type int64Kind struct{}
 
@@ -133,12 +136,38 @@
 	}
 }
 
-// IsString returns true if the value was built with SetString.
+// IsString returns true if the value was built with StringOf.
 func (v Value) IsString() bool {
 	_, ok := v.untyped.(stringptr)
 	return ok
 }
 
+// BytesOf returns a new Value for a string.
+func BytesOf(data []byte) Value {
+	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data))
+	return Value{packed: uint64(hdr.Len), untyped: bytesptr(hdr.Data)}
+}
+
+// Bytes returns the value as a bytes array.
+func (v Value) Bytes() []byte {
+	bp, ok := v.untyped.(bytesptr)
+	if !ok {
+		panic("Bytes called on non []byte value")
+	}
+	var buf []byte
+	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
+	hdr.Data = uintptr(bp)
+	hdr.Len = int(v.packed)
+	hdr.Cap = hdr.Len // TODO: is this safe?
+	return buf
+}
+
+// IsBytes returns true if the value was built with BytesOf.
+func (v Value) IsBytes() bool {
+	_, ok := v.untyped.(bytesptr)
+	return ok
+}
+
 // Int64Of returns a new Value for a signed integer.
 func Int64Of(u int64) Value {
 	return Value{packed: uint64(u), untyped: int64Kind{}}