From f2baf34b03bfcf2dd3696f21a66d68385fcb88e6 Mon Sep 17 00:00:00 2001 From: Max Altgelt Date: Tue, 29 Sep 2026 12:52:39 +0200 Subject: [PATCH] feat: generalize context omission to json Instead of having omitincontext as textlog modifier, create its own struct tag to have it available for JSON as well. --- textlog.go | 7 ++++--- textlog_test.go | 30 ++++++++++++++++++++++++++++++ thorlog/v3/event.go | 40 ++++++++++++++++++---------------------- thorlog/v3/registry.go | 4 ++-- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/textlog.go b/textlog.go index 4c59f12..ad999d1 100644 --- a/textlog.go +++ b/textlog.go @@ -30,9 +30,10 @@ type TextlogValuePair struct { type TextlogFormatter struct { // FormatValue is a function that formats a single value into a string. If it is nil, fmt.Sprint is used. FormatValue func(data any, modifiers []string) string - // Omit is a function that determines whether a field should be omitted from the log entry. + // Omit is a function that determines whether a struct field should be omitted from the log entry. + // It receives the full struct field (including all of its tags) and the field's value. // If it is nil, no fields are omitted. - Omit func(modifiers []string, value any) bool + Omit func(field reflect.StructField, value any) bool } func (t TextlogFormatter) format(data any, modifiers []string) string { @@ -121,7 +122,7 @@ func (t TextlogFormatter) toEntry(object reflect.Value) TextlogEntry { if slices.Contains(tagModifiers, TextlogModifierOmitempty) && isZero(field) { continue } - if t.Omit != nil && t.Omit(tagModifiers, field.Interface()) { + if t.Omit != nil && t.Omit(typeField, field.Interface()) { continue } if typeField.Anonymous || slices.Contains(tagModifiers, TextlogModifierExpand) { diff --git a/textlog_test.go b/textlog_test.go index 5be914c..1f1b53c 100644 --- a/textlog_test.go +++ b/textlog_test.go @@ -2,6 +2,7 @@ package jsonlog import ( "fmt" + "reflect" "testing" "time" @@ -88,3 +89,32 @@ func TestTextlogFormatting(t *testing.T) { {"MAP_KEY", "map"}, }, details) } + +func TestTextlogOmit(t *testing.T) { + type omitTestObject struct { + Kept string `json:"kept" textlog:"kept"` + Omitted string `json:"omitted" textlog:"omitted" context:"omit"` + Nested struct { + Omitted string `json:"omitted" textlog:"omitted" context:"omit"` + Kept int `json:"kept" textlog:"kept"` + } `json:"nested" textlog:"nested,expand"` + } + var test omitTestObject + test.Kept = "kept" + test.Omitted = "omitted" + test.Nested.Omitted = "omitted" + test.Nested.Kept = 42 + + formatter := TextlogFormatter{ + FormatValue: func(data any, modifiers []string) string { + return "formatted:" + fmt.Sprint(data) + }, + Omit: func(field reflect.StructField, value any) bool { + return field.Tag.Get("context") == "omit" + }, + } + assert.Equal(t, TextlogEntry{ + {"KEPT", "formatted:kept"}, + {"NESTED_KEPT", "formatted:42"}, + }, formatter.Format(test)) +} diff --git a/thorlog/v3/event.go b/thorlog/v3/event.go index 922f8db..8adfb11 100644 --- a/thorlog/v3/event.go +++ b/thorlog/v3/event.go @@ -10,7 +10,6 @@ import ( "github.com/NextronSystems/jsonlog" "github.com/NextronSystems/jsonlog/jsonpointer" "github.com/NextronSystems/jsonlog/thorlog/common" - "golang.org/x/exp/slices" ) // Assessment is a summary of a Subject's analysis by THOR. @@ -157,16 +156,7 @@ func (a *Ancestor) UnmarshalJSON(data []byte) error { } func (a Ancestors) MarshalTextLog(t jsonlog.TextlogFormatter) jsonlog.TextlogEntry { - oldOmit := t.Omit - t.Omit = func(modifiers []string, value any) bool { - if slices.Contains(modifiers, omitInContext) { - return true // Omit fields that are marked with "omitincontext" - } - if oldOmit != nil { - return oldOmit(modifiers, value) // Call the original omit function if it exists - } - return false // Default behavior is to not omit any fields - } + t = withOmitInContext(t) var result jsonlog.TextlogEntry for _, ancestor := range a { var prefix string @@ -193,7 +183,22 @@ type Derivative struct { Object ObservedObject `json:"object" textlog:",expand"` } -const omitInContext = "omitincontext" +// withOmitInContext returns a formatter that additionally omits all fields tagged with `context:"omit"`. +// This tag marks fields that are too verbose to be repeated for an object that is only +// logged as context (ancestor or derivative) of another object. +func withOmitInContext(t jsonlog.TextlogFormatter) jsonlog.TextlogFormatter { + oldOmit := t.Omit + t.Omit = func(field reflect.StructField, value any) bool { + if field.Tag.Get("context") == "omit" { + return true + } + if oldOmit != nil { + return oldOmit(field, value) + } + return false + } + return t +} func (d *Derivative) UnmarshalJSON(data []byte) error { type plainDerivative Derivative @@ -214,16 +219,7 @@ func (d *Derivative) UnmarshalJSON(data []byte) error { } func (d Derivative) MarshalTextLog(t jsonlog.TextlogFormatter) jsonlog.TextlogEntry { - oldOmit := t.Omit - t.Omit = func(modifiers []string, value any) bool { - if slices.Contains(modifiers, omitInContext) { - return true // Omit fields that are marked with "omitincontext" - } - if oldOmit != nil { - return oldOmit(modifiers, value) // Call the original omit function if it exists - } - return false // Default behavior is to not omit any fields - } + t = withOmitInContext(t) type plainDerivative Derivative // Wrap this struct to not implement TextlogMarshaler return t.Format(plainDerivative(d)) } diff --git a/thorlog/v3/registry.go b/thorlog/v3/registry.go index d1fe5c6..17850e9 100644 --- a/thorlog/v3/registry.go +++ b/thorlog/v3/registry.go @@ -11,7 +11,7 @@ type RegistryValue struct { Key string `json:"key" textlog:"key"` Modified time.Time `json:"modified" textlog:"modified"` - ParsedValue string `json:"value" textlog:"value,omitincontext"` + ParsedValue string `json:"value" textlog:"value" context:"omit"` Size uint64 `json:"size" textlog:"size"` } @@ -33,7 +33,7 @@ type RegistryKey struct { jsonlog.ObjectHeader Key string `json:"key" textlog:"key"` Modified time.Time `json:"modified" textlog:"modified"` - FormattedValues string `json:"values" textlog:"values,omitincontext"` + FormattedValues string `json:"values" textlog:"values" context:"omit"` } func (RegistryKey) observed() {}