Skip to content

Commit

Permalink
Feature/filter view (#19)
Browse files Browse the repository at this point in the history
* Added filter view

* Major refactorings and rework on key bindings

* Major feature added - Local filtering

* Fixed non-deterministic sorting for adaptative template config.

* Added keybindings for non-focused json view.

* Tweaks on the README.md
  • Loading branch information
aurc authored Jul 31, 2022
1 parent 5302dfd commit ad00fd9
Show file tree
Hide file tree
Showing 20 changed files with 973 additions and 365 deletions.
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ builds:
- arm64
nfpms:
- maintainer: Aurelio Calegari ([email protected])
description: Rich Terminal User Interface for JSON logs
description: Rich Terminal User Interface streaming JSON logs
homepage: https://github.com/aurc/loggo
license: MIT
formats:
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ brew tap aurc/loggo
brew install aurc/loggo/loggo
````

To update:
````
brew upgrade aurc/loggo/loggo
````

### All Systems

### Install with Go
Expand Down Expand Up @@ -251,8 +256,6 @@ kubectl logs -f -n <some-namespace> <pod-name> | loggo stream

Most of the items listed here are slated for development in the near future,
prior the first release.
- Search log entry.
- Filter log by json key(s).
- Browse/Load new log templates on the fly.
- Create template with keys whose name contains `/` as it uses slashes to navigate to nested json branches.

Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/google/uuid v1.3.0
github.com/nxadm/tail v1.4.8
github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.7.1
google.golang.org/api v0.84.0
Expand All @@ -20,7 +21,7 @@ require (
require (
cloud.google.com/go v0.102.1 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
Expand All @@ -37,7 +38,7 @@ require (
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -224,6 +225,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
Expand Down Expand Up @@ -425,8 +428,9 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down
Binary file modified img/loggo_log.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions internal/color/color.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ var (
FieldStyle = tcell.StyleDefault.
Background(ColorBackgroundField).
Foreground(ColorForegroundField)
PlaceholderStyle = tcell.StyleDefault.
Background(ColorBackgroundField).
Foreground(tcell.ColorDarkGray)
SelectStyle = tcell.StyleDefault.
Background(ColorSelectedBackground).
Foreground(ColorSelectedForeground)
Expand Down
6 changes: 3 additions & 3 deletions internal/config/adpatative_log_confiig.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"strings"
)

func MakeConfigFromSample(sample []map[string]interface{}, mergeWith ...Key) *Config {
func MakeConfigFromSample(sample []map[string]interface{}, mergeWith ...Key) (*Config, map[string]*Key) {
keyMap := make(map[string]*Key)
for i := range mergeWith {
v := mergeWith[i]
Expand Down Expand Up @@ -96,8 +96,7 @@ func MakeConfigFromSample(sample []map[string]interface{}, mergeWith ...Key) *Co
for _, v := range sk {
c.Keys = append(c.Keys, *keyMap[v])
}

return c
return c, keyMap
}

type preBakedRule struct {
Expand All @@ -117,6 +116,7 @@ func (p preBakedRule) Keys() []string {
for k := range p.keyMatchesAny {
arr = append(arr, k)
}
sort.Strings(arr)
return arr
}

Expand Down
20 changes: 4 additions & 16 deletions internal/config/log_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,15 @@ func (c *Config) Save(fileName string) error {
return nil
}

func (c *Config) KeyMap() map[string]Key {
nk := make(map[string]Key)
func (c *Config) KeyMap() map[string]*Key {
nk := make(map[string]*Key)
for _, k := range c.Keys {
nk[k.Name] = k
kp := &k
nk[k.Name] = kp
}
return nk
}

func (c *Config) Merge(c2 *Config) {
nk := c2.KeyMap()
for _, v := range c.Keys {
if _, ok := nk[v.Name]; ok {
delete(nk, v.Name)
}
}
for _, v := range nk {
v2 := v
c.Keys = append(c.Keys, v2)
}
}

type Color struct {
Foreground string `json:"foreground" yaml:"foreground"`
Background string `json:"background" yaml:"background"`
Expand Down
1 change: 1 addition & 0 deletions internal/loggo/app_scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func (a *appScaffold) ShowPrefabModal(text string, width, height int, buttons ..
modal := tview.NewFlex().SetDirection(tview.FlexRow)
modal.SetBackgroundColor(tcell.ColorDarkBlue)
mainContent := tview.NewTextView().
SetDynamicColors(true).
SetTextAlign(tview.AlignCenter).
SetWordWrap(true).
SetText(text)
Expand Down
195 changes: 149 additions & 46 deletions internal/loggo/filter_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,74 +23,177 @@ THE SOFTWARE.
package loggo

import (
"fmt"
"strings"

"github.com/aurc/loggo/internal/filter"

"github.com/aurc/loggo/internal/color"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)

type FilterView struct {
tview.Flex
app Loggo
nameField *tview.InputField
filterTree *tview.TreeView
keysTable *tview.Table
keyNameField *tview.InputField
operation *tview.DropDown
value1 *tview.InputField
value2 *tview.InputField
setupPane *tview.Flex
showQuit bool
app Loggo
expressionField *tview.InputField
buttonSearch *tview.Button
buttonClear *tview.Button
keyFinderField *tview.InputField
filterCallback func(*filter.Expression)
}

func NewFilterView(app Loggo, showQuit bool) *FilterView {
func NewFilterView(app Loggo, filterCallback func(*filter.Expression)) *FilterView {
tv := &FilterView{
Flex: *tview.NewFlex(),
app: app,
showQuit: showQuit,
Flex: *tview.NewFlex(),
app: app,
filterCallback: filterCallback,
}
tv.makeUIComponents()
tv.makeLayouts()
tv.makeKeysTableData()
tv.app.SetFocus(tv.keysTable)
return tv
}

func (t *FilterView) makeUIComponents() {
t.nameField = tview.NewInputField()
t.filterTree = tview.NewTreeView()
t.keysTable = tview.NewTable().SetSelectable(true, false)
t.keyNameField = tview.NewInputField()
t.operation = tview.NewDropDown()
t.value1 = tview.NewInputField()
t.value2 = tview.NewInputField()
t.expressionField = tview.NewInputField().
SetPlaceholder("Filter Expression...").
SetFieldStyle(color.FieldStyle).
SetPlaceholderStyle(color.PlaceholderStyle)
t.expressionField.
SetBackgroundColor(color.ColorBackgroundField)
t.buttonSearch = tview.NewButton("Search").SetSelectedFunc(func() {
t.search()
})
t.buttonClear = tview.NewButton("Clear").SetSelectedFunc(func() {
t.expressionField.SetText("")
t.app.SetFocus(t.expressionField)
if t.filterCallback != nil {
t.filterCallback(nil)
}
})

t.keyFinderField = tview.NewInputField().SetPlaceholder("Start typing to find a key...")
t.keyFinderField.SetAutocompleteFunc(func(currentText string) (entries []string) {
matches := make([]string, 0)
for _, v := range t.app.Config().Keys {
vt := strings.ToLower(strings.TrimSpace(v.Name))
ct := strings.ToLower(strings.TrimSpace(currentText))
if strings.Contains(vt, ct) && len(ct) > 0 || ct == "*" {
matches = append(matches, v.Name)
}
}
return matches
})

t.keyFinderField.SetDoneFunc(func(key tcell.Key) {
switch key {
case tcell.KeyEnter, tcell.KeyTAB:
t.addKey()
case tcell.KeyEsc:
t.keyFinderField.SetText("")
}
})

t.keyFinderField.SetBlurFunc(func() {
if len(t.keyFinderField.GetText()) > 0 {
go func() {
t.keyFinderField.SetText("")
t.keyFinderField.InputHandler()(tcell.NewEventKey(tcell.KeyEsc, '0', 0), func(p tview.Primitive) {})
}()
}
})

t.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEnter:
if t.expressionField.HasFocus() {
t.search()
return nil
}
case tcell.KeyEsc:
if t.expressionField.HasFocus() {
t.app.SetFocus(t.buttonClear)
}
}
return event
})
}

func (t *FilterView) search() {
exp, err := filter.ParseFilterExpression(t.expressionField.GetText())
if err != nil {
t.app.ShowPrefabModal(fmt.Sprintf("[yellow::b]Invalid filter expression:[-::-]\n[::i]%v", err), 50, 10,
tview.NewButton("Ok").SetSelectedFunc(func() {
t.app.DismissModal()
}))
}
if t.filterCallback != nil {
t.filterCallback(exp)
}
}
func (t *FilterView) addKey() {
tex := t.expressionField.GetText()
t.expressionField.SetText(tex + " " + t.keyFinderField.GetText())
t.keyFinderField.SetText("")
t.app.SetFocus(t.expressionField)
}

func (t *FilterView) makeLayouts() {
t.Flex.Clear()
filterRow := tview.NewFlex().SetDirection(tview.FlexColumn)
filterField := tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(tview.NewTextView().SetText("🔎").SetTextAlign(tview.AlignCenter), 4, 1, true).
AddItem(t.expressionField, 0, 1, true)
filterField.SetBorder(true)
filterRow.
AddItem(filterField, 0, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(tview.NewBox(), 1, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(tview.NewBox(), 1, 1, false).
AddItem(t.buttonSearch, 10, 1, false).
AddItem(tview.NewBox(), 1, 1, false).
AddItem(t.buttonClear, 10, 1, false), 1, 1, false).
AddItem(tview.NewBox(), 1, 1, false),
23, 1, true)

body := tview.NewFlex().SetDirection(tview.FlexRow)
body.SetBorder(true)
t.Flex.Clear().SetDirection(tview.FlexColumn).
AddItem(body, 0, 1, false).
AddItem(t.keysTable, 40, 1, true)

t.setupPane = tview.NewFlex().SetDirection(tview.FlexRow)
body.Clear().
AddItem(t.filterTree, 0, 1, false).
AddItem(t.setupPane, 5, 1, false)
t.makeSetupPane()
}
okButton := tview.NewButton("OK").SetSelectedFunc(t.addKey)
okButton.SetBackgroundColor(tcell.ColorGreen)
actionBar := tview.NewFlex().SetDirection(tview.FlexColumn)
actionBar.AddItem(tview.NewTextView().SetText(" 🔑 Finder:"), 12, 0, false)
actionBar.AddItem(t.keyFinderField, 0, 1, false).
AddItem(tview.NewBox(), 1, 1, false).
AddItem(okButton, 4, 1, false).
AddItem(tview.NewTextView().SetText(" |"), 2, 0, false)
t.addButton(actionBar, "=")
t.addButton(actionBar, "==")
t.addButton(actionBar, "!=")
t.addButton(actionBar, ">")
t.addButton(actionBar, "<")
t.addButton(actionBar, ">=")
t.addButton(actionBar, "<=")
actionBar.AddItem(tview.NewTextView().SetText(" |"), 2, 0, false)
t.addButton(actionBar, "CONTAINS")
t.addButton(actionBar, "BETWEEN")
t.addButton(actionBar, "MATCH")
actionBar.AddItem(tview.NewTextView().SetText(" |"), 2, 0, false)
t.addButton(actionBar, "AND")
t.addButton(actionBar, "OR")
actionBar.AddItem(tview.NewBox(), 24, 1, false)

func (t *FilterView) makeSetupPane() {
t.setupPane.Clear()
}
t.Flex.Clear().SetDirection(tview.FlexRow).
AddItem(filterRow, 3, 1, false).
AddItem(actionBar, 1, 1, false)

func (t *FilterView) makeKeysTableData() {
t.keysTable.SetCell(0, 0, tview.NewTableCell("[yellow::b]Key Name").SetSelectable(false))
for i, v := range t.app.Config().Keys {
t.keysTable.SetCell(i+1, 0, tview.NewTableCell(v.Name).SetSelectable(true))
}
t.keysTable.SetSelectionChangedFunc(func(row, column int) {
if row > 0 {
}

}
func (t *FilterView) addButton(ab *tview.Flex, title string) {
b := tview.NewButton(title).SetSelectedFunc(func() {
t.expressionField.SetText(fmt.Sprintf(`%s %s `, t.expressionField.GetText(), title))
t.app.SetFocus(t.expressionField)
})
b.SetBackgroundColor(tcell.ColorGray).SetTitleColor(tcell.ColorWhite)
ab.
AddItem(tview.NewBox(), 1, 1, false).
AddItem(b, len(title)+2, 1, false)
}
Loading

0 comments on commit ad00fd9

Please sign in to comment.