From 5a34ef05faa6650df4d7865c71626d3b458101b6 Mon Sep 17 00:00:00 2001 From: Antoine Grondin Date: Thu, 10 Oct 2024 20:09:17 +0900 Subject: [PATCH] feat(scanner): dynamic reordering of successful matchers --- optimize.go | 17 +++++++++++++++++ scanner.go | 47 +++++++++++++++++++++++++++++++++++++--------- time_parse.go | 8 +++++--- time_parse_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 optimize.go diff --git a/optimize.go b/optimize.go new file mode 100644 index 00000000..b1af682a --- /dev/null +++ b/optimize.go @@ -0,0 +1,17 @@ +package humanlog + +// moveToFront moves the element at index `i` to the front +// of the slice +func moveToFront[El any](i int, s []El) []El { + if i == 0 { + return s + } + el := s[i] + for j := i; j > 0; j-- { + s[j] = s[j-1] + } + s[0] = el + return s +} + +const dynamicReordering = false diff --git a/scanner.go b/scanner.go index e0b9f1c3..8ccd55a6 100644 --- a/scanner.go +++ b/scanner.go @@ -32,6 +32,20 @@ func Scan(ctx context.Context, src io.Reader, sink sink.Sink, opts *HandlerOptio data := new(typesv1.StructuredLogEvent) ev.Structured = data + handlers := []func([]byte, *typesv1.StructuredLogEvent) bool{ + jsonEntry.TryHandle, + logfmtEntry.TryHandle, + func(lineData []byte, data *typesv1.StructuredLogEvent) bool { + return tryDockerComposePrefix(lineData, data, &jsonEntry) + }, + func(lineData []byte, data *typesv1.StructuredLogEvent) bool { + return tryDockerComposePrefix(lineData, data, &logfmtEntry) + }, + func(lineData []byte, data *typesv1.StructuredLogEvent) bool { + return tryZapDevPrefix(lineData, data, &jsonEntry) + }, + } + skipNextScan := false for { if !in.Scan() { @@ -65,21 +79,36 @@ func Scan(ctx context.Context, src io.Reader, sink sink.Sink, opts *HandlerOptio // remove that pesky syslog crap lineData = bytes.TrimPrefix(lineData, []byte("@cee: ")) - switch { - case jsonEntry.TryHandle(lineData, data): + handled := false + handled_line: + for i, tryHandler := range handlers { + if tryHandler(lineData, data) { + if dynamicReordering { + handlers = moveToFront(i, handlers) + } + handled = true + break handled_line + } + } + if !handled { + ev.Structured = nil + } + // switch { - case logfmtEntry.TryHandle(lineData, data): + // case jsonEntry.TryHandle(lineData, data): - case tryDockerComposePrefix(lineData, data, &jsonEntry): + // case logfmtEntry.TryHandle(lineData, data): - case tryDockerComposePrefix(lineData, data, &logfmtEntry): + // case tryDockerComposePrefix(lineData, data, &jsonEntry): - case tryZapDevPrefix(lineData, data, &jsonEntry): + // case tryDockerComposePrefix(lineData, data, &logfmtEntry): - default: - ev.Structured = nil - } + // case tryZapDevPrefix(lineData, data, &jsonEntry): + + // default: + + // } if err := sink.Receive(ctx, ev); err != nil { return err } diff --git a/time_parse.go b/time_parse.go index 2c216d5b..bf908eb7 100644 --- a/time_parse.go +++ b/time_parse.go @@ -61,13 +61,15 @@ func tryParseTime(value interface{}) (time.Time, bool) { var t time.Time switch v := value.(type) { case string: - for _, layout := range TimeFormats { - t, err := time.Parse(layout, v) + for i, layout := range TimeFormats { + t, err := time.Parse(layout, value.(string)) if err == nil { + if dynamicReordering { + TimeFormats = moveToFront(i, TimeFormats) + } t = fixTimebeforeUnixZero(t) return t, true } - } // try to parse unix time number from string floatVal, err := strconv.ParseFloat(v, 64) diff --git a/time_parse_test.go b/time_parse_test.go index 53887970..a658aa3d 100644 --- a/time_parse_test.go +++ b/time_parse_test.go @@ -4,8 +4,55 @@ import ( "fmt" "testing" "time" + + "github.com/stretchr/testify/require" ) +func TestMoveToFront(t *testing.T) { + t.Run("already front", func(t *testing.T) { + in := []string{ + "a", + "b", + "c", + } + want := []string{ + "a", + "b", + "c", + } + got := moveToFront(0, in) + require.Equal(t, want, got) + }) + t.Run("middle", func(t *testing.T) { + in := []string{ + "a", + "b", + "c", + } + want := []string{ + "b", + "a", + "c", + } + got := moveToFront(1, in) + require.Equal(t, want, got) + }) + t.Run("last", func(t *testing.T) { + in := []string{ + "a", + "b", + "c", + } + want := []string{ + "c", + "a", + "b", + } + got := moveToFront(2, in) + require.Equal(t, want, got) + }) +} + func TestTimeParseFloat64(t *testing.T) { t.Run("nanoseconds", func(t *testing.T) { golden := float64(1540369190466951764)