From 515e920a0aa14940e32fe65ac6f0e6e5beb01754 Mon Sep 17 00:00:00 2001 From: Kevin Parsons Date: Thu, 10 Jan 2019 11:08:01 -0800 Subject: [PATCH 1/2] Support logging more Go types to ETW Signed-off-by: Kevin Parsons --- internal/etw/eventdata.go | 20 +++ internal/etw/fieldopt.go | 341 ++++++++++++++++++++++++++++++++++++- pkg/etwlogrus/hook.go | 128 +++++++++++++- pkg/etwlogrus/hook_test.go | 111 ++++++++++++ 4 files changed, 593 insertions(+), 7 deletions(-) create mode 100644 pkg/etwlogrus/hook_test.go diff --git a/internal/etw/eventdata.go b/internal/etw/eventdata.go index fc270e19..85307343 100644 --- a/internal/etw/eventdata.go +++ b/internal/etw/eventdata.go @@ -24,6 +24,26 @@ func (ed *EventData) WriteString(data string) { ed.buffer.WriteByte(0) } +// WriteInt8 appends a int8 to the buffer. +func (ed *EventData) WriteInt8(value int8) { + ed.buffer.WriteByte(uint8(value)) +} + +// WriteInt16 appends a int16 to the buffer. +func (ed *EventData) WriteInt16(value int16) { + binary.Write(&ed.buffer, binary.LittleEndian, value) +} + +// WriteInt32 appends a int32 to the buffer. +func (ed *EventData) WriteInt32(value int32) { + binary.Write(&ed.buffer, binary.LittleEndian, value) +} + +// WriteInt64 appends a int64 to the buffer. +func (ed *EventData) WriteInt64(value int64) { + binary.Write(&ed.buffer, binary.LittleEndian, value) +} + // WriteUint8 appends a uint8 to the buffer. func (ed *EventData) WriteUint8(value uint8) { ed.buffer.WriteByte(value) diff --git a/internal/etw/fieldopt.go b/internal/etw/fieldopt.go index 0e6a20fe..76f63bcb 100644 --- a/internal/etw/fieldopt.go +++ b/internal/etw/fieldopt.go @@ -1,5 +1,10 @@ package etw +import ( + "math" + "unsafe" +) + // FieldOpt defines the option function type that can be passed to // Provider.WriteEvent to add fields to the event. type FieldOpt func(em *EventMetadata, ed *EventData) @@ -9,6 +14,33 @@ func WithFields(opts ...FieldOpt) []FieldOpt { return opts } +// BoolField adds a single bool field to the event. +func BoolField(name string, value bool) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeUint8, OutTypeBoolean, 0) + bool8 := uint8(0) + if value { + bool8 = uint8(1) + } + ed.WriteUint8(bool8) + } +} + +// BoolArray adds an array of bool to the event. +func BoolArray(name string, values []bool) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeUint8, OutTypeBoolean, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + bool8 := uint8(0) + if v { + bool8 = uint8(1) + } + ed.WriteUint8(bool8) + } + } +} + // StringField adds a single string field to the event. func StringField(name string, value string) FieldOpt { return func(em *EventMetadata, ed *EventData) { @@ -17,7 +49,7 @@ func StringField(name string, value string) FieldOpt { } } -// StringArray adds an array of strings to the event. +// StringArray adds an array of string to the event. func StringArray(name string, values []string) FieldOpt { return func(em *EventMetadata, ed *EventData) { em.WriteArray(name, InTypeANSIString, OutTypeUTF8, 0) @@ -28,6 +60,313 @@ func StringArray(name string, values []string) FieldOpt { } } +// IntField adds a single int field to the event. +func IntField(name string, value int) FieldOpt { + switch unsafe.Sizeof(value) { + case 4: + return Int32Field(name, int32(value)) + case 8: + return Int64Field(name, int64(value)) + default: + panic("Unsupported int size") + } +} + +// IntArray adds an array of int to the event. +func IntArray(name string, values []int) FieldOpt { + inType := InTypeNull + var writeItem func(*EventData, int) + switch unsafe.Sizeof(values[0]) { + case 4: + inType = InTypeInt32 + writeItem = func(ed *EventData, item int) { ed.WriteInt32(int32(item)) } + case 8: + inType = InTypeInt64 + writeItem = func(ed *EventData, item int) { ed.WriteInt64(int64(item)) } + default: + panic("Unsupported int size") + } + + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, inType, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + writeItem(ed, v) + } + } +} + +// Int8Field adds a single int8 field to the event. +func Int8Field(name string, value int8) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeInt8, OutTypeDefault, 0) + ed.WriteInt8(value) + } +} + +// Int8Array adds an array of int8 to the event. +func Int8Array(name string, values []int8) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeInt8, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteInt8(v) + } + } +} + +// Int16Field adds a single int16 field to the event. +func Int16Field(name string, value int16) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeInt16, OutTypeDefault, 0) + ed.WriteInt16(value) + } +} + +// Int16Array adds an array of int16 to the event. +func Int16Array(name string, values []int16) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeInt16, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteInt16(v) + } + } +} + +// Int32Field adds a single int32 field to the event. +func Int32Field(name string, value int32) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeInt32, OutTypeDefault, 0) + ed.WriteInt32(value) + } +} + +// Int32Array adds an array of int32 to the event. +func Int32Array(name string, values []int32) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeInt32, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteInt32(v) + } + } +} + +// Int64Field adds a single int64 field to the event. +func Int64Field(name string, value int64) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeInt64, OutTypeDefault, 0) + ed.WriteInt64(value) + } +} + +// Int64Array adds an array of int64 to the event. +func Int64Array(name string, values []int64) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeInt64, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteInt64(v) + } + } +} + +// UintField adds a single uint field to the event. +func UintField(name string, value uint) FieldOpt { + switch unsafe.Sizeof(value) { + case 4: + return Uint32Field(name, uint32(value)) + case 8: + return Uint64Field(name, uint64(value)) + default: + panic("Unsupported uint size") + } +} + +// UintArray adds an array of uint to the event. +func UintArray(name string, values []uint) FieldOpt { + inType := InTypeNull + var writeItem func(*EventData, uint) + switch unsafe.Sizeof(values[0]) { + case 4: + inType = InTypeUint32 + writeItem = func(ed *EventData, item uint) { ed.WriteUint32(uint32(item)) } + case 8: + inType = InTypeUint64 + writeItem = func(ed *EventData, item uint) { ed.WriteUint64(uint64(item)) } + default: + panic("Unsupported uint size") + } + + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, inType, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + writeItem(ed, v) + } + } +} + +// Uint8Field adds a single uint8 field to the event. +func Uint8Field(name string, value uint8) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeUint8, OutTypeDefault, 0) + ed.WriteUint8(value) + } +} + +// Uint8Array adds an array of uint8 to the event. +func Uint8Array(name string, values []uint8) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeUint8, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteUint8(v) + } + } +} + +// Uint16Field adds a single uint16 field to the event. +func Uint16Field(name string, value uint16) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeUint16, OutTypeDefault, 0) + ed.WriteUint16(value) + } +} + +// Uint16Array adds an array of uint16 to the event. +func Uint16Array(name string, values []uint16) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeUint16, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteUint16(v) + } + } +} + +// Uint32Field adds a single uint32 field to the event. +func Uint32Field(name string, value uint32) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeUint32, OutTypeDefault, 0) + ed.WriteUint32(value) + } +} + +// Uint32Array adds an array of uint32 to the event. +func Uint32Array(name string, values []uint32) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeUint32, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteUint32(v) + } + } +} + +// Uint64Field adds a single uint64 field to the event. +func Uint64Field(name string, value uint64) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeUint64, OutTypeDefault, 0) + ed.WriteUint64(value) + } +} + +// Uint64Array adds an array of uint64 to the event. +func Uint64Array(name string, values []uint64) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeUint64, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteUint64(v) + } + } +} + +// UintptrField adds a single uintptr field to the event. +func UintptrField(name string, value uintptr) FieldOpt { + inType := InTypeNull + var writeItem func(*EventData, uintptr) + switch unsafe.Sizeof(value) { + case 4: + inType = InTypeHexInt32 + writeItem = func(ed *EventData, item uintptr) { ed.WriteUint32(uint32(item)) } + case 8: + inType = InTypeHexInt64 + writeItem = func(ed *EventData, item uintptr) { ed.WriteUint64(uint64(item)) } + default: + panic("Unsupported uintptr size") + } + + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, inType, OutTypeDefault, 0) + writeItem(ed, value) + } +} + +// UintptrArray adds an array of uintptr to the event. +func UintptrArray(name string, values []uintptr) FieldOpt { + inType := InTypeNull + var writeItem func(*EventData, uintptr) + switch unsafe.Sizeof(values[0]) { + case 4: + inType = InTypeHexInt32 + writeItem = func(ed *EventData, item uintptr) { ed.WriteUint32(uint32(item)) } + case 8: + inType = InTypeHexInt64 + writeItem = func(ed *EventData, item uintptr) { ed.WriteUint64(uint64(item)) } + default: + panic("Unsupported uintptr size") + } + + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, inType, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + writeItem(ed, v) + } + } +} + +// Float32Field adds a single float32 field to the event. +func Float32Field(name string, value float32) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeFloat, OutTypeDefault, 0) + ed.WriteUint32(math.Float32bits(value)) + } +} + +// Float32Array adds an array of float32 to the event. +func Float32Array(name string, values []float32) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeFloat, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteUint32(math.Float32bits(v)) + } + } +} + +// Float64Field adds a single float64 field to the event. +func Float64Field(name string, value float64) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteField(name, InTypeDouble, OutTypeDefault, 0) + ed.WriteUint64(math.Float64bits(value)) + } +} + +// Float64Array adds an array of float64 to the event. +func Float64Array(name string, values []float64) FieldOpt { + return func(em *EventMetadata, ed *EventData) { + em.WriteArray(name, InTypeDouble, OutTypeDefault, 0) + ed.WriteUint16(uint16(len(values))) + for _, v := range values { + ed.WriteUint64(math.Float64bits(v)) + } + } +} + // Struct adds a nested struct to the event, the FieldOpts in the opts argument // are used to specify the fields of the struct. func Struct(name string, opts ...FieldOpt) FieldOpt { diff --git a/pkg/etwlogrus/hook.go b/pkg/etwlogrus/hook.go index 28d079f7..d3d5713b 100644 --- a/pkg/etwlogrus/hook.go +++ b/pkg/etwlogrus/hook.go @@ -53,12 +53,7 @@ func (h *Hook) Fire(e *logrus.Entry) error { fields = append(fields, etw.StringField("Message", e.Message)) for k, v := range e.Data { - switch v := v.(type) { - case string: - fields = append(fields, etw.StringField(k, v)) - default: - fields = append(fields, etw.StringField(k, fmt.Sprintf(" %v", reflect.TypeOf(v), v))) - } + fields = append(fields, getFieldOpt(k, v)) } // We could try to map Logrus levels to ETW levels, but we would lose some @@ -70,6 +65,127 @@ func (h *Hook) Fire(e *logrus.Entry) error { fields) } +// Currently, we support logging basic builtin types (int, string, etc), slices +// of basic builtin types, error, types derived from the basic types (e.g. "type +// foo int"), and structs (recursively logging their fields). We do not support +// slices of derived types (e.g. "[]foo"). +// +// For types that we don't support, the value is formatted via fmt.Sprint, and +// we also log a message that the type is unsupported along with the formatted +// type. The intent of this is to make it easier to see which types are not +// supported in traces, so we can evaluate adding support for more types in the +// future. +func getFieldOpt(k string, v interface{}) etw.FieldOpt { + switch v := v.(type) { + case bool: + return etw.BoolField(k, v) + case []bool: + return etw.BoolArray(k, v) + case string: + return etw.StringField(k, v) + case []string: + return etw.StringArray(k, v) + case int: + return etw.IntField(k, v) + case []int: + return etw.IntArray(k, v) + case int8: + return etw.Int8Field(k, v) + case []int8: + return etw.Int8Array(k, v) + case int16: + return etw.Int16Field(k, v) + case []int16: + return etw.Int16Array(k, v) + case int32: + return etw.Int32Field(k, v) + case []int32: + return etw.Int32Array(k, v) + case int64: + return etw.Int64Field(k, v) + case []int64: + return etw.Int64Array(k, v) + case uint: + return etw.UintField(k, v) + case []uint: + return etw.UintArray(k, v) + case uint8: + return etw.Uint8Field(k, v) + case []uint8: + return etw.Uint8Array(k, v) + case uint16: + return etw.Uint16Field(k, v) + case []uint16: + return etw.Uint16Array(k, v) + case uint32: + return etw.Uint32Field(k, v) + case []uint32: + return etw.Uint32Array(k, v) + case uint64: + return etw.Uint64Field(k, v) + case []uint64: + return etw.Uint64Array(k, v) + case uintptr: + return etw.UintptrField(k, v) + case []uintptr: + return etw.UintptrArray(k, v) + case float32: + return etw.Float32Field(k, v) + case []float32: + return etw.Float32Array(k, v) + case float64: + return etw.Float64Field(k, v) + case []float64: + return etw.Float64Array(k, v) + case error: + return etw.StringField(k, v.Error()) + default: + switch rv := reflect.ValueOf(v); rv.Kind() { + case reflect.Bool: + return getFieldOpt(k, rv.Bool()) + case reflect.Int: + return getFieldOpt(k, int(rv.Int())) + case reflect.Int8: + return getFieldOpt(k, int8(rv.Int())) + case reflect.Int16: + return getFieldOpt(k, int16(rv.Int())) + case reflect.Int32: + return getFieldOpt(k, int32(rv.Int())) + case reflect.Int64: + return getFieldOpt(k, int64(rv.Int())) + case reflect.Uint: + return getFieldOpt(k, uint(rv.Uint())) + case reflect.Uint8: + return getFieldOpt(k, uint8(rv.Uint())) + case reflect.Uint16: + return getFieldOpt(k, uint16(rv.Uint())) + case reflect.Uint32: + return getFieldOpt(k, uint32(rv.Uint())) + case reflect.Uint64: + return getFieldOpt(k, uint64(rv.Uint())) + case reflect.Uintptr: + return getFieldOpt(k, uintptr(rv.Uint())) + case reflect.Float32: + return getFieldOpt(k, float32(rv.Float())) + case reflect.Float64: + return getFieldOpt(k, float64(rv.Float())) + case reflect.String: + return getFieldOpt(k, rv.String()) + case reflect.Struct: + fields := make([]etw.FieldOpt, 0, rv.NumField()) + for i := 0; i < rv.NumField(); i++ { + field := rv.Field(i) + if field.CanInterface() { + fields = append(fields, getFieldOpt(k, field.Interface())) + } + } + return etw.Struct(k, fields...) + } + } + + return etw.StringField(k, fmt.Sprintf("(Unsupported: %T) %v", v, v)) +} + // Close cleans up the hook and closes the ETW provider. func (h *Hook) Close() error { return h.provider.Close() diff --git a/pkg/etwlogrus/hook_test.go b/pkg/etwlogrus/hook_test.go new file mode 100644 index 00000000..f6ad52c9 --- /dev/null +++ b/pkg/etwlogrus/hook_test.go @@ -0,0 +1,111 @@ +package etwlogrus + +import ( + "github.com/Microsoft/go-winio/internal/etw" + "testing" +) + +func fireEvent(t *testing.T, p *etw.Provider, name string, value interface{}) { + if err := p.WriteEvent( + name, + nil, + etw.WithFields(getFieldOpt("Field", value))); err != nil { + + t.Fatal(err) + } +} + +// The purpose of this test is to log lots of different field types, to test the +// logic that converts them to ETW. Because we don't have a way to +// programatically validate the ETW events, this test has two main purposes: (1) +// validate nothing causes a panic while logging (2) allow manual validation that +// the data is logged correctly (through a tool like WPA). +func TestFieldLogging(t *testing.T) { + // Sample WPRP to collect this provider: + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // Start collection: + // wpr -start HookTest.wprp -filemode + // + // Stop collection: + // wpr -stop HookTest.etl + p, err := etw.NewProvider("HookTest", nil) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := p.Close(); err != nil { + t.Fatal(err) + } + }() + + fireEvent(t, p, "Bool", true) + fireEvent(t, p, "BoolSlice", []bool{true, false, true}) + fireEvent(t, p, "String", "teststring") + fireEvent(t, p, "StringSlice", []string{"sstr1", "sstr2", "sstr3"}) + fireEvent(t, p, "Int", int(1)) + fireEvent(t, p, "IntSlice", []int{2, 3, 4}) + fireEvent(t, p, "Int8", int8(5)) + fireEvent(t, p, "Int8Slice", []int8{6, 7, 8}) + fireEvent(t, p, "Int16", int16(9)) + fireEvent(t, p, "Int16Slice", []int16{10, 11, 12}) + fireEvent(t, p, "Int32", int32(13)) + fireEvent(t, p, "Int32Slice", []int32{14, 15, 16}) + fireEvent(t, p, "Int64", int64(17)) + fireEvent(t, p, "Int64Slice", []int64{18, 19, 20}) + fireEvent(t, p, "Uint", uint(21)) + fireEvent(t, p, "UintSlice", []uint{22, 23, 24}) + fireEvent(t, p, "Uint8", uint8(25)) + fireEvent(t, p, "Uint8Slice", []uint8{26, 27, 28}) + fireEvent(t, p, "Uint16", uint16(29)) + fireEvent(t, p, "Uint16Slice", []uint16{30, 31, 32}) + fireEvent(t, p, "Uint32", uint32(33)) + fireEvent(t, p, "Uint32Slice", []uint32{34, 35, 36}) + fireEvent(t, p, "Uint64", uint64(37)) + fireEvent(t, p, "Uint64Slice", []uint64{38, 39, 40}) + fireEvent(t, p, "Uintptr", uintptr(41)) + fireEvent(t, p, "UintptrSlice", []uintptr{42, 43, 44}) + fireEvent(t, p, "Float32", float32(45.46)) + fireEvent(t, p, "Float32Slice", []float32{47.48, 49.50, 51.52}) + fireEvent(t, p, "Float64", float64(53.54)) + fireEvent(t, p, "Float64Slice", []float64{55.56, 57.58, 59.60}) + + type struct1 struct { + A float32 + priv int + B []uint + } + type struct2 struct { + A int + B int + } + type struct3 struct { + struct2 + A int + B string + priv string + C struct1 + D uint16 + } + // Unexported fields, and fields in embedded structs, should not log. + fireEvent(t, p, "Struct1", struct3{struct2{-1, -2}, 1, "2s", "-3s", struct1{3.4, -4, []uint{5, 6, 7}}, 8}) +} From 6e71d2839caa650619416986367c39f3c52140ed Mon Sep 17 00:00:00 2001 From: Kevin Parsons Date: Fri, 11 Jan 2019 12:22:02 -0800 Subject: [PATCH 2/2] Add test cases for logging empty slices Signed-off-by: Kevin Parsons --- pkg/etwlogrus/hook_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/etwlogrus/hook_test.go b/pkg/etwlogrus/hook_test.go index f6ad52c9..5b1d07f8 100644 --- a/pkg/etwlogrus/hook_test.go +++ b/pkg/etwlogrus/hook_test.go @@ -60,34 +60,49 @@ func TestFieldLogging(t *testing.T) { fireEvent(t, p, "Bool", true) fireEvent(t, p, "BoolSlice", []bool{true, false, true}) + fireEvent(t, p, "EmptyBoolSlice", []bool{}) fireEvent(t, p, "String", "teststring") fireEvent(t, p, "StringSlice", []string{"sstr1", "sstr2", "sstr3"}) + fireEvent(t, p, "EmptyStringSlice", []string{}) fireEvent(t, p, "Int", int(1)) fireEvent(t, p, "IntSlice", []int{2, 3, 4}) + fireEvent(t, p, "EmptyIntSlice", []int{}) fireEvent(t, p, "Int8", int8(5)) fireEvent(t, p, "Int8Slice", []int8{6, 7, 8}) + fireEvent(t, p, "EmptyInt8Slice", []int8{}) fireEvent(t, p, "Int16", int16(9)) fireEvent(t, p, "Int16Slice", []int16{10, 11, 12}) + fireEvent(t, p, "EmptyInt16Slice", []int16{}) fireEvent(t, p, "Int32", int32(13)) fireEvent(t, p, "Int32Slice", []int32{14, 15, 16}) + fireEvent(t, p, "EmptyInt32Slice", []int32{}) fireEvent(t, p, "Int64", int64(17)) fireEvent(t, p, "Int64Slice", []int64{18, 19, 20}) + fireEvent(t, p, "EmptyInt64Slice", []int64{}) fireEvent(t, p, "Uint", uint(21)) fireEvent(t, p, "UintSlice", []uint{22, 23, 24}) + fireEvent(t, p, "EmptyUintSlice", []uint{}) fireEvent(t, p, "Uint8", uint8(25)) fireEvent(t, p, "Uint8Slice", []uint8{26, 27, 28}) + fireEvent(t, p, "EmptyUint8Slice", []uint8{}) fireEvent(t, p, "Uint16", uint16(29)) fireEvent(t, p, "Uint16Slice", []uint16{30, 31, 32}) + fireEvent(t, p, "EmptyUint16Slice", []uint16{}) fireEvent(t, p, "Uint32", uint32(33)) fireEvent(t, p, "Uint32Slice", []uint32{34, 35, 36}) + fireEvent(t, p, "EmptyUint32Slice", []uint32{}) fireEvent(t, p, "Uint64", uint64(37)) fireEvent(t, p, "Uint64Slice", []uint64{38, 39, 40}) + fireEvent(t, p, "EmptyUint64Slice", []uint64{}) fireEvent(t, p, "Uintptr", uintptr(41)) fireEvent(t, p, "UintptrSlice", []uintptr{42, 43, 44}) + fireEvent(t, p, "EmptyUintptrSlice", []uintptr{}) fireEvent(t, p, "Float32", float32(45.46)) fireEvent(t, p, "Float32Slice", []float32{47.48, 49.50, 51.52}) + fireEvent(t, p, "EmptyFloat32Slice", []float32{}) fireEvent(t, p, "Float64", float64(53.54)) fireEvent(t, p, "Float64Slice", []float64{55.56, 57.58, 59.60}) + fireEvent(t, p, "EmptyFloat64Slice", []float64{}) type struct1 struct { A float32 @@ -107,5 +122,5 @@ func TestFieldLogging(t *testing.T) { D uint16 } // Unexported fields, and fields in embedded structs, should not log. - fireEvent(t, p, "Struct1", struct3{struct2{-1, -2}, 1, "2s", "-3s", struct1{3.4, -4, []uint{5, 6, 7}}, 8}) + fireEvent(t, p, "Struct", struct3{struct2{-1, -2}, 1, "2s", "-3s", struct1{3.4, -4, []uint{5, 6, 7}}, 8}) }