package gui import ( "context" "fmt" "io" "os" "os/signal" "time" dockerTypes "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stdcopy" "github.com/fatih/color" "github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/tasks" "github.com/jesseduffield/lazydocker/pkg/utils" ) func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.TaskFunc { return gui.NewTickerTask(TickerTaskOpts{ Func: func(ctx context.Context, notifyStopped chan struct{}) { gui.renderContainerLogsToMainAux(container, ctx, notifyStopped) }, Duration: time.Millisecond * 200, // TODO: see why this isn't working (when switching from Top tab to Logs tab in the services panel, the tops tab's content isn't removed) Before: func(ctx context.Context) { gui.clearMainView() }, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Autoscroll: true, }) } func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, ctx context.Context, notifyStopped chan struct{}) { gui.clearMainView() defer func() { notifyStopped <- struct{}{} }() mainView := gui.Views.Main if err := gui.writeContainerLogs(container, ctx, mainView); err != nil { gui.Log.Error(err) } // if we are here because the task has been stopped, we should return // if we are here then the container must have exited, meaning we should wait until it's back again before ticker := time.NewTicker(time.Millisecond * 100) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: result, err := container.Inspect() if err != nil { // if we get an error, then the container has probably been removed so we'll get out of here gui.Log.Error(err) return } if result.State.Running { return } } } } func (gui *Gui) renderLogsToStdout(container *commands.Container) { stop := make(chan os.Signal, 1) defer signal.Stop(stop) ctx, cancel := context.WithCancel(context.Background()) go func() { signal.Notify(stop, os.Interrupt) <-stop cancel() }() if err := gui.g.Suspend(); err != nil { gui.Log.Error(err) return } defer func() { if err := gui.g.Resume(); err != nil { gui.Log.Error(err) } }() if err := gui.writeContainerLogs(container, ctx, os.Stdout); err != nil { gui.Log.Error(err) return } gui.promptToReturn() } func (gui *Gui) promptToReturn() { if !gui.Config.UserConfig.Gui.ReturnImmediately { fmt.Fprintf(os.Stdout, "\n\n%s", utils.ColoredString(gui.Tr.PressEnterToReturn, color.FgGreen)) // wait for enter press if _, err := fmt.Scanln(); err != nil { gui.Log.Error(err) } } } func (gui *Gui) writeContainerLogs(container *commands.Container, ctx context.Context, writer io.Writer) error { readCloser, err := gui.DockerCommand.Client.ContainerLogs(ctx, container.ID, dockerTypes.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Timestamps: gui.Config.UserConfig.Logs.Timestamps, Since: gui.Config.UserConfig.Logs.Since, Tail: gui.Config.UserConfig.Logs.Tail, Follow: true, }) if err != nil { return err } if container.DetailsLoaded() && container.Details.Config.Tty { _, err = io.Copy(writer, readCloser) if err != nil { return err } } else { _, err = stdcopy.StdCopy(writer, writer, readCloser) if err != nil { return err } } return nil }