k9s/internal/view/logs.go

178 lines
3.6 KiB
Go

package view
import (
"context"
"fmt"
"time"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"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() ui.ActionHandler
App() *App
}
// Logs presents a collection of logs.
Logs struct {
*tview.Pages
app *App
parent loggable
actions ui.KeyActions
cancelFunc context.CancelFunc
}
)
// NewLogs returns a new logs viewer.
func NewLogs(title string, parent loggable) *Logs {
return &Logs{
Pages: tview.NewPages(),
parent: parent,
}
}
// Protocol...
func (l *Logs) reload(co string, parent loggable, prevLogs bool) {
l.parent = parent
l.deletePage()
l.AddPage("logs", NewLog(co, l.app, l.backCmd), true, true)
l.load(co, prevLogs)
}
// SetActions to handle keyboard events.
func (l *Logs) setActions(aa ui.KeyActions) {
l.actions = aa
}
// Hints show action hints
func (l *Logs) Hints() model.MenuHints {
v := l.CurrentPage().Item.(*Log)
return v.actions.Hints()
}
func (l *Logs) backFn() ui.ActionHandler {
return l.backCmd
}
func (l *Logs) deletePage() {
l.RemovePage("logs")
}
func (l *Logs) stop() {
if l.cancelFunc == nil {
return
}
l.cancelFunc()
log.Debug().Msgf("Canceling logs...")
l.cancelFunc = nil
}
func (l *Logs) load(container string, prevLogs bool) {
if err := l.doLoad(l.parent.getSelection(), container, prevLogs); err != nil {
l.app.Flash().Err(err)
l := l.CurrentPage().Item.(*Log)
l.log("😂 Doh! No logs are available at this time. Check again later on...")
return
}
l.app.SetFocus(l)
}
func (l *Logs) doLoad(path, co string, prevLogs bool) error {
l.stop()
v := l.CurrentPage().Item.(*Log)
v.logs.Clear()
v.setTitle(path, co)
var ctx context.Context
ctx = context.WithValue(context.Background(), resource.IKey("informer"), l.app.informer)
ctx, l.cancelFunc = context.WithCancel(ctx)
c := make(chan string, 10)
go updateLogs(ctx, c, v, logBuffSize)
res, ok := l.parent.getList().Resource().(resource.Tailable)
if !ok {
close(c)
return fmt.Errorf("Resource %T is not tailable", l.parent.getList().Resource())
}
if err := res.Logs(ctx, c, l.logOpts(path, co, prevLogs)); err != nil {
l.cancelFunc()
close(c)
return err
}
return nil
}
func (l *Logs) 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(l.app.Config.K9s.LogRequestSize),
Previous: prevLogs,
}
}
func updateLogs(ctx context.Context, c <-chan string, l *Log, buffSize int) {
defer func() {
log.Debug().Msgf("updateLogs view bailing out!")
}()
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
case <-ctx.Done():
return
}
}
}
// ----------------------------------------------------------------------------
// Actions...
func (l *Logs) backCmd(evt *tcell.EventKey) *tcell.EventKey {
l.stop()
l.parent.switchPage("master")
return evt
}