mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-21 23:01:03 +00:00
use SDK to print container logs to stdout
This commit is contained in:
parent
d940f2e7ab
commit
d0a53fbe60
8 changed files with 146 additions and 121 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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{})
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
134
pkg/gui/container_logs.go
Normal file
134
pkg/gui/container_logs.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue