Merge pull request #342 from jesseduffield/container-logs-in-sdk

pull/346/head
Jesse Duffield 2 years ago committed by GitHub
commit 2fa6a8964a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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{},

@ -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 {

@ -317,6 +317,8 @@ outer:
if errorCount > 0 {
select {
case <-ctx.Done():
return
case err := <-errChan:
onError(err)
continue outer

@ -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…
Cancel
Save