func (fs *EventFormatString) collectFields(
	ctx *eventEvalContext,
	event common.MapStr,
) error {
	for _, fi := range fs.fields {
		s, err := fieldString(event, fi.path)
		if err != nil {
			if fi.required {
				return err
			}
			s = ""
		}
		ctx.keys = append(ctx.keys, s)
	}
	if fs.timestamp {
		timestamp, found := event["@timestamp"]
		if !found {
			return errors.New("missing timestamp")
		}
		switch t := timestamp.(type) {
		case common.Time:
			ctx.ts = time.Time(t)
		case time.Time:
			ctx.ts = t
		default:
			return errors.New("unknown timestamp type")
		}
	}
	return nil
}
collectFields tries to extract and convert all required fields into an array
of strings.
package fmtstr
import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/elastic/beats/libbeat/common"
	"github.com/elastic/beats/libbeat/common/dtfmt"
	"github.com/elastic/beats/libbeat/logp"
)
type EventFormatString struct {
	formatter StringFormatter
	fields    []fieldInfo
	timestamp bool
}
type eventFieldEvaler struct {
	index int
}
type defaultEventFieldEvaler struct {
	index        int
	defaultValue string
}
type eventTimestampEvaler struct {
	formatter *dtfmt.Formatter
}
type eventFieldCompiler struct {
	keys      map[string]keyInfo
	timestamp bool
	index     int
}
type fieldInfo struct {
	path     string
	required bool
}
type keyInfo struct {
	index    int
	required bool
}
type eventEvalContext struct {
	keys []string
	ts   time.Time
	buf  *bytes.Buffer
}
var (
	errMissingKeys   = errors.New("missing keys")
	errConvertString = errors.New("can not convert to string")
)
var eventCtxPool = &sync.Pool{
	New: func() interface{} { return &eventEvalContext{} },
}
func newEventCtx(sz int) *eventEvalContext {
	ctx := eventCtxPool.Get().(*eventEvalContext)
	if ctx.keys == nil || cap(ctx.keys) < sz {
		ctx.keys = make([]string, 0, sz)
	} else {
		ctx.keys = ctx.keys[:0]
	}
	return ctx
}
func releaseCtx(c *eventEvalContext) {
	eventCtxPool.Put(c)
}
func MustCompileEvent(in string) *EventFormatString {
	fs, err := CompileEvent(in)
	if err != nil {
		panic(err)
	}
	return fs
}
func CompileEvent(in string) (*EventFormatString, error) {
	ctx := &eventEvalContext{}
	efComp := &eventFieldCompiler{
		keys:      map[string]keyInfo{},
		index:     0,
		timestamp: false,
	}
	sf, err := Compile(in, efComp.compileExpression)
	if err != nil {
		return nil, err
	}
	keys := make([]fieldInfo, len(efComp.keys))
	for path, info := range efComp.keys {
		keys[info.index] = fieldInfo{
			path:     path,
			required: info.required,
		}
	}
	ctx.keys = make([]string, len(keys))
	efs := &EventFormatString{
		formatter: sf,
		fields:    keys,
		timestamp: efComp.timestamp,
	}
	return efs, nil
}
func (fs *EventFormatString) Unpack(v interface{}) error {
	s, err := tryConvString(v)
	if err != nil {
		return err
	}
	tmp, err := CompileEvent(s)
	if err != nil {
		return err
	}
	*fs = *tmp
	return nil
}
func (fs *EventFormatString) NumFields() int {
	return len(fs.fields)
}
func (fs *EventFormatString) Fields() []string {
	var fields []string
	for _, fi := range fs.fields {
		if fi.required {
			fields = append(fields, fi.path)
		}
	}
	return fields
}
func (fs *EventFormatString) Run(event common.MapStr) (string, error) {
	ctx := newEventCtx(len(fs.fields))
	defer releaseCtx(ctx)
	if ctx.buf == nil {
		ctx.buf = bytes.NewBuffer(nil)
	} else {
		ctx.buf.Reset()
	}
	if err := fs.collectFields(ctx, event); err != nil {
		return "", err
	}
	err := fs.formatter.Eval(ctx, ctx.buf)
	if err != nil {
		return "", err
	}
	return ctx.buf.String(), nil
}
func (fs *EventFormatString) RunBytes(event common.MapStr) ([]byte, error) {
	ctx := newEventCtx(len(fs.fields))
	defer releaseCtx(ctx)
	buf := bytes.NewBuffer(nil)
	if err := fs.collectFields(ctx, event); err != nil {
		return nil, err
	}
	err := fs.formatter.Eval(ctx, buf)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}
func (fs *EventFormatString) Eval(out *bytes.Buffer, event common.MapStr) error {
	ctx := newEventCtx(len(fs.fields))
	defer releaseCtx(ctx)
	if err := fs.collectFields(ctx, event); err != nil {
		return err
	}
	return fs.formatter.Eval(ctx, out)
}
func (fs *EventFormatString) IsConst() bool {
	return fs.formatter.IsConst()
}