diff --git a/Makefile b/Makefile
index 1199104b..3bb4d0b1 100644
--- a/Makefile
+++ b/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}
diff --git a/change_logs/release_v0.25.15.md b/change_logs/release_v0.25.15.md
new file mode 100644
index 00000000..73d5bdd4
--- /dev/null
+++ b/change_logs/release_v0.25.15.md
@@ -0,0 +1,27 @@
+
+
+# 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!
+
+---
+
+
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
diff --git a/internal/dao/container.go b/internal/dao/container.go
index f3ae0428..12d32f3a 100644
--- a/internal/dao/container.go
+++ b/internal/dao/container.go
@@ -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)
}
// ----------------------------------------------------------------------------
diff --git a/internal/dao/dp.go b/internal/dao/dp.go
index 55d51309..e07b7ffc 100644
--- a/internal/dao/dp.go
+++ b/internal/dao/dp.go
@@ -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.
diff --git a/internal/dao/ds.go b/internal/dao/ds.go
index cb91a05d..aee85d34 100644
--- a/internal/dao/ds.go
+++ b/internal/dao/ds.go
@@ -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.
diff --git a/internal/dao/job.go b/internal/dao/job.go
index 43d531b5..db0a2080 100644
--- a/internal/dao/job.go
+++ b/internal/dao/job.go
@@ -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.
diff --git a/internal/dao/log_options.go b/internal/dao/log_options.go
index cc7e9f19..cd3015a2 100644
--- a/internal/dao/log_options.go
+++ b/internal/dao/log_options.go
@@ -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
}
diff --git a/internal/dao/pod.go b/internal/dao/pod.go
index e789c046..4cebf749 100644
--- a/internal/dao/pod.go
+++ b/internal/dao/pod.go
@@ -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
}
}
diff --git a/internal/dao/sts.go b/internal/dao/sts.go
index f05323b2..c7b469b7 100644
--- a/internal/dao/sts.go
+++ b/internal/dao/sts.go
@@ -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.
diff --git a/internal/dao/svc.go b/internal/dao/svc.go
index d4242952..85d8eb56 100644
--- a/internal/dao/svc.go
+++ b/internal/dao/svc.go
@@ -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.
diff --git a/internal/dao/types.go b/internal/dao/types.go
index 0f1f2d44..dd3e2d61 100644
--- a/internal/dao/types.go
+++ b/internal/dao/types.go
@@ -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.
diff --git a/internal/model/log.go b/internal/model/log.go
index 4c7f6aeb..52290a73 100644
--- a/internal/model/log.go
+++ b/internal/model/log.go
@@ -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
diff --git a/internal/model/log_test.go b/internal/model/log_test.go
index 5ff94f5e..09d00c69 100644
--- a/internal/model/log_test.go
+++ b/internal/model/log_test.go
@@ -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())
}
diff --git a/internal/view/app.go b/internal/view/app.go
index 771412ed..037bec95 100644
--- a/internal/view/app.go
+++ b/internal/view/app.go
@@ -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
}
diff --git a/internal/view/log.go b/internal/view/log.go
index 4a67fe93..1d809d1a 100644
--- a/internal/view/log.go
+++ b/internal/view/log.go
@@ -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() {