mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
Merge pull request #342 from jesseduffield/container-logs-in-sdk
This commit is contained in:
commit
2fa6a8964a
9 changed files with 148 additions and 121 deletions
|
|
@ -40,8 +40,6 @@ commandTemplates:
|
||||||
viewServiceLogs: '{{ .DockerCompose }} logs --follow {{ .Service.Name }}'
|
viewServiceLogs: '{{ .DockerCompose }} logs --follow {{ .Service.Name }}'
|
||||||
rebuildService: '{{ .DockerCompose }} up -d --build {{ .Service.Name }}'
|
rebuildService: '{{ .DockerCompose }} up -d --build {{ .Service.Name }}'
|
||||||
recreateService: '{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}'
|
recreateService: '{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}'
|
||||||
viewContainerLogs: docker logs --follow --since=60m {{ .Container.ID
|
|
||||||
}}
|
|
||||||
allLogs: '{{ .DockerCompose }} logs --tail=300 --follow'
|
allLogs: '{{ .DockerCompose }} logs --tail=300 --follow'
|
||||||
viewAlLogs: '{{ .DockerCompose }} logs'
|
viewAlLogs: '{{ .DockerCompose }} logs'
|
||||||
dockerComposeConfig: '{{ .DockerCompose }} config'
|
dockerComposeConfig: '{{ .DockerCompose }} config'
|
||||||
|
|
|
||||||
|
|
@ -217,20 +217,6 @@ func (c *Container) Top() (container.ContainerTopOKBody, error) {
|
||||||
return c.Client.ContainerTop(context.Background(), c.ID, []string{})
|
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
|
// PruneContainers prunes containers
|
||||||
func (c *DockerCommand) PruneContainers() error {
|
func (c *DockerCommand) PruneContainers() error {
|
||||||
_, err := c.Client.ContainersPrune(context.Background(), filters.Args{})
|
_, 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
|
// and ensure they're running before trying to run the service at hand
|
||||||
RecreateService string `yaml:"recreateService,omitempty"`
|
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
|
// AllLogs is for showing what you get from doing `docker-compose logs`. It
|
||||||
// combines all the logs together
|
// combines all the logs together
|
||||||
AllLogs string `yaml:"allLogs,omitempty"`
|
AllLogs string `yaml:"allLogs,omitempty"`
|
||||||
|
|
||||||
// ViewAllLogs is to AllLogs what ViewContainerLogs is to ContainerLogs. It's
|
// ViewAllLogs is the command we use when you want to see all logs in a subprocess with no filtering
|
||||||
// just the command we use when you want to see all logs in a subprocess with
|
|
||||||
// no filtering
|
|
||||||
ViewAllLogs string `yaml:"viewAlLogs,omitempty"`
|
ViewAllLogs string `yaml:"viewAlLogs,omitempty"`
|
||||||
|
|
||||||
// DockerComposeConfig is the command for viewing the config of your docker
|
// DockerComposeConfig is the command for viewing the config of your docker
|
||||||
|
|
@ -340,8 +335,6 @@ func GetDefaultConfig() UserConfig {
|
||||||
DockerComposeConfig: "{{ .DockerCompose }} config",
|
DockerComposeConfig: "{{ .DockerCompose }} config",
|
||||||
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet",
|
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet",
|
||||||
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}",
|
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}",
|
||||||
// TODO: use SDK
|
|
||||||
ViewContainerLogs: "docker logs --timestamps --follow --since=60m {{ .Container.ID }}",
|
|
||||||
},
|
},
|
||||||
CustomCommands: CustomCommands{
|
CustomCommands: CustomCommands{
|
||||||
Containers: []CustomCommand{},
|
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
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/gocui"
|
"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] {
|
switch gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] {
|
||||||
case "logs":
|
case "logs":
|
||||||
if err := gui.renderContainerLogs(container); err != nil {
|
if err := gui.renderContainerLogsToMain(container); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "config":
|
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 {
|
func (gui *Gui) refreshContainersAndServices() error {
|
||||||
containersView := gui.getContainersView()
|
containersView := gui.getContainersView()
|
||||||
if containersView == nil {
|
if containersView == nil {
|
||||||
|
|
@ -561,13 +488,9 @@ func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := container.ViewLogs()
|
gui.renderLogsToStdout(container)
|
||||||
if err != nil {
|
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.SubProcess = c
|
return nil
|
||||||
return gui.Errors.ErrSubProcess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,8 @@ outer:
|
||||||
|
|
||||||
if errorCount > 0 {
|
if errorCount > 0 {
|
||||||
select {
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
onError(err)
|
onError(err)
|
||||||
continue outer
|
continue outer
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
ViewName: "services",
|
ViewName: "services",
|
||||||
Key: 'm',
|
Key: 'm',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleServiceViewLogs,
|
Handler: gui.handleServiceRenderLogsToMain,
|
||||||
Description: gui.Tr.ViewLogs,
|
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 {
|
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
|
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()
|
service, err := gui.getSelectedService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,12 @@ func (gui *Gui) runCommand() error {
|
||||||
gui.SubProcess.Stderr = os.Stdout
|
gui.SubProcess.Stderr = os.Stdout
|
||||||
gui.SubProcess.Stdin = os.Stdin
|
gui.SubProcess.Stdin = os.Stdin
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
stop := make(chan os.Signal, 1)
|
||||||
|
defer signal.Stop(stop)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
signal.Notify(c, os.Interrupt)
|
signal.Notify(stop, os.Interrupt)
|
||||||
<-c
|
<-stop
|
||||||
signal.Stop(c)
|
|
||||||
|
|
||||||
if err := gui.OSCommand.Kill(gui.SubProcess); err != nil {
|
if err := gui.OSCommand.Kill(gui.SubProcess); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.Log.Error(err)
|
||||||
|
|
@ -72,16 +72,7 @@ func (gui *Gui) runCommand() error {
|
||||||
gui.SubProcess.Stderr = ioutil.Discard
|
gui.SubProcess.Stderr = ioutil.Discard
|
||||||
gui.SubProcess = nil
|
gui.SubProcess = nil
|
||||||
|
|
||||||
signal.Stop(c)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue