event: add Event.Clone

Add a method for cloning an Event.

This lets users who plan to deliver many events for the same context
to amortize the context lookup.

Change-Id: I71624a8f42508fb70142c85dd9d390a903f2eb6a
Reviewed-on: https://go-review.googlesource.com/c/exp/+/329789
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/event/adapter/logr/logr.go b/event/adapter/logr/logr.go
index 6877591..2684547 100644
--- a/event/adapter/logr/logr.go
+++ b/event/adapter/logr/logr.go
@@ -14,7 +14,7 @@
 )
 
 type logger struct {
-	ctx       context.Context
+	ev        *event.Event // cloned, never delivered
 	labels    []event.Label
 	nameSep   string
 	name      string
@@ -25,7 +25,7 @@
 
 func NewLogger(ctx context.Context, nameSep string) logr.Logger {
 	return &logger{
-		ctx:     ctx,
+		ev:      event.New(ctx, event.LogKind),
 		nameSep: nameSep,
 	}
 }
@@ -69,10 +69,10 @@
 // variable information.  The key/value pairs should alternate string
 // keys and arbitrary values.
 func (l *logger) Info(msg string, keysAndValues ...interface{}) {
-	ev := event.New(l.ctx, event.LogKind)
-	if ev != nil {
-		l.log(ev, msg, keysAndValues)
+	if l.ev == nil {
+		return
 	}
+	l.log(l.ev.Clone(), msg, keysAndValues)
 }
 
 // Error logs an error, with the given message and key/value pairs as context.
@@ -84,11 +84,12 @@
 // while the err field should be used to attach the actual error that
 // triggered this log line, if present.
 func (l *logger) Error(err error, msg string, keysAndValues ...interface{}) {
-	ev := event.New(l.ctx, event.LogKind)
-	if ev != nil {
-		ev.Labels = append(ev.Labels, event.Value("error", err))
-		l.log(ev, msg, keysAndValues)
+	if l.ev == nil {
+		return
 	}
+	ev := l.ev.Clone()
+	ev.Labels = append(ev.Labels, event.Value("error", err))
+	l.log(ev, msg, keysAndValues)
 }
 
 func (l *logger) log(ev *event.Event, msg string, keysAndValues []interface{}) {
diff --git a/event/adapter/zap/zap.go b/event/adapter/zap/zap.go
index 013a987..df46bcc 100644
--- a/event/adapter/zap/zap.go
+++ b/event/adapter/zap/zap.go
@@ -26,14 +26,14 @@
 )
 
 type core struct {
-	ctx    context.Context
+	ev     *event.Event // cloned but never delivered
 	labels []event.Label
 }
 
 var _ zapcore.Core = (*core)(nil)
 
 func NewCore(ctx context.Context) zapcore.Core {
-	return &core{ctx: ctx}
+	return &core{ev: event.New(ctx, event.LogKind)}
 }
 
 func (c *core) Enabled(level zapcore.Level) bool {
@@ -53,15 +53,19 @@
 }
 
 func (c *core) Write(e zapcore.Entry, fs []zapcore.Field) error {
-	ev := event.New(c.ctx, event.LogKind)
+	if c.ev == nil {
+		return nil
+	}
+	ev := c.ev.Clone()
 	if ev == nil {
 		return nil
 	}
 	ev.At = e.Time
+	// TODO: Add labels more efficiently: compare cap(ev.Labels) to the total number to add,
+	// and allocate a []Label once.
 	ev.Labels = append(ev.Labels, c.labels...)
 	ev.Labels = append(ev.Labels, convertLevel(e.Level).Label())
 	ev.Labels = append(ev.Labels, event.String("name", e.LoggerName))
-	// TODO: add these additional labels more efficiently.
 	if e.Stack != "" {
 		ev.Labels = append(ev.Labels, event.String("stack", e.Stack))
 	}
@@ -89,8 +93,7 @@
 		zapcore.ErrorType:
 		return event.Value(f.Key, f.Interface)
 	case zapcore.DurationType:
-		// TODO: avoid this allocation?
-		return event.Value(f.Key, time.Duration(f.Integer))
+		return event.Duration(f.Key, time.Duration(f.Integer))
 	case zapcore.Float64Type:
 		return event.Float64(f.Key, math.Float64frombits(uint64(f.Integer)))
 	case zapcore.Float32Type:
diff --git a/event/event.go b/event/event.go
index 7c3aed6..485e706 100644
--- a/event/event.go
+++ b/event/event.go
@@ -95,16 +95,26 @@
 	return ev
 }
 
+// Clone makes a deep copy of the Event.
+// Deliver can be called on both Events independently.
+func (ev *Event) Clone() *Event {
+	ev2 := eventPool.Get().(*Event)
+	*ev2 = *ev
+	ev2.Labels = append(ev2.labels[:0], ev.Labels...)
+	return ev2
+}
+
 // Deliver the event to the exporter that was found in New.
 // This also returns the event to the pool, it is an error to do anything
 // with the event after it is delivered.
 func (ev *Event) Deliver() context.Context {
+	e := ev.target.exporter
 	// get the event ready to send
-	ev.target.exporter.prepare(ev)
+	e.prepare(ev)
 	// now hold the lock while we deliver the event
-	ev.target.exporter.mu.Lock()
-	defer ev.target.exporter.mu.Unlock()
-	ctx := ev.target.exporter.handler.Event(ev.ctx, ev)
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	ctx := e.handler.Event(ev.ctx, ev)
 	eventPool.Put(ev)
 	return ctx
 }