From e01ee548a23eb3e05c584b211e8493efa682e220 Mon Sep 17 00:00:00 2001 From: flejz Date: Wed, 26 Jun 2024 17:09:16 +0200 Subject: [PATCH 1/2] Implement struct event parser --- event.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++ log_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/event.go b/event.go index 56de6061..5a3ce85c 100644 --- a/event.go +++ b/event.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "os" + "reflect" "runtime" "sync" "time" @@ -248,6 +249,66 @@ func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { return e } +// Struct traversers a struct object reading the log tag to add its value as a Str +func (e *Event) Struct(obj interface{}) *Event { + if e == nil { + return e + } + objValue := reflect.ValueOf(obj) + objType := reflect.TypeOf(obj) + for i := 0; i < objType.NumField(); i++ { + field := objType.Field(i) + + if key, ok := field.Tag.Lookup("log"); ok { + fieldVal := objValue.Field(i) + + switch fieldVal.Kind() { + case reflect.String: + e.Str(key, fieldVal.String()) + case reflect.Bool: + e.Bool(key, fieldVal.Bool()) + case reflect.Int: + e.Int(key, fieldVal.Interface().(int)) + case reflect.Int8: + e.Int8(key, fieldVal.Interface().(int8)) + case reflect.Int16: + e.Int16(key, fieldVal.Interface().(int16)) + case reflect.Int32: + e.Int32(key, fieldVal.Interface().(int32)) + case reflect.Int64: + e.Int64(key, fieldVal.Interface().(int64)) + case reflect.Uint: + e.Uint(key, fieldVal.Interface().(uint)) + case reflect.Uint8: + e.Uint8(key, fieldVal.Interface().(uint8)) + case reflect.Uint16: + e.Uint16(key, fieldVal.Interface().(uint16)) + case reflect.Uint32: + e.Uint32(key, fieldVal.Interface().(uint32)) + case reflect.Uint64: + e.Uint64(key, fieldVal.Interface().(uint64)) + case reflect.Float32: + e.Float32(key, fieldVal.Interface().(float32)) + case reflect.Float64: + e.Float64(key, fieldVal.Interface().(float64)) + case reflect.Struct: + { + if field.Type == reflect.TypeOf(time.Time{}) { + e.Time(key, fieldVal.Interface().(time.Time)) + } + } + case reflect.Interface: + if err, ok := fieldVal.Interface().(error); ok { + e.AnErr(key, err) + } else if stringer, ok := fieldVal.Interface().(fmt.Stringer); ok { + e.Stringer(key, stringer) + } + } + } + } + return e +} + // Str adds the field key with val as a string to the *Event context. func (e *Event) Str(key, val string) *Event { if e == nil { diff --git a/log_test.go b/log_test.go index 64a58be2..71334f75 100644 --- a/log_test.go +++ b/log_test.go @@ -1041,3 +1041,53 @@ func TestHTMLNoEscaping(t *testing.T) { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } + +func TestStruct(t *testing.T) { + type Object struct { + Str string `log:"str"` + StrNoTag string + Err error `log:"err"` + Bool bool `log:"bool"` + Int int `log:"int"` + Int8 int8 `log:"int8"` + Int16 int16 `log:"int16"` + Int32 int32 `log:"int32"` + Int64 int64 `log:"int64"` + Uint uint `log:"uint"` + Uint8 uint8 `log:"uint8"` + Uint16 uint16 `log:"uint16"` + Uint32 uint32 `log:"uint32"` + Uint64 uint64 `log:"uint64"` + Float32 float32 `log:"float32"` + Float64 float64 `log:"float64"` + Time time.Time `log:"time"` + } + + obj := Object{ + Str: "string", + Err: errors.New("error"), + Bool: true, + Int: -1000000000000000000, + Int8: -10, + Int16: -10000, + Int32: -1000000000, + Int64: -1000000000000000000, + Uint: 1000000000000000000, + Uint8: 10, + Uint16: 10000, + Uint32: 1000000000, + Uint64: 1000000000000000000, + Float32: 1000000000000000000, + Float64: 1000000000000000000, + Time: time.Date(2020, time.January, 1, 1, 1, 1, 1, &time.Location{}), + } + + out := &bytes.Buffer{} + log := New(out) + log.Log().Struct(obj).Msg("") + + if got, want := decodeIfBinaryToString(out.Bytes()), `{"str":"string","err":"error","bool":true,"int":-1000000000000000000,"int8":-10,"int16":-10000,"int32":-1000000000,"int64":-1000000000000000000,"uint":1000000000000000000,"uint8":10,"uint16":10000,"uint32":1000000000,"uint64":1000000000000000000,"float32":1000000000000000000,"float64":1000000000000000000,"time":"2020-01-01T01:01:01Z"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + +} From 76a242a5d4d8709b2c6d046f5b7a9577a0926a45 Mon Sep 17 00:00:00 2001 From: flejz Date: Wed, 26 Jun 2024 18:16:37 +0200 Subject: [PATCH 2/2] Add support to nested objects --- event.go | 51 ++++++++++++++++----------------------------------- log_test.go | 50 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/event.go b/event.go index 5a3ce85c..9628d13f 100644 --- a/event.go +++ b/event.go @@ -249,7 +249,7 @@ func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { return e } -// Struct traversers a struct object reading the log tag to add its value as a Str +// Struct traverses a struct object reading the log tag to log its value as its equivalent data type func (e *Event) Struct(obj interface{}) *Event { if e == nil { return e @@ -263,46 +263,27 @@ func (e *Event) Struct(obj interface{}) *Event { fieldVal := objValue.Field(i) switch fieldVal.Kind() { - case reflect.String: - e.Str(key, fieldVal.String()) - case reflect.Bool: - e.Bool(key, fieldVal.Bool()) - case reflect.Int: - e.Int(key, fieldVal.Interface().(int)) - case reflect.Int8: - e.Int8(key, fieldVal.Interface().(int8)) - case reflect.Int16: - e.Int16(key, fieldVal.Interface().(int16)) - case reflect.Int32: - e.Int32(key, fieldVal.Interface().(int32)) - case reflect.Int64: - e.Int64(key, fieldVal.Interface().(int64)) - case reflect.Uint: - e.Uint(key, fieldVal.Interface().(uint)) - case reflect.Uint8: - e.Uint8(key, fieldVal.Interface().(uint8)) - case reflect.Uint16: - e.Uint16(key, fieldVal.Interface().(uint16)) - case reflect.Uint32: - e.Uint32(key, fieldVal.Interface().(uint32)) - case reflect.Uint64: - e.Uint64(key, fieldVal.Interface().(uint64)) - case reflect.Float32: - e.Float32(key, fieldVal.Interface().(float32)) - case reflect.Float64: - e.Float64(key, fieldVal.Interface().(float64)) case reflect.Struct: - { - if field.Type == reflect.TypeOf(time.Time{}) { - e.Time(key, fieldVal.Interface().(time.Time)) - } + if field.Type == reflect.TypeOf(time.Time{}) { + e.Time(key, fieldVal.Interface().(time.Time)) + } else { + ne := newEvent(e.w, e.level).Struct(fieldVal.Interface()) + e.RawJSON(key, enc.AppendEndMarker(ne.buf)) + } + case reflect.Slice: + if field.Type == reflect.TypeOf(net.HardwareAddr{}) { + e.MACAddr(key, fieldVal.Interface().(net.HardwareAddr)) + } else if field.Type == reflect.TypeOf(net.IP{}) { + e.IPAddr(key, fieldVal.Interface().(net.IP)) } case reflect.Interface: if err, ok := fieldVal.Interface().(error); ok { e.AnErr(key, err) - } else if stringer, ok := fieldVal.Interface().(fmt.Stringer); ok { - e.Stringer(key, stringer) + } else { + e.Interface(key, fieldVal.Interface()) } + default: + e.Interface(key, fieldVal.Interface()) } } } diff --git a/log_test.go b/log_test.go index 71334f75..6b1daaff 100644 --- a/log_test.go +++ b/log_test.go @@ -1043,24 +1043,38 @@ func TestHTMLNoEscaping(t *testing.T) { } func TestStruct(t *testing.T) { + type NestedObject struct { + NestedStr string `log:"nested_str"` + NestedBool bool `log:"nested_bool"` + } + type Object struct { Str string `log:"str"` StrNoTag string - Err error `log:"err"` - Bool bool `log:"bool"` - Int int `log:"int"` - Int8 int8 `log:"int8"` - Int16 int16 `log:"int16"` - Int32 int32 `log:"int32"` - Int64 int64 `log:"int64"` - Uint uint `log:"uint"` - Uint8 uint8 `log:"uint8"` - Uint16 uint16 `log:"uint16"` - Uint32 uint32 `log:"uint32"` - Uint64 uint64 `log:"uint64"` - Float32 float32 `log:"float32"` - Float64 float64 `log:"float64"` - Time time.Time `log:"time"` + Err error `log:"err"` + Bool bool `log:"bool"` + Int int `log:"int"` + Int8 int8 `log:"int8"` + Int16 int16 `log:"int16"` + Int32 int32 `log:"int32"` + Int64 int64 `log:"int64"` + Uint uint `log:"uint"` + Uint8 uint8 `log:"uint8"` + Uint16 uint16 `log:"uint16"` + Uint32 uint32 `log:"uint32"` + Uint64 uint64 `log:"uint64"` + Float32 float32 `log:"float32"` + Float64 float64 `log:"float64"` + Time time.Time `log:"time"` + IPv4 net.IP `log:"ipv4"` + IPv6 net.IP `log:"ipv6"` + Mac net.HardwareAddr `log:"macaddress"` + Nested NestedObject `log:"nested"` + } + + nested := NestedObject{ + NestedStr: "string", + NestedBool: false, } obj := Object{ @@ -1080,13 +1094,17 @@ func TestStruct(t *testing.T) { Float32: 1000000000000000000, Float64: 1000000000000000000, Time: time.Date(2020, time.January, 1, 1, 1, 1, 1, &time.Location{}), + IPv4: net.IP{192, 168, 0, 100}, + IPv6: net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, + Mac: net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45}, + Nested: nested, } out := &bytes.Buffer{} log := New(out) log.Log().Struct(obj).Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"str":"string","err":"error","bool":true,"int":-1000000000000000000,"int8":-10,"int16":-10000,"int32":-1000000000,"int64":-1000000000000000000,"uint":1000000000000000000,"uint8":10,"uint16":10000,"uint32":1000000000,"uint64":1000000000000000000,"float32":1000000000000000000,"float64":1000000000000000000,"time":"2020-01-01T01:01:01Z"}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"str":"string","err":"error","bool":true,"int":-1000000000000000000,"int8":-10,"int16":-10000,"int32":-1000000000,"int64":-1000000000000000000,"uint":1000000000000000000,"uint8":10,"uint16":10000,"uint32":1000000000,"uint64":1000000000000000000,"float32":1000000000000000000,"float64":1000000000000000000,"time":"2020-01-01T01:01:01Z","ipv4":"192.168.0.100","ipv6":"2001:db8:85a3::8a2e:370:7334","macaddress":"00:14:22:01:23:45","nested":{"nested_str":"string","nested_bool":false}}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) }