k9s/internal/views/logs.go

171 lines
3.4 KiB
Go

package views
import (
"context"
"fmt"
"time"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
const (
logBuffSize = 100
flushTimeout = 200 * time.Millisecond
logCoFmt = " Logs([fg:bg:]%s:[hilite:bg:b]%s[-:bg:-]) "
logFmt = " Logs([fg:bg:]%s) "
)
type (
masterView interface {
backFn() actionHandler
appView() *appView
}
logsView struct {
*tview.Pages
app *appView
parent loggable
actions keyActions
cancelFunc context.CancelFunc
}
)
func newLogsView(title string, app *appView, parent loggable) *logsView {
v := logsView{
app: app,
Pages: tview.NewPages(),
parent: parent,
}
return &v
}
// Protocol...
func (v *logsView) reload(co string, parent loggable, prevLogs bool) {
v.parent = parent
v.deletePage()
v.AddPage("logs", newLogView(co, v.app, v.backCmd), true, true)
v.load(co, prevLogs)
}
// SetActions to handle keyboard events.
func (v *logsView) setActions(aa keyActions) {
v.actions = aa
}
// Hints show action hints
func (v *logsView) hints() hints {
l := v.CurrentPage().Item.(*logView)
return l.actions.toHints()
}
func (v *logsView) backFn() actionHandler {
return v.backCmd
}
func (v *logsView) deletePage() {
v.RemovePage("logs")
}
func (v *logsView) stop() {
if v.cancelFunc == nil {
return
}
v.cancelFunc()
log.Debug().Msgf("Canceling logs...")
v.cancelFunc = nil
}
func (v *logsView) load(container string, prevLogs bool) {
if err := v.doLoad(v.parent.getSelection(), container, prevLogs); err != nil {
v.app.flash().err(err)
l := v.CurrentPage().Item.(*logView)
l.log("😂 Doh! No logs are available at this time. Check again later on...")
return
}
v.app.SetFocus(v)
}
func (v *logsView) doLoad(path, co string, prevLogs bool) error {
log.Debug().Msgf("----Container %q", co)
v.stop()
l := v.CurrentPage().Item.(*logView)
l.logs.Clear()
l.setTitle(path, co)
c := make(chan string, 10)
go updateLogs(c, l, logBuffSize)
res, ok := v.parent.getList().Resource().(resource.Tailable)
if !ok {
close(c)
return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource())
}
var ctx context.Context
ctx = context.WithValue(context.Background(), resource.IKey("informer"), v.app.informer)
ctx, v.cancelFunc = context.WithCancel(ctx)
if err := res.Logs(ctx, c, v.logOpts(path, co, prevLogs)); err != nil {
v.cancelFunc()
return err
}
return nil
}
func (v *logsView) logOpts(path, co string, prevLogs bool) resource.LogOptions {
ns, po := namespaced(path)
return resource.LogOptions{
Fqn: resource.Fqn{
Namespace: ns,
Name: po,
Container: co,
},
Lines: int64(v.app.config.K9s.LogRequestSize),
Previous: prevLogs,
}
}
func updateLogs(c <-chan string, l *logView, buffSize int) {
buff, index := make([]string, buffSize), 0
for {
select {
case line, ok := <-c:
if !ok {
log.Debug().Msgf("Closed channel detected. Bailing out...")
l.flush(index, buff)
return
}
if index < buffSize {
buff[index] = line
index++
continue
}
l.flush(index, buff)
index = 0
buff[index] = line
index++
case <-time.After(flushTimeout):
l.flush(index, buff)
index = 0
}
}
}
// ----------------------------------------------------------------------------
// Actions...
func (v *logsView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.stop()
v.parent.switchPage("master")
return evt
}