Skip to content

Commit

Permalink
feat: Fast stdin display, lower memory usage, follow/reverse mode, ho…
Browse files Browse the repository at this point in the history
…me/end key support. long help support (#85)

Signed-off-by: Hiram Chirino <[email protected]>
Co-authored-by: Maksym Kryvchun <[email protected]>
  • Loading branch information
chirino and hedhyw authored Sep 16, 2024
1 parent 906283d commit e0a876b
Show file tree
Hide file tree
Showing 44 changed files with 1,094 additions and 1,115 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bin

# IDE
.vscode
.idea

# Test binary, built with `go test -c`
*.test
Expand Down
7 changes: 6 additions & 1 deletion .golangci.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@
"ireturn",
"gomoddirectives",
"execinquery",
"tagalign"
"tagalign",
"mnd",
"nlreturn"
]
},
"linters-settings": {
"goimports": {
"local-prefixes": "github.com/hedhyw/json-log-viewer/"
},
"cyclop": {
"max-complexity": 15
},
"revive": {}
}
}
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ lint: bin/golangci-lint-${GOLANG_CI_LINT_VER}
./bin/golangci-lint-${GOLANG_CI_LINT_VER} run
.PHONY: lint

fix: bin/golangci-lint-${GOLANG_CI_LINT_VER}
gofumpt -l -w .
./bin/golangci-lint-${GOLANG_CI_LINT_VER} run --fix
.PHONY: lint-fix

test:
go test \
-coverpkg=${COVER_PACKAGES} \
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,19 @@ docker logs -f 000000000000 2>&1 | jlv

### Hotkeys

| Key | Action |
| ------ | -------------- |
| Enter | Open/Close log |
| F | Filter |
| Ctrl+C | Exit |
| F10 | Exit |
| Esc | Back |
| ↑↓ | Navigation |

> \[\] Click Up on the first row to reload the file.
| Key | Action |
|--------|-------------------|
| Enter | Open log |
| Esc | Back |
| F | Filter |
| R | Reverse |
| Ctrl+C | Exit |
| F10 | Exit |
| ↑↓ | Line Up / Down |
| Home | Navigate to Start |
| End | Navigate to End |

> Attempting to navigate past the last line in the log will put you in follow mode.
## Install

Expand Down
1 change: 1 addition & 0 deletions assets/jlv.log
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
time=2024-08-19T15:06:16.848-04:00 level=INFO msg="case <-eofEvent"
53 changes: 32 additions & 21 deletions cmd/jlv/main.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package main

import (
"bytes"
"context"
"flag"
"fmt"
"io/fs"
"os"
"path"

tea "github.com/charmbracelet/bubbletea"

"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"
"github.com/hedhyw/json-log-viewer/internal/pkg/source/fileinput"
"github.com/hedhyw/json-log-viewer/internal/pkg/source/readerinput"
)

// version will be set on build.
Expand All @@ -39,41 +37,54 @@ func main() {
fatalf("Error reading config: %s\n", err)
}

var sourceInput source.Input
fileName := ""
var inputSource *source.Source

switch flag.NArg() {
case 0:
sourceInput, err = getStdinSource(cfg, os.Stdin)
// Tee stdin to a temp file, so that we can
// lazy load the log entries using random access.
fileName = "-"

stdIn, err := getStdinReader(os.Stdin)
if err != nil {
fatalf("Stdin: %s\n", err)
}

inputSource, err = source.Reader(stdIn, cfg)
if err != nil {
fatalf("Could not create temp flie: %s\n", err)
}
defer inputSource.Close()

case 1:
sourceInput = fileinput.New(flag.Arg(0))
fileName = flag.Arg(0)
inputSource, err = source.File(fileName, cfg)
if err != nil {
fatalf("Could not create temp flie: %s\n", err)
}
defer inputSource.Close()

default:
fatalf("Invalid arguments, usage: %s file.log\n", os.Args[0])
}

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

inputSource.StartStreaming(context.Background(), func(entries source.LazyLogEntries, err error) {
if err != nil {
program.Send(events.ErrorOccuredMsg{Err: err})
} else {
program.Send(events.LogEntriesUpdateMsg(entries))
}
})

if _, err := program.Run(); err != nil {
fatalf("Error running program: %s\n", err)
}
}

func getStdinSource(cfg *config.Config, defaultInput fs.File) (source.Input, error) {
stat, err := defaultInput.Stat()
if err != nil {
return nil, fmt.Errorf("stat: %w", err)
}

if stat.Mode()&os.ModeCharDevice != 0 {
return readerinput.New(bytes.NewReader(nil), cfg.StdinReadTimeout), nil
}

return readerinput.New(defaultInput, cfg.StdinReadTimeout), nil
}

func fatalf(message string, args ...any) {
fmt.Fprintf(os.Stderr, message, args...)
os.Exit(1)
Expand Down
25 changes: 5 additions & 20 deletions cmd/jlv/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@ package main

import (
"bytes"
"context"
"errors"
"io"
"io/fs"
"os"
"testing"

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

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetStdinSource(t *testing.T) {
t.Parallel()

ctx := context.Background()

t.Run("ModeNamedPipe", func(t *testing.T) {
t.Parallel()

Expand All @@ -32,15 +27,10 @@ func TestGetStdinSource(t *testing.T) {
},
}

input, err := getStdinSource(config.GetDefaultConfig(), file)
require.NoError(t, err)

readCloser, err := input.ReadCloser(ctx)
input, err := getStdinReader(file)
require.NoError(t, err)

t.Cleanup(func() { assert.NoError(t, readCloser.Close()) })

data, err := io.ReadAll(readCloser)
data, err := io.ReadAll(input)
require.NoError(t, err)
assert.Equal(t, content, string(data))
})
Expand All @@ -55,15 +45,10 @@ func TestGetStdinSource(t *testing.T) {
},
}

input, err := getStdinSource(config.GetDefaultConfig(), file)
require.NoError(t, err)

readCloser, err := input.ReadCloser(ctx)
input, err := getStdinReader(file)
require.NoError(t, err)

t.Cleanup(func() { assert.NoError(t, readCloser.Close()) })

data, err := io.ReadAll(readCloser)
data, err := io.ReadAll(input)
require.NoError(t, err)
assert.Empty(t, data)
})
Expand All @@ -76,7 +61,7 @@ func TestGetStdinSource(t *testing.T) {

file := fakeFile{ErrStat: errStat}

_, err := getStdinSource(config.GetDefaultConfig(), file)
_, err := getStdinReader(file)
require.Error(t, err)
require.ErrorIs(t, err, errStat)
})
Expand Down
24 changes: 24 additions & 0 deletions cmd/jlv/stdin_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build !mock_stdin

package main

import (
"bytes"
"fmt"
"io"
"io/fs"
"os"
)

func getStdinReader(defaultInput fs.File) (io.Reader, error) {
stat, err := defaultInput.Stat()
if err != nil {
return nil, fmt.Errorf("stat: %w", err)
}

if stat.Mode()&os.ModeCharDevice != 0 {
return bytes.NewReader(nil), nil
}

return defaultInput, nil
}
30 changes: 30 additions & 0 deletions cmd/jlv/stdin_reader_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build mock_stdin

package main

import (
"fmt"
"io"
"io/fs"
"os"
"time"
)

func getStdinReader(defaultInput fs.File) (io.Reader, error) {
r, w, err := os.Pipe()
if err != nil {
return nil, fmt.Errorf("pipe: %w", err)
}
go func() {
defer w.Close()
for i := 0; ; i++ {
_, err := w.Write([]byte(fmt.Sprintf(`{"message": "Line %d"}
`, i)))
if err != nil {
fatalf("Write failed: %s\n", err)
}
time.Sleep(10 * time.Millisecond)
}
}()
return r, nil
}
6 changes: 0 additions & 6 deletions example.jlv.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,7 @@
"50": "error",
"60": "fatal"
},
// The number of rows to pre-render.
"prerenderRows": 100,
// The number nanoseconds between manual file reloads.
"reloadThreshold": 1000000000,
// The maximum size of the file in bytes.
// The rest of the file will be ignored.
"maxFileSizeBytes": 1073741824,
// StdinReadTimeout is the timeout (in nanoseconds) of reading from the standart input.
"stdinReadTimeout": 1000000000
}
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ module github.com/hedhyw/json-log-viewer

go 1.22

replace github.com/antonmedv/fx => github.com/hedhyw/fx v0.0.2
replace github.com/antonmedv/fx => github.com/chirino/fx v0.0.0-20240818132837-248e67b184d9

replace github.com/charmbracelet/bubbles => github.com/hedhyw/bubbles v0.0.4

require (
github.com/antonmedv/fx v0.0.0-20240428214715-6793ff4a0e59
github.com/antonmedv/fx v0.0.0-20240807042048-dd653cf7bf83
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.11.0
github.com/charmbracelet/lipgloss v0.12.1
github.com/go-playground/validator/v10 v10.22.0
github.com/hedhyw/jsoncjson v1.1.0
github.com/muesli/reflow v0.3.0
Expand All @@ -21,12 +21,13 @@ require (
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/x/ansi v0.1.2 // indirect
github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/hedhyw/semerr v0.6.7 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
Expand Down
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/teatest v0.0.0-20230904163802-ca705a396e0f h1:kI7ZjLqp210CeIUKhjdLmtnc9UIVcfKSePgwGTxQ0J4=
github.com/charmbracelet/x/exp/teatest v0.0.0-20230904163802-ca705a396e0f/go.mod h1:TckAxPtan3aJ5wbTgBkySpc50SZhXJRZ8PtYICnZJEw=
github.com/chirino/fx v0.0.0-20240818132837-248e67b184d9 h1:v5OXoBrEplJ65ylCZ0gZ9DCTB7EWd+SWIvVL9jxdPTc=
github.com/chirino/fx v0.0.0-20240818132837-248e67b184d9/go.mod h1:km/FnS8aa6d/z086KPzYtYzLv/lWMEnF275IS3wC0jE=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand All @@ -29,10 +35,10 @@ github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/hedhyw/bubbles v0.0.4 h1:4QZeYNa6KcE2CAszgQ07rSk4qd2EDj4FlH9Q3oECq5w=
github.com/hedhyw/bubbles v0.0.4/go.mod h1:0B5SDVyyRXMteAgJRkYRJQ6bvsKtWdzeepp8rN+RhXQ=
github.com/hedhyw/fx v0.0.2 h1:btVjqV+CjHlpt6YZsLOK7jciBpS9TJy6nZHSoMxIhVU=
github.com/hedhyw/fx v0.0.2/go.mod h1:sxGcEYNS1KgU2wRDf7L2wNfpY+BeEw0vYhjSHdFQdHk=
github.com/hedhyw/jsoncjson v1.1.0 h1:uw/aqmbSXAQNJHDPLb+DpwlPNzMREGIsrs+TIwPk+f0=
github.com/hedhyw/jsoncjson v1.1.0/go.mod h1:++nXlbEXzRMcqkoDLvH5I/z5qBkacAWSZDt1u6osUPc=
github.com/hedhyw/semerr v0.6.7 h1:C9TaGpxJfbiiyyja+kFSZB9QK7vSNKc1RZPmWbXBmPI=
github.com/hedhyw/semerr v0.6.7/go.mod h1:GqxYzQ0igy0bi6pc0e38FScn9rQk1n4l+PuVKlQNMW4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
Expand Down
Loading

0 comments on commit e0a876b

Please sign in to comment.