Skip to content

Commit

Permalink
feat(test): improve anoonation code and add unit tests to cover proce…
Browse files Browse the repository at this point in the history
…ssStruct()
  • Loading branch information
iamrz1 committed Jan 23, 2025
1 parent 599b03a commit ffe256f
Showing 3 changed files with 169 additions and 8 deletions.
37 changes: 36 additions & 1 deletion tools/logger/generating.md
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ type OrgInfo struct

```

With the addition of the annotation (which matches the [standard
With the addition of the log tags `ie. log:"xyz"` for fields (xyz matches the [standard
attributes](https://app.datadoghq.com/logs/pipelines/standard-attributes)),
we can use
[logger](https://github.com/getoutreach/gobox/tree/master/tools/logger)
@@ -51,6 +51,41 @@ func (s *OrgInfo) MarshalLog(addField func(key string, value interface{})) {
}
```
### Annotations:
These tags for fields also have limited support for annotations: `ie. log:"xyz,annotation"`.
Supported annotations:
- **omitempty**: makes the field optional.
`omitempty` annotation is available for simple built-in types or
custom types with underlying type being built-in or pointers of any type.
Example:
```go
type OrgInfo struct
Org string `log:"or.org.shortname,omitempty"`
Guid *Custom `log:"or.org.guid,omitempty"`
}
```
Generated code:
```go
func (s *OrgInfo) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}
if s.Org != "" {
addField("or.org.shortname", s.Org)
}
if s.Guid != nil {
addField("or.org.guid", s.Guid)
}
}
```
## Using go generate
Go `generate` is the standard way to generate pre-build artifacts
20 changes: 13 additions & 7 deletions tools/logger/logger.go
Original file line number Diff line number Diff line change
@@ -46,6 +46,10 @@ func (s *{{ .name }}) MarshalLog(addField func(key string, value interface{})) {
addField("{{.key}}", s.{{.name}}.UTC().Format(time.RFC3339Nano))`
simpleFieldFormat = `
addField("{{.key}}", s.{{.name}})`
simpleOptionalFieldFormat = `
if s.{{.name}} != %s {
addField("{{.key}}", s.{{.name}})
}`
nestedMarshalerFormat = `
s.{{.name}}.MarshalLog(addField)`
nestedNilableMarshalerFormat = `
@@ -55,7 +59,7 @@ if s.{{.name}} != nil {
)

const (
tagOmitEmpty = ",omitempty"
annotationOmitEmpty = "omitempty"
)

func main() {
@@ -136,6 +140,12 @@ func processStruct(w io.Writer, s *types.Struct, name string) {
write(w, functionHeaderFormat, map[string]string{"name": name})
for kk := 0; kk < s.NumFields(); kk++ {
if field, ok := reflect.StructTag(s.Tag(kk)).Lookup("log"); ok {
var annotations string
fieldParts := strings.SplitN(field, ",", 2)
field = fieldParts[0]
if len(fieldParts) > 1 {
annotations = fieldParts[1]
}
args := map[string]string{"key": field, "name": s.Field(kk).Name()}
switch {
case s.Field(kk).Type().String() == "time.Time":
@@ -144,8 +154,7 @@ func processStruct(w io.Writer, s *types.Struct, name string) {
write(w, nestedNilableMarshalerFormat, args)
case field == ".":
write(w, nestedMarshalerFormat, args)
case strings.HasSuffix(field, tagOmitEmpty):
args["key"] = strings.TrimSuffix(field, tagOmitEmpty)
case strings.Contains(annotations, annotationOmitEmpty):
write(w, getSimpleOptionalFieldFormat(s.Field(kk).Type()), args)
default:
write(w, simpleFieldFormat, args)
@@ -192,8 +201,5 @@ func getSimpleOptionalFieldFormat(p types.Type) string {
defaultValue = "nil"
}

return fmt.Sprintf(`
if s.{{.name}} != %s {
addField("{{.key}}", s.{{.name}})
}`, defaultValue)
return fmt.Sprintf(simpleOptionalFieldFormat, defaultValue)
}
120 changes: 120 additions & 0 deletions tools/logger/logger_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"bytes"
"go/types"
"gotest.tools/v3/assert"
"testing"
)

@@ -80,3 +82,121 @@ func TestGetSimpleOptionalFieldFormat(t *testing.T) {
})
}
}

func TestProcessStruct(t *testing.T) {
tests := []struct {
name string
setup func() (*types.Struct, string)
expected string
}{
{
name: "basic fields",
setup: func() (*types.Struct, string) {
fields := []*types.Var{
types.NewField(0, nil, "Name", types.Typ[types.String], false),
types.NewField(0, nil, "Age", types.Typ[types.Int], false),
}
tags := []string{
`log:"name"`,
`log:"age"`,
}
return types.NewStruct(fields, tags), "BasicStruct"
},
expected: `
func (s *BasicStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}
addField("name", s.Name)
addField("age", s.Age)
}
`,
},
{
name: "time field",
setup: func() (*types.Struct, string) {
pkg := types.NewPackage("time", "time")
timeType := types.NewNamed(types.NewTypeName(0, pkg, "Time", nil), &types.Struct{}, nil)

fields := []*types.Var{
types.NewField(0, nil, "CreatedAt", timeType, false),
}
tags := []string{`log:"created_at"`}
return types.NewStruct(fields, tags), "TimeStruct"
},
expected: `
func (s *TimeStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}
addField("created_at", s.CreatedAt.UTC().Format(time.RFC3339Nano))
}
`,
},
{
name: "omitempty fields",
setup: func() (*types.Struct, string) {
fields := []*types.Var{
types.NewField(0, nil, "Name", types.Typ[types.String], false),
types.NewField(0, nil, "AgeP", types.NewPointer(types.Typ[types.Int]), false),
}
tags := []string{
`log:"name,omitempty"`,
`log:"ageP,omitempty"`,
}
return types.NewStruct(fields, tags), "OmitStruct"
},
expected: `
func (s *OmitStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}
if s.Name != "" {
addField("name", s.Name)
}
if s.AgeP != nil {
addField("ageP", s.AgeP)
}
}
`,
},
{
name: "nested marshaler",
setup: func() (*types.Struct, string) {
nestedType := types.NewPointer(types.NewNamed(
types.NewTypeName(0, nil, "NestedStruct", nil),
&types.Struct{},
nil,
))
fields := []*types.Var{
types.NewField(0, nil, "Nested", nestedType, false),
}
tags := []string{`log:"."`}
return types.NewStruct(fields, tags), "ParentStruct"
},
expected: `
func (s *ParentStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}
if s.Nested != nil {
s.Nested.MarshalLog(addField)
}
}
`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, name := tt.setup()
buf := &bytes.Buffer{}
processStruct(buf, s, name)
assert.Equal(t, tt.expected, buf.String())
})
}
}

0 comments on commit ffe256f

Please sign in to comment.