Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions .golangci.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"bodyclose",
"wsl",
"funlen",
"maligned",
"exhaustivestruct",
"gci",
"wrapcheck",
"varnamelen",
Expand All @@ -18,24 +16,16 @@
"thelper",
"paralleltest",
"tagliatelle",
"scopelint",
"golint",
"interfacer",
"nonamedreturns",
"exhaustruct",
"nolintlint",
"deadcode",
"wastedassign",
"structcheck",
"varcheck",
"ifshort",
"nosnakecase",
"rowserrcheck",
"depguard",
"ireturn",
"gomoddirectives",
"tagalign",
"testifylint"
"execinquery",
"tagalign"
]
},
"linters-settings": {
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
GOLANG_CI_LINT_VER:=v1.55.2
GOLANG_CI_LINT_VER:=v1.59.0
OUT_BIN?=${PWD}/bin/jlv
COVER_PACKAGES=./...
VERSION?=${shell git describe --tags}
Expand All @@ -10,6 +10,11 @@ run:
go run ./cmd/jlv assets/example.log
.PHONY: build

run.stdin:
@echo "building ${VERSION}"
go run ./cmd/jlv < assets/example.log
.PHONY: build

build:
@echo "building ${VERSION}"
go build \
Expand Down
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,34 @@ The application is designed to help in visualization, navigation, and analyzing

## Usage

```sh
jlv file.json
jlv -config .jlv.jsonc file.json
### Reading from file
```shell
jlv assets/example.log
jlv -config example.jlv.jsonc assets/example.log
```

### Reading from Stdin

```shell
jlv < assets/example.log
```

Common applications:

```shell
curl https://raw.githubusercontent.com/hedhyw/json-log-viewer/main/assets/example.log | jlv

jlv << EOF
{"time":"1970-01-01T00:00:00.00","level":"INFO","message": "day 1"}
{"time":"1970-01-02T00:00:00.00","level":"INFO","message": "day 2"}
EOF

kubectl logs pod/POD_NAME -f | jlv
docker logs 000000000000 | jlv
```

### Hotkeys

| Key | Action |
| ------ | -------------- |
| Enter | Open/Close log |
Expand Down
20 changes: 15 additions & 5 deletions cmd/jlv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (

"github.com/hedhyw/json-log-viewer/internal/app"
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
"github.com/hedhyw/json-log-viewer/internal/pkg/source"
"github.com/hedhyw/json-log-viewer/internal/pkg/source/fileinput"
"github.com/hedhyw/json-log-viewer/internal/pkg/source/readerinput"
)

const configFileName = ".jlv.jsonc"
Expand All @@ -18,16 +21,23 @@ func main() {
configPath := flag.String("config", "", "Path to the config")
flag.Parse()

if flag.NArg() != 1 {
fatalf("Invalid arguments, usage: %s file.log\n", os.Args[0])
}

cfg, err := readConfig(*configPath)
if err != nil {
fatalf("Error reading config: %s\n", err)
}

appModel := app.NewModel(flag.Args()[0], cfg)
var sourceInput source.Input

switch flag.NArg() {
case 0:
sourceInput = readerinput.New(os.Stdin, cfg.StdinReadTimeout)
case 1:
sourceInput = fileinput.New(flag.Arg(0))
default:
fatalf("Invalid arguments, usage: %s file.log\n", os.Args[0])
}

appModel := app.NewModel(sourceInput, cfg)
program := tea.NewProgram(appModel, tea.WithAltScreen())

if _, err := program.Run(); err != nil {
Expand Down
4 changes: 3 additions & 1 deletion example.jlv.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,7 @@
"reloadThreshold": 1000000000,
// The maximum size of the file in bytes.
// The rest of the file will be ignored.
"maxFileSizeBytes": 1073741824
"maxFileSizeBytes": 1073741824,
// StdinReadTimeout is the timeout (in nanoseconds) of reading from the standart input.
"stdinReadTimeout": 1000000000
}
15 changes: 8 additions & 7 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,29 @@ import (
"github.com/charmbracelet/lipgloss"

"github.com/hedhyw/json-log-viewer/internal/pkg/config"
"github.com/hedhyw/json-log-viewer/internal/pkg/source"
)

// Application global state.
type Application struct {
Path string
Config *config.Config
SourceInput source.Input
Config *config.Config

BaseStyle lipgloss.Style
FooterStyle lipgloss.Style

LastWindowSize tea.WindowSizeMsg
}

func newApplication(path string, config *config.Config) Application {
func newApplication(sourceInput source.Input, config *config.Config) Application {
const (
initialWidth = 70
initialHeight = 20
)

return Application{
Path: path,
Config: config,
SourceInput: sourceInput,
Config: config,

BaseStyle: getBaseStyle(),
FooterStyle: getFooterStyle(),
Expand All @@ -40,6 +41,6 @@ func newApplication(path string, config *config.Config) Application {

// NewModel initializes a new application model. It accept the path
// to the file with logs.
func NewModel(path string, config *config.Config) tea.Model {
return newStateInitial(newApplication(path, config))
func NewModel(sourceInput source.Input, config *config.Config) tea.Model {
return newStateInitial(newApplication(sourceInput, config))
}
3 changes: 2 additions & 1 deletion internal/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/hedhyw/json-log-viewer/internal/app"
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
"github.com/hedhyw/json-log-viewer/internal/pkg/source/fileinput"
"github.com/hedhyw/json-log-viewer/internal/pkg/tests"
)

Expand All @@ -18,7 +19,7 @@ func newTestModel(tb testing.TB, content []byte) tea.Model {

testFile := tests.RequireCreateFile(tb, content)

model := app.NewModel(testFile, config.GetDefaultConfig())
model := app.NewModel(fileinput.New(testFile), config.GetDefaultConfig())
model = handleUpdate(model, model.Init()())

return model
Expand Down
29 changes: 25 additions & 4 deletions internal/app/helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package app

import (
"context"
"errors"
"fmt"
"runtime"
"strings"
Expand All @@ -20,11 +22,9 @@ type helper struct {
Application
}

// LoadEntries reads and parses entries from the input source.
func (h helper) LoadEntries() tea.Msg {
logEntries, err := source.LoadLogsFromFile(
h.Path,
h.Config,
)
logEntries, err := h.loadEntriesFromSourceInput()
if err != nil {
return events.ErrorOccuredMsg{Err: err}
}
Expand All @@ -34,6 +34,27 @@ func (h helper) LoadEntries() tea.Msg {
return events.LogEntriesLoadedMsg(logEntries)
}

func (h helper) loadEntriesFromSourceInput() (logEntries source.LazyLogEntries, err error) {
ctx := context.Background()

readCloser, err := h.SourceInput.ReadCloser(ctx)
if err != nil {
return nil, fmt.Errorf("readcloser: %w", err)
}

defer func() { err = errors.Join(err, readCloser.Close()) }()

logEntries, err = source.ParseLogEntriesFromReader(
readCloser,
h.Config,
)
if err != nil {
return nil, fmt.Errorf("reading logs: %w", err)
}

return logEntries, nil
}

func (h helper) getLogLevelStyle(
logEntries source.LazyLogEntries,
baseStyle lipgloss.Style,
Expand Down
9 changes: 6 additions & 3 deletions internal/app/logstable.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func newLogsTableModel(application Application, logEntries source.LazyLogEntries
minRenderedRows: application.Config.PrerenderRows,
allEntries: logEntries,
lastCursor: 0,
renderedRows: make([]table.Row, 0, application.Config.PrerenderRows*2),
renderedRows: make([]table.Row, 0, application.Config.PrerenderRows),
}.withRenderedRows()

return logsTableModel{
Expand Down Expand Up @@ -90,12 +90,15 @@ func (m logsTableModel) Update(msg tea.Msg) (logsTableModel, tea.Cmd) {
}

func (m logsTableModel) handleWindowSizeMsg(msg tea.WindowSizeMsg) logsTableModel {
const heightOffset = 4
const (
heightOffset = 4
widthOffset = -10
)

x, y := m.BaseStyle.GetFrameSize()
m.lazyTable.table.SetWidth(msg.Width - x*2)
m.lazyTable.table.SetHeight(msg.Height - y*2 - footerSize - heightOffset)
m.lazyTable.table.SetColumns(getColumns(m.lazyTable.table.Width()-10, m.Config))
m.lazyTable.table.SetColumns(getColumns(m.lazyTable.table.Width()+widthOffset, m.Config))
m.lastWindowSize = msg

return m
Expand Down
2 changes: 1 addition & 1 deletion internal/app/statefiltering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func TestStateFilteringReset(t *testing.T) {
rendered := model.View()

index := strings.Index(rendered, "filtered 0 by:")
if assert.Greater(t, index, 0) {
if assert.Positive(t, index) {
rendered = rendered[:index]
}

Expand Down
8 changes: 7 additions & 1 deletion internal/app/stateinitial_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package app_test

import (
"bytes"
"fmt"
"testing"
"time"

"github.com/hedhyw/json-log-viewer/internal/app"
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
"github.com/hedhyw/json-log-viewer/internal/pkg/events"
"github.com/hedhyw/json-log-viewer/internal/pkg/source/readerinput"

tea "github.com/charmbracelet/bubbletea"
"github.com/stretchr/testify/assert"
Expand All @@ -16,7 +19,10 @@ import (
func TestStateInitial(t *testing.T) {
t.Parallel()

model := app.NewModel("", config.GetDefaultConfig())
model := app.NewModel(
readerinput.New(bytes.NewReader([]byte{}), time.Millisecond),
config.GetDefaultConfig(),
)

_, ok := model.(app.StateInitialModel)
require.Truef(t, ok, "%s", model)
Expand Down
2 changes: 1 addition & 1 deletion internal/app/stateloaded.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (s StateLoadedModel) handleRequestOpenJSON() (tea.Model, tea.Cmd) {
}

func (s StateLoadedModel) handleViewRowsReloadRequestedMsg() (tea.Model, tea.Cmd) {
if time.Since(s.lastReloadAt) < s.Config.ReloadThreshold {
if time.Since(s.lastReloadAt) < s.Config.ReloadThreshold || s.reloading {
return s, nil
}

Expand Down
3 changes: 2 additions & 1 deletion internal/app/stateloaded_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,9 @@ func overwriteFileInStateLoaded(tb testing.TB, model tea.Model, content []byte)
stateLoaded, ok := model.(app.StateLoadedModel)
require.True(tb, ok)

// nolint: gosec // Test.
err := os.WriteFile(
stateLoaded.Application().Path,
stateLoaded.Application().SourceInput.String(),
content,
os.ModePerm,
)
Expand Down
5 changes: 3 additions & 2 deletions internal/app/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (

// Component sizes.
const (
footerSize = 1
footerSize = 1
footerPaddingLeft = 2
)

// Possible colors.
Expand Down Expand Up @@ -41,5 +42,5 @@ func getBaseStyle() lipgloss.Style {
}

func getFooterStyle() lipgloss.Style {
return lipgloss.NewStyle().Height(footerSize).PaddingLeft(2)
return lipgloss.NewStyle().Height(footerSize).PaddingLeft(footerPaddingLeft)
}
2 changes: 0 additions & 2 deletions internal/app/style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ func TestGetColorForLogLevel(t *testing.T) {
}}

for _, testCase := range testCases {
testCase := testCase

t.Run(testCase.Level.String(), func(t *testing.T) {
t.Parallel()

Expand Down
Loading