diff --git a/docs/Config.md b/docs/Config.md index efa7d2e5..f2388d5f 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -40,8 +40,6 @@ commandTemplates: viewServiceLogs: '{{ .DockerCompose }} logs --follow {{ .Service.Name }}' rebuildService: '{{ .DockerCompose }} up -d --build {{ .Service.Name }}' recreateService: '{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}' - viewContainerLogs: docker logs --follow --since=60m {{ .Container.ID - }} allLogs: '{{ .DockerCompose }} logs --tail=300 --follow' viewAlLogs: '{{ .DockerCompose }} logs' dockerComposeConfig: '{{ .DockerCompose }} config' diff --git a/pkg/commands/container.go b/pkg/commands/container.go index c7d425a2..f664eba3 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -217,20 +217,6 @@ func (c *Container) Top() (container.ContainerTopOKBody, error) { return c.Client.ContainerTop(context.Background(), c.ID, []string{}) } -// ViewLogs attaches to a subprocess viewing the container's logs -func (c *Container) ViewLogs() (*exec.Cmd, error) { - templateString := c.OSCommand.Config.UserConfig.CommandTemplates.ViewContainerLogs - command := utils.ApplyTemplate( - templateString, - c.DockerCommand.NewCommandObject(CommandObject{Container: c}), - ) - - cmd := c.OSCommand.ExecutableFromString(command) - c.OSCommand.PrepareForChildren(cmd) - - return cmd, nil -} - // PruneContainers prunes containers func (c *DockerCommand) PruneContainers() error { _, err := c.Client.ContainersPrune(context.Background(), filters.Args{}) diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 5625c429..add2ad7d 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -158,16 +158,11 @@ type CommandTemplatesConfig struct { // and ensure they're running before trying to run the service at hand RecreateService string `yaml:"recreateService,omitempty"` - // ViewContainerLogs is like ViewServiceLogs but for containers - ViewContainerLogs string `yaml:"viewContainerLogs,omitempty"` - // AllLogs is for showing what you get from doing `docker-compose logs`. It // combines all the logs together AllLogs string `yaml:"allLogs,omitempty"` - // ViewAllLogs is to AllLogs what ViewContainerLogs is to ContainerLogs. It's - // just the command we use when you want to see all logs in a subprocess with - // no filtering + // ViewAllLogs is the command we use when you want to see all logs in a subprocess with no filtering ViewAllLogs string `yaml:"viewAlLogs,omitempty"` // DockerComposeConfig is the command for viewing the config of your docker @@ -340,8 +335,6 @@ func GetDefaultConfig() UserConfig { DockerComposeConfig: "{{ .DockerCompose }} config", CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet", ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}", - // TODO: use SDK - ViewContainerLogs: "docker logs --timestamps --follow --since=60m {{ .Container.ID }}", }, CustomCommands: CustomCommands{ Containers: []CustomCommand{}, diff --git a/pkg/gui/container_logs.go b/pkg/gui/container_logs.go new file mode 100644 index 00000000..b09d3adc --- /dev/null +++ b/pkg/gui/container_logs.go @@ -0,0 +1,134 @@ +package gui + +import ( + "context" + "fmt" + "io" + "os" + "os/signal" + "time" + + "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/utils" +) + +func (gui *Gui) renderContainerLogsToMain(container *commands.Container) error { + mainView := gui.getMainView() + mainView.Autoscroll = true + mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel + + return gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { + gui.renderContainerLogsToMainAux(container, stop, notifyStopped) + }) +} + +func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, stop, notifyStopped chan struct{}) { + gui.clearMainView() + defer func() { + notifyStopped <- struct{}{} + }() + + ctx, ctxCancel := context.WithCancel(context.Background()) + go func() { + <-stop + ctxCancel() + }() + + mainView := gui.getMainView() + + 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 <-stop: + 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, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Timestamps: gui.Config.UserConfig.Logs.Timestamps, + Since: gui.Config.UserConfig.Logs.Since, + 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 +} diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index df7dad4a..fe4715ea 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -1,15 +1,12 @@ package gui import ( - "context" "encoding/json" "fmt" - "io" "strings" "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/stdcopy" "github.com/fatih/color" "github.com/go-errors/errors" "github.com/jesseduffield/gocui" @@ -69,7 +66,7 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error { switch gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] { case "logs": - if err := gui.renderContainerLogs(container); err != nil { + if err := gui.renderContainerLogsToMain(container); err != nil { return err } case "config": @@ -221,76 +218,6 @@ func (gui *Gui) renderContainerTop(container *commands.Container) error { }) } -func (gui *Gui) renderContainerLogs(container *commands.Container) error { - mainView := gui.getMainView() - mainView.Autoscroll = true - mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel - - return gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { - gui.renderContainerLogsAux(container, stop, notifyStopped) - }) -} - -func (gui *Gui) renderContainerLogsAux(container *commands.Container, stop, notifyStopped chan struct{}) { - gui.clearMainView() - defer func() { - notifyStopped <- struct{}{} - }() - - ctx, ctxCancel := context.WithCancel(context.Background()) - go func() { - <-stop - ctxCancel() - }() - - readCloser, err := gui.DockerCommand.Client.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{ - ShowStdout: true, - ShowStderr: true, - Timestamps: gui.Config.UserConfig.Logs.Timestamps, - Since: gui.Config.UserConfig.Logs.Since, - Follow: true, - }) - if err != nil { - gui.Log.Error(err) - return - } - - mainView := gui.getMainView() - - if container.DetailsLoaded() && container.Details.Config.Tty { - _, err = io.Copy(mainView, readCloser) - if err != nil { - gui.Log.Error(err) - } - } else { - _, err = stdcopy.StdCopy(mainView, mainView, readCloser) - if 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 <-stop: - 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) refreshContainersAndServices() error { containersView := gui.getContainersView() if containersView == nil { @@ -561,13 +488,9 @@ func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error { return nil } - c, err := container.ViewLogs() - if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) - } + gui.renderLogsToStdout(container) - gui.SubProcess = c - return gui.Errors.ErrSubProcess + return nil } func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 875a26e9..aabf1b3b 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -315,7 +315,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "services", Key: 'm', Modifier: gocui.ModNone, - Handler: gui.handleServiceViewLogs, + Handler: gui.handleServiceRenderLogsToMain, Description: gui.Tr.ViewLogs, }, { diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index da7a8b6e..a40569a7 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -132,7 +132,7 @@ func (gui *Gui) renderServiceLogs(service *commands.Service) error { }) } - return gui.renderContainerLogs(service.Container) + return gui.renderContainerLogsToMain(service.Container) } func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error { @@ -285,7 +285,7 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error { return gui.Errors.ErrSubProcess } -func (gui *Gui) handleServiceViewLogs(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error { service, err := gui.getSelectedService() if err != nil { return nil diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go index 61d3e1e9..df23afd6 100644 --- a/pkg/gui/subprocess.go +++ b/pkg/gui/subprocess.go @@ -47,12 +47,12 @@ func (gui *Gui) runCommand() error { gui.SubProcess.Stderr = os.Stdout gui.SubProcess.Stdin = os.Stdin - c := make(chan os.Signal, 1) + stop := make(chan os.Signal, 1) + defer signal.Stop(stop) go func() { - signal.Notify(c, os.Interrupt) - <-c - signal.Stop(c) + signal.Notify(stop, os.Interrupt) + <-stop if err := gui.OSCommand.Kill(gui.SubProcess); err != nil { gui.Log.Error(err) @@ -72,16 +72,7 @@ func (gui *Gui) runCommand() error { gui.SubProcess.Stderr = ioutil.Discard gui.SubProcess = nil - signal.Stop(c) - - 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) - } - } + gui.promptToReturn() return nil }