parent
306e4f239f
commit
df613ec88d
2
Makefile
2
Makefile
|
|
@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME)
|
|||
GIT_REV ?= $(shell git rev-parse --short HEAD)
|
||||
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||
VERSION ?= v0.25.14
|
||||
VERSION ?= v0.25.15
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.25.15
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
|
||||
|
||||
If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
## Maintenance Release!
|
||||
|
||||
Aye! Hot fix on the way...
|
||||
|
||||
---
|
||||
|
||||
## Resolved Issues
|
||||
|
||||
* [Issue #1384](https://github.com/derailed/k9s/issues/1384) Leaving Logs View Causes Crash: "panic: send on closed channel" - with feelings!
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -55,11 +55,11 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
}
|
||||
|
||||
// TailLogs tails a given container logs.
|
||||
func (c *Container) TailLogs(ctx context.Context, logChan LogChan, opts *LogOptions) error {
|
||||
func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
po := Pod{}
|
||||
po.Init(c.Factory, client.NewGVR("v1/pods"))
|
||||
|
||||
return po.TailLogs(ctx, logChan, opts)
|
||||
return po.TailLogs(ctx, opts)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -114,16 +114,16 @@ func (d *Deployment) Restart(ctx context.Context, path string) error {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this Deployment.
|
||||
func (d *Deployment) TailLogs(ctx context.Context, c LogChan, opts *LogOptions) error {
|
||||
func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
dp, err := d.Load(d.Factory, opts.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
|
||||
return fmt.Errorf("No valid selector found on Deployment %s", opts.Path)
|
||||
return nil, fmt.Errorf("No valid selector found on Deployment %s", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts)
|
||||
return podLogs(ctx, dp.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
|
|
|
|||
|
|
@ -91,54 +91,59 @@ func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this DaemonSet.
|
||||
func (d *DaemonSet) TailLogs(ctx context.Context, c LogChan, opts *LogOptions) error {
|
||||
func (d *DaemonSet) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
ds, err := d.GetInstance(opts.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 {
|
||||
return fmt.Errorf("no valid selector found on daemonset %q", opts.Path)
|
||||
return nil, fmt.Errorf("no valid selector found on daemonset %q", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, ds.Spec.Selector.MatchLabels, opts)
|
||||
return podLogs(ctx, ds.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
func podLogs(ctx context.Context, out LogChan, sel map[string]string, opts *LogOptions) error {
|
||||
func podLogs(ctx context.Context, sel map[string]string, opts *LogOptions) ([]LogChan, error) {
|
||||
f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
|
||||
if !ok {
|
||||
return errors.New("expecting a context factory")
|
||||
return nil, errors.New("expecting a context factory")
|
||||
}
|
||||
ls, err := metav1.ParseToLabelSelector(toSelector(sel))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
lsel, err := metav1.LabelSelectorAsSelector(ls)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns, _ := client.Namespaced(opts.Path)
|
||||
oo, err := f.List("v1/pods", ns, true, lsel)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
opts.MultiPods = true
|
||||
|
||||
po := Pod{}
|
||||
po.Init(f, client.NewGVR("v1/pods"))
|
||||
|
||||
outs := make([]LogChan, 0, len(oo))
|
||||
for _, o := range oo {
|
||||
u, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected unstructured got %t", o)
|
||||
return nil, fmt.Errorf("expected unstructured got %t", o)
|
||||
}
|
||||
opts = opts.Clone()
|
||||
opts.Path = client.FQN(u.GetNamespace(), u.GetName())
|
||||
if err := po.TailLogs(ctx, out, opts); err != nil {
|
||||
return err
|
||||
cc, err := po.TailLogs(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outs = append(outs, cc...)
|
||||
}
|
||||
return nil
|
||||
|
||||
return outs, nil
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
|
|
|
|||
|
|
@ -57,23 +57,23 @@ func (j *Job) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this Job.
|
||||
func (j *Job) TailLogs(ctx context.Context, c LogChan, opts *LogOptions) error {
|
||||
func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
o, err := j.Factory.Get(j.gvr.String(), opts.Path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var job batchv1.Job
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
|
||||
if err != nil {
|
||||
return errors.New("expecting a job resource")
|
||||
return nil, errors.New("expecting a job resource")
|
||||
}
|
||||
|
||||
if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 {
|
||||
return fmt.Errorf("No valid selector found on Job %s", opts.Path)
|
||||
return nil, fmt.Errorf("No valid selector found on Job %s", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, job.Spec.Selector.MatchLabels, opts)
|
||||
return podLogs(ctx, job.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
// ScanSA scans for serviceaccount refs.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ func (o *LogOptions) Clone() *LogOptions {
|
|||
DefaultContainer: o.DefaultContainer,
|
||||
Lines: o.Lines,
|
||||
Previous: o.Previous,
|
||||
Head: o.Head,
|
||||
SingleContainer: o.SingleContainer,
|
||||
MultiPods: o.MultiPods,
|
||||
ShowTimestamp: o.ShowTimestamp,
|
||||
|
|
@ -82,14 +83,9 @@ func (o *LogOptions) ToPodLogOptions() *v1.PodLogOptions {
|
|||
TailLines: &o.Lines,
|
||||
}
|
||||
if o.Head {
|
||||
var maxBytes int64 = 1000
|
||||
//var defaultTail int64 = -1
|
||||
//var defaultSince int64
|
||||
|
||||
var maxBytes int64 = 5000
|
||||
opts.Follow = false
|
||||
opts.TailLines, opts.SinceSeconds, opts.SinceTime = nil, nil, nil
|
||||
//opts.TailLines = &defaultTail
|
||||
//opts.SinceSeconds = &defaultSince
|
||||
opts.LimitBytes = &maxBytes
|
||||
return &opts
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
|
|
@ -125,12 +126,12 @@ func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, er
|
|||
return nil, fmt.Errorf("user is not authorized to view pod logs")
|
||||
}
|
||||
|
||||
ns, n := client.Namespaced(path)
|
||||
dial, err := p.Client().DialLogs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns, n := client.Namespaced(path)
|
||||
return dial.CoreV1().Pods(ns).GetLogs(n, opts), nil
|
||||
}
|
||||
|
||||
|
|
@ -177,64 +178,49 @@ func (p *Pod) GetInstance(fqn string) (*v1.Pod, error) {
|
|||
}
|
||||
|
||||
// TailLogs tails a given container logs.
|
||||
func (p *Pod) TailLogs(ctx context.Context, out LogChan, opts *LogOptions) error {
|
||||
func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
|
||||
if !ok {
|
||||
return errors.New("No factory in context")
|
||||
return nil, errors.New("No factory in context")
|
||||
}
|
||||
o, err := fac.Get(p.gvr.String(), opts.Path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
var po v1.Pod
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 {
|
||||
coCounts := len(po.Spec.InitContainers) + len(po.Spec.Containers) + len(po.Spec.EphemeralContainers)
|
||||
if coCounts == 1 {
|
||||
opts.SingleContainer = true
|
||||
}
|
||||
|
||||
outs := make([]LogChan, 0, coCounts)
|
||||
if co, ok := GetDefaultLogContainer(po.ObjectMeta, po.Spec); ok && !opts.AllContainers {
|
||||
opts.DefaultContainer = co
|
||||
return tailLogs(ctx, p, out, opts)
|
||||
return append(outs, tailLogs(ctx, p, opts)), nil
|
||||
}
|
||||
|
||||
if opts.HasContainer() && !opts.AllContainers {
|
||||
return tailLogs(ctx, p, out, opts)
|
||||
return append(outs, tailLogs(ctx, p, opts)), nil
|
||||
}
|
||||
|
||||
var tailed bool
|
||||
for _, co := range po.Spec.InitContainers {
|
||||
o := opts.Clone()
|
||||
o.Container = co.Name
|
||||
if err := tailLogs(ctx, p, out, o); err != nil {
|
||||
return err
|
||||
}
|
||||
tailed = true
|
||||
outs = append(outs, tailLogs(ctx, p, o))
|
||||
}
|
||||
for _, co := range po.Spec.Containers {
|
||||
o := opts.Clone()
|
||||
o.Container = co.Name
|
||||
if err := tailLogs(ctx, p, out, o); err != nil {
|
||||
return err
|
||||
}
|
||||
tailed = true
|
||||
outs = append(outs, tailLogs(ctx, p, o))
|
||||
}
|
||||
for _, co := range po.Spec.EphemeralContainers {
|
||||
o := opts.Clone()
|
||||
o.Container = co.Name
|
||||
if err := tailLogs(ctx, p, out, o); err != nil {
|
||||
return err
|
||||
}
|
||||
tailed = true
|
||||
outs = append(outs, tailLogs(ctx, p, o))
|
||||
}
|
||||
|
||||
if !tailed {
|
||||
return fmt.Errorf("no loggable containers found for pod %s", opts.Path)
|
||||
}
|
||||
|
||||
return nil
|
||||
return outs, nil
|
||||
}
|
||||
|
||||
// ScanSA scans for ServiceAccount refs.
|
||||
|
|
@ -325,78 +311,90 @@ func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func tailLogs(ctx context.Context, logger Logger, out LogChan, opts *LogOptions) error {
|
||||
func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan {
|
||||
var (
|
||||
err error
|
||||
req *restclient.Request
|
||||
stream io.ReadCloser
|
||||
out = make(LogChan, 2)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
o := opts.ToPodLogOptions()
|
||||
done:
|
||||
for r := 0; r < logRetryCount; r++ {
|
||||
var e error
|
||||
req, err = logger.Logs(opts.Path, o)
|
||||
if err == nil {
|
||||
// This call will block if nothing is in the stream!!
|
||||
if stream, err = req.Stream(ctx); err == nil {
|
||||
go readLogs(ctx, stream, out, opts)
|
||||
break
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
log.Debug().Msgf("<<< RETRY-TAIL DONE!!! %s", opts.Info())
|
||||
}()
|
||||
podOpts := opts.ToPodLogOptions()
|
||||
log.Debug().Msgf(">>> RETRY-TAIL START %s", opts.Info())
|
||||
var stream io.ReadCloser
|
||||
for r := 0; r < logRetryCount; r++ {
|
||||
var e error
|
||||
req, err := logger.Logs(opts.Path, podOpts)
|
||||
if err == nil {
|
||||
// This call will block if nothing is in the stream!!
|
||||
if stream, err = req.Stream(ctx); err == nil {
|
||||
wg.Add(1)
|
||||
go readLogs(ctx, &wg, stream, out, opts)
|
||||
return
|
||||
}
|
||||
e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info())
|
||||
log.Error().Err(e).Msg("logs-stream")
|
||||
} else {
|
||||
e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info())
|
||||
log.Error().Err(e).Msg("log-request")
|
||||
}
|
||||
e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info())
|
||||
log.Error().Err(e).Msg("logs-stream")
|
||||
} else {
|
||||
e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info())
|
||||
log.Error().Err(e).Msg("log-request")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
break done
|
||||
default:
|
||||
if e != nil {
|
||||
out <- opts.ToErrLogItem(e)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Debug().Msgf("LOG CANCELED %s", opts.Info())
|
||||
return
|
||||
default:
|
||||
if e != nil {
|
||||
out <- opts.ToErrLogItem(e)
|
||||
}
|
||||
time.Sleep(logRetryWait)
|
||||
}
|
||||
time.Sleep(logRetryWait)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(out)
|
||||
log.Debug().Msgf("<<< LOG-TAILER %s DONE!!", opts.Info())
|
||||
}()
|
||||
|
||||
return err
|
||||
return out
|
||||
}
|
||||
|
||||
func readLogs(ctx context.Context, stream io.ReadCloser, c LogChan, opts *LogOptions) {
|
||||
func readLogs(ctx context.Context, wg *sync.WaitGroup, stream io.ReadCloser, out chan<- *LogItem, opts *LogOptions) {
|
||||
defer func() {
|
||||
if err := stream.Close(); err != nil {
|
||||
log.Error().Err(err).Msgf("Fail to close stream %s", opts.Info())
|
||||
}
|
||||
log.Debug().Msgf("<<< LOG-READER EXIT!!! %s", opts.Info())
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
log.Debug().Msgf("READ_LOGS PROCESSING %#v", opts)
|
||||
log.Debug().Msgf(">>> LOG-READER PROCESSING %#v", opts)
|
||||
r := bufio.NewReader(stream)
|
||||
for {
|
||||
var item *LogItem
|
||||
bytes, err := r.ReadBytes('\n')
|
||||
if err == nil {
|
||||
if bytes, err := r.ReadBytes('\n'); err == nil {
|
||||
item = opts.ToLogItem(bytes)
|
||||
} else {
|
||||
if errors.Is(err, io.EOF) {
|
||||
e := fmt.Errorf("Stream closed %w for %s", err, opts.Info())
|
||||
item = opts.ToErrLogItem(e)
|
||||
log.Warn().Err(e).Msgf("stream closed")
|
||||
log.Debug().Err(e).Msg("log-reader EOF")
|
||||
} else {
|
||||
e := fmt.Errorf("Stream failed %w for %s", err, opts.Info())
|
||||
e := fmt.Errorf("Stream canceled %w for %s", err, opts.Info())
|
||||
item = opts.ToErrLogItem(e)
|
||||
log.Warn().Err(e).Msgf("stream read failed")
|
||||
log.Debug().Err(e).Msg("log-reader canceled")
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(c)
|
||||
return
|
||||
case c <- item:
|
||||
case out <- item:
|
||||
if item.IsError {
|
||||
close(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,16 +132,16 @@ func (*StatefulSet) Load(f Factory, fqn string) (*appsv1.StatefulSet, error) {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this StatefulSet.
|
||||
func (s *StatefulSet) TailLogs(ctx context.Context, c LogChan, opts *LogOptions) error {
|
||||
func (s *StatefulSet) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
sts, err := s.getStatefulSet(opts.Path)
|
||||
if err != nil {
|
||||
return errors.New("expecting StatefulSet resource")
|
||||
return nil, errors.New("expecting StatefulSet resource")
|
||||
}
|
||||
if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 {
|
||||
return fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path)
|
||||
return nil, fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, sts.Spec.Selector.MatchLabels, opts)
|
||||
return podLogs(ctx, sts.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
|
|
|
|||
|
|
@ -24,16 +24,16 @@ type Service struct {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this Service.
|
||||
func (s *Service) TailLogs(ctx context.Context, c LogChan, opts *LogOptions) error {
|
||||
func (s *Service) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||
svc, err := s.GetInstance(opts.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if svc.Spec.Selector == nil || len(svc.Spec.Selector) == 0 {
|
||||
return fmt.Errorf("no valid selector found on Service %s", opts.Path)
|
||||
return nil, fmt.Errorf("no valid selector found on Service %s", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, svc.Spec.Selector, opts)
|
||||
return podLogs(ctx, svc.Spec.Selector, opts)
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ type NodeMaintainer interface {
|
|||
// Loggable represents resources with logs.
|
||||
type Loggable interface {
|
||||
// TaiLogs streams resource logs.
|
||||
TailLogs(ctx context.Context, c LogChan, opts *LogOptions) error
|
||||
TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
|
||||
}
|
||||
|
||||
// Describer describes a resource.
|
||||
|
|
|
|||
|
|
@ -89,19 +89,19 @@ func (l *Log) ToggleShowTimestamp(b bool) {
|
|||
l.Refresh()
|
||||
}
|
||||
|
||||
func (l *Log) Head(ctx context.Context, c dao.LogChan) {
|
||||
func (l *Log) Head(ctx context.Context) {
|
||||
l.mx.Lock()
|
||||
{
|
||||
l.logOptions.Head = true
|
||||
}
|
||||
l.mx.Unlock()
|
||||
l.Restart(ctx, c, true)
|
||||
l.Restart(ctx)
|
||||
}
|
||||
|
||||
// SetSinceSeconds sets the logs retrieval time.
|
||||
func (l *Log) SetSinceSeconds(ctx context.Context, c dao.LogChan, i int64) {
|
||||
func (l *Log) SetSinceSeconds(ctx context.Context, i int64) {
|
||||
l.logOptions.SinceSeconds, l.logOptions.Head = i, false
|
||||
l.Restart(ctx, c, true)
|
||||
l.Restart(ctx)
|
||||
}
|
||||
|
||||
// Configure sets logger configuration.
|
||||
|
|
@ -151,18 +151,16 @@ func (l *Log) Refresh() {
|
|||
}
|
||||
|
||||
// Restart restarts the logger.
|
||||
func (l *Log) Restart(ctx context.Context, c dao.LogChan, clear bool) {
|
||||
func (l *Log) Restart(ctx context.Context) {
|
||||
l.Stop()
|
||||
if clear {
|
||||
l.Clear()
|
||||
}
|
||||
l.Clear()
|
||||
l.fireLogResume()
|
||||
l.Start(ctx, c)
|
||||
l.Start(ctx)
|
||||
}
|
||||
|
||||
// Start starts logging.
|
||||
func (l *Log) Start(ctx context.Context, c dao.LogChan) {
|
||||
if err := l.load(ctx, c); err != nil {
|
||||
func (l *Log) Start(ctx context.Context) {
|
||||
if err := l.load(ctx); err != nil {
|
||||
log.Error().Err(err).Msgf("Tail logs failed!")
|
||||
l.fireLogError(err)
|
||||
}
|
||||
|
|
@ -170,7 +168,6 @@ func (l *Log) Start(ctx context.Context, c dao.LogChan) {
|
|||
|
||||
// Stop terminates logging.
|
||||
func (l *Log) Stop() {
|
||||
defer log.Debug().Msgf("<<<< Logger STOPPED!")
|
||||
l.cancel()
|
||||
}
|
||||
|
||||
|
|
@ -214,16 +211,17 @@ func (l *Log) Filter(q string) {
|
|||
l.fireLogBuffChanged(0)
|
||||
}
|
||||
|
||||
func (l *Log) load(ctx context.Context, c dao.LogChan) error {
|
||||
func (l *Log) cancel() {
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
if l.cancelFn != nil {
|
||||
l.cancelFn()
|
||||
log.Debug().Msgf("!!! LOG-MODEL CANCELED !!!")
|
||||
l.cancelFn = nil
|
||||
}
|
||||
}
|
||||
|
||||
ctx = context.WithValue(context.Background(), internal.KeyFactory, l.factory)
|
||||
ctx, l.cancelFn = context.WithCancel(ctx)
|
||||
go l.updateLogs(ctx, c)
|
||||
|
||||
func (l *Log) load(ctx context.Context) error {
|
||||
accessor, err := dao.AccessorFor(l.factory, l.gvr)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -233,36 +231,28 @@ func (l *Log) load(ctx context.Context, c dao.LogChan) error {
|
|||
return fmt.Errorf("Resource %s is not Loggable", l.gvr)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = loggable.TailLogs(ctx, c, l.logOptions); err != nil {
|
||||
log.Error().Err(err).Msgf("Tail logs failed")
|
||||
l.cancel()
|
||||
l.fireLogError(err)
|
||||
}
|
||||
}()
|
||||
l.cancel()
|
||||
ctx = context.WithValue(ctx, internal.KeyFactory, l.factory)
|
||||
ctx, l.cancelFn = context.WithCancel(ctx)
|
||||
|
||||
cc, err := loggable.TailLogs(ctx, l.logOptions)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Tail logs failed")
|
||||
l.cancel()
|
||||
l.fireLogError(err)
|
||||
}
|
||||
for _, c := range cc {
|
||||
go l.updateLogs(ctx, c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Log) cancel() {
|
||||
l.mx.Lock()
|
||||
{
|
||||
if l.cancelFn == nil {
|
||||
l.mx.Unlock()
|
||||
return
|
||||
}
|
||||
l.cancelFn()
|
||||
l.cancelFn = nil
|
||||
}
|
||||
l.mx.Unlock()
|
||||
}
|
||||
|
||||
// Append adds a log line.
|
||||
func (l *Log) Append(line *dao.LogItem) {
|
||||
if line == nil || line.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
l.logOptions.SinceTime = line.GetTimestamp()
|
||||
|
|
@ -289,19 +279,18 @@ func (l *Log) Notify() {
|
|||
}
|
||||
|
||||
// ToggleAllContainers toggles to show all containers logs.
|
||||
func (l *Log) ToggleAllContainers(ctx context.Context, c dao.LogChan) {
|
||||
func (l *Log) ToggleAllContainers(ctx context.Context) {
|
||||
l.logOptions.ToggleAllContainers()
|
||||
l.Restart(ctx, c, true)
|
||||
l.Restart(ctx)
|
||||
}
|
||||
|
||||
func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) {
|
||||
defer log.Debug().Msgf("updateLogs view bailing out!")
|
||||
|
||||
defer log.Debug().Msgf("<<< LOG-MODEL UPDATER DONE %s!!!!", l.logOptions.Info())
|
||||
log.Debug().Msgf(">>> START LOG-MODEL UPDATER %s", l.logOptions.Info())
|
||||
for {
|
||||
select {
|
||||
case item, ok := <-c:
|
||||
if !ok {
|
||||
log.Debug().Msgf("Closed channel detected. Bailing out!")
|
||||
l.Append(item)
|
||||
l.Notify()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -99,10 +99,9 @@ func TestLogStartStop(t *testing.T) {
|
|||
v := newTestView()
|
||||
m.AddListener(v)
|
||||
|
||||
c := make(dao.LogChan, 2)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
m.Start(ctx, c)
|
||||
m.Start(ctx)
|
||||
data := dao.NewLogItems()
|
||||
data.Add(dao.NewLogItemFromString("line1"), dao.NewLogItemFromString("line2"))
|
||||
for _, d := range data.Items() {
|
||||
|
|
@ -227,10 +226,9 @@ func TestToggleAllContainers(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := make(dao.LogChan, 2)
|
||||
m.ToggleAllContainers(ctx, c)
|
||||
m.ToggleAllContainers(ctx)
|
||||
assert.Equal(t, "", m.GetContainer())
|
||||
m.ToggleAllContainers(ctx, c)
|
||||
m.ToggleAllContainers(ctx)
|
||||
assert.Equal(t, "blee", m.GetContainer())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
func (a *App) bindKeys() {
|
||||
a.AddActions(ui.KeyActions{
|
||||
ui.KeyShiftG: ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false),
|
||||
ui.KeyShift9: ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false),
|
||||
tcell.KeyCtrlE: ui.NewSharedKeyAction("ToggleHeader", a.toggleHeaderCmd, false),
|
||||
tcell.KeyCtrlG: ui.NewSharedKeyAction("toggleCrumbs", a.toggleCrumbsCmd, false),
|
||||
ui.KeyHelp: ui.NewSharedKeyAction("Help", a.helpCmd, false),
|
||||
|
|
@ -193,9 +193,10 @@ func (a *App) bindKeys() {
|
|||
}
|
||||
|
||||
func (a *App) dumpGOR(evt *tcell.EventKey) *tcell.EventKey {
|
||||
bb := make([]byte, 5_000_000)
|
||||
runtime.Stack(bb, true)
|
||||
log.Debug().Msgf("GOR\n%s", string(bb))
|
||||
log.Debug().Msgf("GOR %d", runtime.NumGoroutine())
|
||||
// bb := make([]byte, 5_000_000)
|
||||
// runtime.Stack(bb, true)
|
||||
// log.Debug().Msgf("GOR\n%s", string(bb))
|
||||
return evt
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ type Log struct {
|
|||
cancelFn context.CancelFunc
|
||||
cancelUpdates bool
|
||||
mx sync.Mutex
|
||||
logChan dao.LogChan
|
||||
follow bool
|
||||
}
|
||||
|
||||
|
|
@ -51,15 +50,18 @@ var _ model.Component = (*Log)(nil)
|
|||
// NewLog returns a new viewer.
|
||||
func NewLog(gvr client.GVR, opts *dao.LogOptions) *Log {
|
||||
l := Log{
|
||||
Flex: tview.NewFlex(),
|
||||
logChan: make(dao.LogChan, 2),
|
||||
model: model.NewLog(gvr, opts, defaultFlushTimeout),
|
||||
follow: true,
|
||||
Flex: tview.NewFlex(),
|
||||
model: model.NewLog(gvr, opts, defaultFlushTimeout),
|
||||
follow: true,
|
||||
}
|
||||
|
||||
return &l
|
||||
}
|
||||
|
||||
func logChan() dao.LogChan {
|
||||
return make(dao.LogChan, 2)
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (l *Log) Init(ctx context.Context) (err error) {
|
||||
if l.app, err = extractApp(ctx); err != nil {
|
||||
|
|
@ -91,7 +93,7 @@ func (l *Log) Init(ctx context.Context) (err error) {
|
|||
l.bindKeys()
|
||||
|
||||
l.StylesChanged(l.app.Styles)
|
||||
l.goFullScreen()
|
||||
l.toggleFullScreen()
|
||||
|
||||
l.model.Init(l.app.factory)
|
||||
l.updateTitle()
|
||||
|
|
@ -126,7 +128,6 @@ func (l *Log) LogResume() {
|
|||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
|
||||
log.Debug().Msgf("LOG_RESUME!!!")
|
||||
l.cancelUpdates = false
|
||||
}
|
||||
|
||||
|
|
@ -196,18 +197,27 @@ func (l *Log) ExtraHints() map[string]string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (l *Log) getContext() context.Context {
|
||||
func (l *Log) cancel() {
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
if l.cancelFn != nil {
|
||||
log.Debug().Msgf("!!! LOG-VIEWER CANCELED !!!")
|
||||
l.cancelFn()
|
||||
l.cancelFn = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Log) getContext() context.Context {
|
||||
l.cancel()
|
||||
ctx := context.Background()
|
||||
ctx, l.cancelFn = context.WithCancel(ctx)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Start runs the component.
|
||||
func (l *Log) Start() {
|
||||
l.model.Restart(l.getContext(), l.logChan, true)
|
||||
l.model.Start(l.getContext())
|
||||
l.model.AddListener(l)
|
||||
l.app.Styles.AddListener(l)
|
||||
l.logs.cmdBuff.AddListener(l)
|
||||
|
|
@ -219,14 +229,7 @@ func (l *Log) Start() {
|
|||
func (l *Log) Stop() {
|
||||
l.model.RemoveListener(l)
|
||||
l.model.Stop()
|
||||
l.mx.Lock()
|
||||
{
|
||||
if l.cancelFn != nil {
|
||||
l.cancelFn()
|
||||
l.cancelFn = nil
|
||||
}
|
||||
}
|
||||
l.mx.Unlock()
|
||||
l.cancel()
|
||||
l.app.Styles.RemoveListener(l)
|
||||
l.logs.cmdBuff.RemoveListener(l)
|
||||
l.logs.cmdBuff.RemoveListener(l.app.Prompt())
|
||||
|
|
@ -238,7 +241,7 @@ func (l *Log) Name() string { return logTitle }
|
|||
func (l *Log) bindKeys() {
|
||||
l.logs.Actions().Set(ui.KeyActions{
|
||||
ui.Key0: ui.NewKeyAction("tail", l.sinceCmd(-1), true),
|
||||
ui.Key1: ui.NewKeyAction("head", l.head(), true),
|
||||
ui.Key1: ui.NewKeyAction("head", l.sinceCmd(0), true),
|
||||
ui.Key2: ui.NewKeyAction("1m", l.sinceCmd(60), true),
|
||||
ui.Key3: ui.NewKeyAction("5m", l.sinceCmd(5*60), true),
|
||||
ui.Key4: ui.NewKeyAction("15m", l.sinceCmd(15*60), true),
|
||||
|
|
@ -355,24 +358,17 @@ func (l *Log) Flush(lines [][]byte) {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Actions()...
|
||||
// Actions...
|
||||
|
||||
func (l *Log) head() func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
log.Debug().Msgf("!!!!HEAD!!!!")
|
||||
l.cancelUpdates = true
|
||||
l.logs.Clear()
|
||||
l.model.Head(l.getContext(), l.logChan)
|
||||
l.updateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Log) sinceCmd(a int) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (l *Log) sinceCmd(n int) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
l.logs.Clear()
|
||||
l.model.SetSinceSeconds(l.getContext(), l.logChan, int64(a))
|
||||
ctx := l.getContext()
|
||||
if n == 0 {
|
||||
l.model.Head(ctx)
|
||||
} else {
|
||||
l.model.SetSinceSeconds(ctx, int64(n))
|
||||
}
|
||||
l.updateTitle()
|
||||
|
||||
return nil
|
||||
|
|
@ -384,7 +380,7 @@ func (l *Log) toggleAllContainers(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
l.indicator.ToggleAllContainers()
|
||||
l.model.ToggleAllContainers(l.getContext(), l.logChan)
|
||||
l.model.ToggleAllContainers(l.getContext())
|
||||
l.updateTitle()
|
||||
|
||||
return nil
|
||||
|
|
@ -499,12 +495,6 @@ func (l *Log) toggleAutoScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
l.indicator.ToggleAutoScroll()
|
||||
l.follow = l.indicator.AutoScroll()
|
||||
// if l.indicator.AutoScroll() {
|
||||
|
||||
// // l.model.Restart(l.getContext(), l.logChan, false)
|
||||
// } else {
|
||||
// // l.model.Stop()
|
||||
// }
|
||||
l.indicator.Refresh()
|
||||
|
||||
return nil
|
||||
|
|
@ -515,13 +505,13 @@ func (l *Log) toggleFullScreenCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
l.indicator.ToggleFullScreen()
|
||||
l.goFullScreen()
|
||||
l.toggleFullScreen()
|
||||
l.indicator.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Log) goFullScreen() {
|
||||
func (l *Log) toggleFullScreen() {
|
||||
l.SetFullScreen(l.indicator.FullScreen())
|
||||
l.Box.SetBorder(!l.indicator.FullScreen())
|
||||
if l.indicator.FullScreen() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue