Skip to content

Commit 120d8df

Browse files
authored
feat(#55): support standart input (#75)
1 parent 31522c0 commit 120d8df

27 files changed

+485
-103
lines changed

.golangci.json

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
"bodyclose",
88
"wsl",
99
"funlen",
10-
"maligned",
11-
"exhaustivestruct",
1210
"gci",
1311
"wrapcheck",
1412
"varnamelen",
@@ -18,24 +16,16 @@
1816
"thelper",
1917
"paralleltest",
2018
"tagliatelle",
21-
"scopelint",
22-
"golint",
23-
"interfacer",
2419
"nonamedreturns",
2520
"exhaustruct",
2621
"nolintlint",
27-
"deadcode",
2822
"wastedassign",
29-
"structcheck",
30-
"varcheck",
31-
"ifshort",
32-
"nosnakecase",
3323
"rowserrcheck",
3424
"depguard",
3525
"ireturn",
3626
"gomoddirectives",
37-
"tagalign",
38-
"testifylint"
27+
"execinquery",
28+
"tagalign"
3929
]
4030
},
4131
"linters-settings": {

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
GOLANG_CI_LINT_VER:=v1.55.2
1+
GOLANG_CI_LINT_VER:=v1.59.0
22
OUT_BIN?=${PWD}/bin/jlv
33
COVER_PACKAGES=./...
44
VERSION?=${shell git describe --tags}
@@ -10,6 +10,11 @@ run:
1010
go run ./cmd/jlv assets/example.log
1111
.PHONY: build
1212

13+
run.stdin:
14+
@echo "building ${VERSION}"
15+
go run ./cmd/jlv < assets/example.log
16+
.PHONY: build
17+
1318
build:
1419
@echo "building ${VERSION}"
1520
go build \

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,34 @@ The application is designed to help in visualization, navigation, and analyzing
4747

4848
## Usage
4949

50-
```sh
51-
jlv file.json
52-
jlv -config .jlv.jsonc file.json
50+
### Reading from file
51+
```shell
52+
jlv assets/example.log
53+
jlv -config example.jlv.jsonc assets/example.log
54+
```
55+
56+
### Reading from Stdin
57+
58+
```shell
59+
jlv < assets/example.log
60+
```
61+
62+
Common applications:
63+
64+
```shell
65+
curl https://raw.githubusercontent.com/hedhyw/json-log-viewer/main/assets/example.log | jlv
66+
67+
jlv << EOF
68+
{"time":"1970-01-01T00:00:00.00","level":"INFO","message": "day 1"}
69+
{"time":"1970-01-02T00:00:00.00","level":"INFO","message": "day 2"}
70+
EOF
71+
72+
kubectl logs pod/POD_NAME -f | jlv
73+
docker logs 000000000000 | jlv
5374
```
5475

76+
### Hotkeys
77+
5578
| Key | Action |
5679
| ------ | -------------- |
5780
| Enter | Open/Close log |

cmd/jlv/main.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010

1111
"github.com/hedhyw/json-log-viewer/internal/app"
1212
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
13+
"github.com/hedhyw/json-log-viewer/internal/pkg/source"
14+
"github.com/hedhyw/json-log-viewer/internal/pkg/source/fileinput"
15+
"github.com/hedhyw/json-log-viewer/internal/pkg/source/readerinput"
1316
)
1417

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

21-
if flag.NArg() != 1 {
22-
fatalf("Invalid arguments, usage: %s file.log\n", os.Args[0])
23-
}
24-
2524
cfg, err := readConfig(*configPath)
2625
if err != nil {
2726
fatalf("Error reading config: %s\n", err)
2827
}
2928

30-
appModel := app.NewModel(flag.Args()[0], cfg)
29+
var sourceInput source.Input
30+
31+
switch flag.NArg() {
32+
case 0:
33+
sourceInput = readerinput.New(os.Stdin, cfg.StdinReadTimeout)
34+
case 1:
35+
sourceInput = fileinput.New(flag.Arg(0))
36+
default:
37+
fatalf("Invalid arguments, usage: %s file.log\n", os.Args[0])
38+
}
39+
40+
appModel := app.NewModel(sourceInput, cfg)
3141
program := tea.NewProgram(appModel, tea.WithAltScreen())
3242

3343
if _, err := program.Run(); err != nil {

example.jlv.jsonc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,7 @@
6969
"reloadThreshold": 1000000000,
7070
// The maximum size of the file in bytes.
7171
// The rest of the file will be ignored.
72-
"maxFileSizeBytes": 1073741824
72+
"maxFileSizeBytes": 1073741824,
73+
// StdinReadTimeout is the timeout (in nanoseconds) of reading from the standart input.
74+
"stdinReadTimeout": 1000000000
7375
}

internal/app/app.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,29 @@ import (
55
"github.com/charmbracelet/lipgloss"
66

77
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
8+
"github.com/hedhyw/json-log-viewer/internal/pkg/source"
89
)
910

1011
// Application global state.
1112
type Application struct {
12-
Path string
13-
Config *config.Config
13+
SourceInput source.Input
14+
Config *config.Config
1415

1516
BaseStyle lipgloss.Style
1617
FooterStyle lipgloss.Style
1718

1819
LastWindowSize tea.WindowSizeMsg
1920
}
2021

21-
func newApplication(path string, config *config.Config) Application {
22+
func newApplication(sourceInput source.Input, config *config.Config) Application {
2223
const (
2324
initialWidth = 70
2425
initialHeight = 20
2526
)
2627

2728
return Application{
28-
Path: path,
29-
Config: config,
29+
SourceInput: sourceInput,
30+
Config: config,
3031

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

4142
// NewModel initializes a new application model. It accept the path
4243
// to the file with logs.
43-
func NewModel(path string, config *config.Config) tea.Model {
44-
return newStateInitial(newApplication(path, config))
44+
func NewModel(sourceInput source.Input, config *config.Config) tea.Model {
45+
return newStateInitial(newApplication(sourceInput, config))
4546
}

internal/app/app_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/hedhyw/json-log-viewer/internal/app"
1212
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
13+
"github.com/hedhyw/json-log-viewer/internal/pkg/source/fileinput"
1314
"github.com/hedhyw/json-log-viewer/internal/pkg/tests"
1415
)
1516

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

1920
testFile := tests.RequireCreateFile(tb, content)
2021

21-
model := app.NewModel(testFile, config.GetDefaultConfig())
22+
model := app.NewModel(fileinput.New(testFile), config.GetDefaultConfig())
2223
model = handleUpdate(model, model.Init()())
2324

2425
return model

internal/app/helper.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package app
22

33
import (
4+
"context"
5+
"errors"
46
"fmt"
57
"runtime"
68
"strings"
@@ -20,11 +22,9 @@ type helper struct {
2022
Application
2123
}
2224

25+
// LoadEntries reads and parses entries from the input source.
2326
func (h helper) LoadEntries() tea.Msg {
24-
logEntries, err := source.LoadLogsFromFile(
25-
h.Path,
26-
h.Config,
27-
)
27+
logEntries, err := h.loadEntriesFromSourceInput()
2828
if err != nil {
2929
return events.ErrorOccuredMsg{Err: err}
3030
}
@@ -34,6 +34,27 @@ func (h helper) LoadEntries() tea.Msg {
3434
return events.LogEntriesLoadedMsg(logEntries)
3535
}
3636

37+
func (h helper) loadEntriesFromSourceInput() (logEntries source.LazyLogEntries, err error) {
38+
ctx := context.Background()
39+
40+
readCloser, err := h.SourceInput.ReadCloser(ctx)
41+
if err != nil {
42+
return nil, fmt.Errorf("readcloser: %w", err)
43+
}
44+
45+
defer func() { err = errors.Join(err, readCloser.Close()) }()
46+
47+
logEntries, err = source.ParseLogEntriesFromReader(
48+
readCloser,
49+
h.Config,
50+
)
51+
if err != nil {
52+
return nil, fmt.Errorf("reading logs: %w", err)
53+
}
54+
55+
return logEntries, nil
56+
}
57+
3758
func (h helper) getLogLevelStyle(
3859
logEntries source.LazyLogEntries,
3960
baseStyle lipgloss.Style,

internal/app/logstable.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func newLogsTableModel(application Application, logEntries source.LazyLogEntries
5454
minRenderedRows: application.Config.PrerenderRows,
5555
allEntries: logEntries,
5656
lastCursor: 0,
57-
renderedRows: make([]table.Row, 0, application.Config.PrerenderRows*2),
57+
renderedRows: make([]table.Row, 0, application.Config.PrerenderRows),
5858
}.withRenderedRows()
5959

6060
return logsTableModel{
@@ -90,12 +90,15 @@ func (m logsTableModel) Update(msg tea.Msg) (logsTableModel, tea.Cmd) {
9090
}
9191

9292
func (m logsTableModel) handleWindowSizeMsg(msg tea.WindowSizeMsg) logsTableModel {
93-
const heightOffset = 4
93+
const (
94+
heightOffset = 4
95+
widthOffset = -10
96+
)
9497

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

101104
return m

internal/app/statefiltering_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func TestStateFilteringReset(t *testing.T) {
136136
rendered := model.View()
137137

138138
index := strings.Index(rendered, "filtered 0 by:")
139-
if assert.Greater(t, index, 0) {
139+
if assert.Positive(t, index) {
140140
rendered = rendered[:index]
141141
}
142142

0 commit comments

Comments
 (0)