diff --git a/example.jlv.jsonc b/example.jlv.jsonc index 630ce63..d49fb7e 100644 --- a/example.jlv.jsonc +++ b/example.jlv.jsonc @@ -33,7 +33,7 @@ // AM/PM mark: "PM" // // More details: https://go.dev/src/time/format.go. - "time_format": "2006-01-02T15:04:05Z07:00" + "timeFormat": "2006-01-02T15:04:05Z07:00" }, { "title": "Level", @@ -65,13 +65,15 @@ "width": 0 } ], + // Show logs in reverse order by default. + "isReverseDefault": true, // Time layouts to reformat. // // If the time field has been parsed to any of the specified layouts, - // it will be formatted according to "time_format" configuration. + // it will be formatted according to "timeFormat" configuration. // // See: https://pkg.go.dev/time#pkg-constants. - "time_layouts": [ + "timeLayouts": [ "01/02 03:04:05PM '06 -0700", "Mon Jan _2 15:04:05 2006", "Mon Jan _2 15:04:05 MST 2006", diff --git a/internal/app/app_test.go b/internal/app/app_test.go index ab99dea..6783287 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -19,14 +19,26 @@ import ( const testVersion = "v0.0.1" -func newTestModel(tb testing.TB, content []byte) tea.Model { +type configSetter func(*config.Config) + +func newTestModel( + tb testing.TB, + content []byte, + configSetters ...configSetter, +) tea.Model { tb.Helper() testFile := tests.RequireCreateFile(tb, content) - inputSource, err := source.File(testFile, config.GetDefaultConfig()) + cfg := config.GetDefaultConfig() + + for _, set := range configSetters { + set(cfg) + } + + inputSource, err := source.File(testFile, cfg) require.NoError(tb, err) - model := app.NewModel(testFile, config.GetDefaultConfig(), testVersion) + model := app.NewModel(testFile, cfg, testVersion) entries, err := inputSource.ParseLogEntries() require.NoError(tb, err) diff --git a/internal/app/stateloaded.go b/internal/app/stateloaded.go index 46b8586..5e2ee99 100644 --- a/internal/app/stateloaded.go +++ b/internal/app/stateloaded.go @@ -26,8 +26,8 @@ func newStateViewLogs( table := newLogsTableModel( application, logEntries, - true, // follow. - true, // reverse. + true, // follow. + application.Config.IsReverseDefault, // reverse. ) return StateLoadedModel{ diff --git a/internal/app/stateloaded_test.go b/internal/app/stateloaded_test.go index 1129531..c64cccc 100644 --- a/internal/app/stateloaded_test.go +++ b/internal/app/stateloaded_test.go @@ -32,10 +32,10 @@ func TestStateLoadedEmpty(t *testing.T) { func TestStateLoaded(t *testing.T) { t.Parallel() - setup := func() tea.Model { + setup := func(configSetters ...configSetter) tea.Model { const jsonFile = `{"time":"1970-01-01T00:00:00.00","level":"INFO","message": "test"}` - model := newTestModel(t, []byte(jsonFile)) + model := newTestModel(t, []byte(jsonFile), configSetters...) _, ok := model.(app.StateLoadedModel) require.Truef(t, ok, "%s", model) @@ -111,6 +111,17 @@ func TestStateLoaded(t *testing.T) { assert.Contains(t, view, "reverse") }) + t.Run("label_reverse_disabled_in_config", func(t *testing.T) { + t.Parallel() + + model := setup(func(cfg *config.Config) { + cfg.IsReverseDefault = false + }) + + view := model.View() + assert.NotContains(t, view, "reverse") + }) + t.Run("label_not_reverse", func(t *testing.T) { t.Parallel() diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 60754e2..8f87d42 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -25,8 +25,12 @@ type Config struct { Path string `json:"-"` Fields []Field `json:"fields" validate:"min=1"` + + TimeLayoutsDeprecated []string `json:"time_layouts,omitempty"` // TimeLayouts to reformat. - TimeLayouts []string `json:"time_layouts"` + TimeLayouts []string `json:"timeLayouts"` + + IsReverseDefault bool `json:"isReverseDefault"` CustomLevelMapping map[string]string `json:"customLevelMapping"` @@ -55,7 +59,9 @@ type Field struct { Kind FieldKind `json:"kind" validate:"required,oneof=time message numerictime secondtime millitime microtime level any"` References []string `json:"ref" validate:"min=1,dive,required"` Width int `json:"width" validate:"min=0"` - TimeFormat *string `json:"time_format,omitempty"` + + TimeFormatDeprecated *string `json:"time_format,omitempty"` + TimeFormat *string `json:"timeFormat,omitempty"` } // GetDefaultConfig returns the configuration with default values. @@ -102,6 +108,7 @@ func GetDefaultConfig() *Config { Kind: FieldKindMessage, References: []string{"$.message", "$.msg", "$.error", "$.err"}, }}, + IsReverseDefault: true, } } @@ -165,6 +172,18 @@ func readConfigFromFile(path string) (cfg *Config, err error) { cfg.Path = path + if len(cfg.TimeLayoutsDeprecated) != 0 { + cfg.TimeLayouts = cfg.TimeLayoutsDeprecated + } + + for i, f := range cfg.Fields { + if f.TimeFormatDeprecated != nil && *f.TimeFormatDeprecated != "" { + f.TimeFormat = f.TimeFormatDeprecated + } + + cfg.Fields[i] = f + } + return cfg, nil } diff --git a/internal/pkg/config/config_test.go b/internal/pkg/config/config_test.go index d5ddd80..5504919 100644 --- a/internal/pkg/config/config_test.go +++ b/internal/pkg/config/config_test.go @@ -114,7 +114,7 @@ func ExampleGetDefaultConfig() { // "$[\"@timestamp\"]" // ], // "width": 30, - // "time_format": "2006-01-02T15:04:05Z07:00" + // "timeFormat": "2006-01-02T15:04:05Z07:00" // }, // { // "title": "Level", @@ -138,7 +138,7 @@ func ExampleGetDefaultConfig() { // "width": 0 // } // ], - // "time_layouts": [ + // "timeLayouts": [ // "01/02 03:04:05PM '06 -0700", // "Mon Jan _2 15:04:05 2006", // "Mon Jan _2 15:04:05 MST 2006", @@ -157,6 +157,7 @@ func ExampleGetDefaultConfig() { // "2006-01-02 15:04:05", // "2006-01-02" // ], + // "isReverseDefault": true, // "customLevelMapping": { // "10": "trace", // "20": "debug", @@ -395,3 +396,42 @@ func TestByteSizeParseFailed(t *testing.T) { require.Error(t, err) }) } + +func TestReadTimeLayoutsDeprecated(t *testing.T) { + t.Parallel() + + cfg := config.GetDefaultConfig() + cfg.TimeLayoutsDeprecated = []string{time.Layout} + cfg.TimeLayouts = nil + + configJSON := tests.RequireEncodeJSON(t, cfg) + fileFirst := tests.RequireCreateFile(t, configJSON) + + actual, err := config.Read(fileFirst) + if assert.NoError(t, err) { + assert.ElementsMatch(t, actual.TimeLayouts, cfg.TimeLayoutsDeprecated) + } +} + +func TestReadTimeFormatDeprecated(t *testing.T) { + t.Parallel() + + timeFormat := time.Layout + + cfg := config.GetDefaultConfig() + cfg.Fields = []config.Field{{ + Title: "Time", + Kind: config.FieldKindNumericTime, + References: []string{"$.timestamp", "$.time", "$.t", "$.ts", "$[\"@timestamp\"]"}, + Width: 30, + TimeFormatDeprecated: &timeFormat, + }} + + configJSON := tests.RequireEncodeJSON(t, cfg) + fileFirst := tests.RequireCreateFile(t, configJSON) + + actual, err := config.Read(fileFirst) + if assert.NoError(t, err) { + assert.Equal(t, timeFormat, *actual.Fields[0].TimeFormat) + } +}