diff --git a/docs/keybindings/Keybindings_de.md b/docs/keybindings/Keybindings_de.md
index dc9dd08e..3934cf56 100644
--- a/docs/keybindings/Keybindings_de.md
+++ b/docs/keybindings/Keybindings_de.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: anbinden
m: zeige Protokolle
E: exec shell
+ t: cycle how many log lines are shown
c: führe vordefinierten benutzerdefinierten Befehl aus
b: view bulk commands
w: open in browser (first port is http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: start
a: anbinden
m: zeige Protokolle
+ t: cycle how many log lines are shown
U: up project
D: down project
R: zeige Neustartoptionen
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index ae21c4dc..b9d7160f 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: attach
m: view logs
E: exec shell
+ t: cycle how many log lines are shown
c: run predefined custom command
b: view bulk commands
w: open in browser (first port is http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: start
a: attach
m: view logs
+ t: cycle how many log lines are shown
U: up project
D: down project
R: view restart options
diff --git a/docs/keybindings/Keybindings_es.md b/docs/keybindings/Keybindings_es.md
index 6b4eb921..a0a46c51 100644
--- a/docs/keybindings/Keybindings_es.md
+++ b/docs/keybindings/Keybindings_es.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: attach
m: ver logs
E: ejecutar shell
+ t: cycle how many log lines are shown
c: ejecutar comando personalizado
b: ver comandos masivos
w: abrir en navegador (first port is http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: iniciar
a: attach
m: ver logs
+ t: cycle how many log lines are shown
U: levantar proyecto
D: dar de baja el proyecto
R: ver opciones de reinicio
diff --git a/docs/keybindings/Keybindings_fr.md b/docs/keybindings/Keybindings_fr.md
index 109078b2..323be0d1 100644
--- a/docs/keybindings/Keybindings_fr.md
+++ b/docs/keybindings/Keybindings_fr.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: attacher
m: voir les enregistrements
E: exécuter le shell
+ t: cycle how many log lines are shown
c: exécuter une commande prédéfinie
b: voir les commandes groupées
w: ouvrir dans le navigateur (le premier port est http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: démarrer
a: attacher
m: voir les enregistrements
+ t: cycle how many log lines are shown
U: up project
D: down project
R: voir les options de redémarrage
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index 24477006..1d4902e0 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: verbinden
m: bekijk logs
E: exec shell
+ t: cycle how many log lines are shown
c: draai een vooraf bedacht aangepaste opdracht
b: view bulk commands
w: open in browser (first port is http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: start
a: verbinden
m: bekijk logs
+ t: cycle how many log lines are shown
U: up project
D: down project
R: bekijk herstart opties
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 2f4cb93f..c08a1113 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: przyczep
m: pokaż logi
E: exec shell
+ t: cycle how many log lines are shown
c: wykonaj predefiniowaną własną komende
b: view bulk commands
w: open in browser (first port is http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: start
a: przyczep
m: pokaż logi
+ t: cycle how many log lines are shown
U: up project
D: down project
R: pokaż opcje restartu
diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md
index 32a557b5..99183344 100644
--- a/docs/keybindings/Keybindings_pt.md
+++ b/docs/keybindings/Keybindings_pt.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: anexar
m: ver logs
E: executar shell
+ t: cycle how many log lines are shown
c: executar comando personalizado predefinido
b: ver comandos em massa
w: abrir no navegador (primeira porta é http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: iniciar
a: anexar
m: ver logs
+ t: cycle how many log lines are shown
U: subir projeto
D: derrubar projeto
R: ver opções de reinício
diff --git a/docs/keybindings/Keybindings_tr.md b/docs/keybindings/Keybindings_tr.md
index 3192a7c2..d97ea769 100644
--- a/docs/keybindings/Keybindings_tr.md
+++ b/docs/keybindings/Keybindings_tr.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: bağlan/iliştir
m: kayıt defterini görüntüle
E: exec shell
+ t: cycle how many log lines are shown
c: önceden tanımlanmış özel komutu çalıştır
b: view bulk commands
w: open in browser (first port is http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: start
a: bağlan/iliştir
m: kayıt defterini görüntüle
+ t: cycle how many log lines are shown
U: up project
D: down project
R: yeniden başlatma seçeneklerini görüntüle
diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md
index 0c75fbcb..3263d839 100644
--- a/docs/keybindings/Keybindings_zh.md
+++ b/docs/keybindings/Keybindings_zh.md
@@ -25,6 +25,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
a: attach
m: 查看日志
E: 执行shell
+ t: cycle how many log lines are shown
c: 运行预定义的自定义命令
b: 查看批量命令
w: 在浏览器中打开(第一个端口为http)
@@ -45,6 +46,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
S: 启动项目
a: attach
m: 查看日志
+ t: cycle how many log lines are shown
U: 创建并启动容器
D: 停止并移除容器
R: 查看重启选项
diff --git a/pkg/gui/container_logs.go b/pkg/gui/container_logs.go
index bdb81608..7d00517d 100644
--- a/pkg/gui/container_logs.go
+++ b/pkg/gui/container_logs.go
@@ -6,16 +6,88 @@ import (
"io"
"os"
"os/signal"
+ "strconv"
+ "strings"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/fatih/color"
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
+// containerLogsTailPresets are the values we cycle through when the user asks to
+// change how many lines of logs are displayed. An empty string means 'show all logs'.
+var containerLogsTailPresets = []string{"", "50", "100", "200", "500"}
+
+func containerLogsTailLabel(tail string) string {
+ if tail == "" {
+ return "all"
+ }
+
+ return tail
+}
+
+// cycleContainerLogsTail moves to the next tail preset (wrapping back to the start)
+// and forces the logs tab to re-render with the new value.
+func (gui *Gui) cycleContainerLogsTail(g *gocui.Gui, v *gocui.View) error {
+ currentIdx := 0
+ for i, preset := range containerLogsTailPresets {
+ if preset == gui.State.ContainerLogsTail {
+ currentIdx = i
+ break
+ }
+ }
+
+ gui.State.ContainerLogsTail = containerLogsTailPresets[(currentIdx+1)%len(containerLogsTailPresets)]
+
+ if v != nil && v.Name() == "services" {
+ return gui.Panels.Services.HandleSelect()
+ }
+
+ return gui.Panels.Containers.HandleSelect()
+}
+
+// tailLimitingWriter wraps a writer and re-renders it on every line so that it only
+// ever displays the last maxLines lines, keeping a true sliding window rather than
+// just applying `--tail` to the initial snapshot and then growing forever as new
+// lines are followed in.
+type tailLimitingWriter struct {
+ view *gocui.View
+ maxLines int
+ lines []string
+ partial string
+}
+
+func newTailLimitingWriter(view *gocui.View, maxLines int) *tailLimitingWriter {
+ return &tailLimitingWriter{view: view, maxLines: maxLines}
+}
+
+func (w *tailLimitingWriter) Write(p []byte) (int, error) {
+ w.partial += string(p)
+ split := strings.Split(w.partial, "\n")
+ w.partial = split[len(split)-1]
+ newLines := split[:len(split)-1]
+
+ if len(newLines) == 0 {
+ return len(p), nil
+ }
+
+ w.lines = append(w.lines, newLines...)
+ if excess := len(w.lines) - w.maxLines; excess > 0 {
+ w.lines = w.lines[excess:]
+ }
+
+ // SetContent clears and rewrites atomically, so the view never renders empty
+ // in between
+ w.view.SetContent(strings.Join(w.lines, "\n") + "\n")
+
+ return len(p), nil
+}
+
func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.TaskFunc {
return gui.NewTickerTask(TickerTaskOpts{
Func: func(ctx context.Context, notifyStopped chan struct{}) {
@@ -36,8 +108,17 @@ func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, ctx
}()
mainView := gui.Views.Main
+ tail := gui.State.ContainerLogsTail
+ mainView.Subtitle = fmt.Sprintf("tail: %s (%s to cycle)", containerLogsTailLabel(tail), "t")
- if err := gui.writeContainerLogs(container, ctx, mainView); err != nil {
+ var writer io.Writer = mainView
+ if maxLines, err := strconv.Atoi(tail); err == nil && maxLines > 0 {
+ // keep re-truncating to the chosen number of lines as new ones stream in,
+ // instead of just limiting the initial snapshot and growing unbounded after that
+ writer = newTailLimitingWriter(mainView, maxLines)
+ }
+
+ if err := gui.writeContainerLogs(container, ctx, writer); err != nil {
gui.Log.Error(err)
}
@@ -110,7 +191,7 @@ func (gui *Gui) writeContainerLogs(ctr *commands.Container, ctx context.Context,
ShowStderr: true,
Timestamps: gui.Config.UserConfig.Logs.Timestamps,
Since: gui.Config.UserConfig.Logs.Since,
- Tail: gui.Config.UserConfig.Logs.Tail,
+ Tail: gui.State.ContainerLogsTail,
Follow: true,
})
if err != nil {
diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go
index a638ee29..daa2c6c2 100644
--- a/pkg/gui/containers_panel.go
+++ b/pkg/gui/containers_panel.go
@@ -68,7 +68,7 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
// where a container restarts but the new logs don't get read.
// Note that this might be jarring if we have a lot of logs and the container
// restarts a lot, so let's keep an eye on it.
- return "containers-" + container.ID + "-" + container.Container.State
+ return "containers-" + container.ID + "-" + container.Container.State + "-" + gui.State.ContainerLogsTail
},
},
ListPanel: panels.ListPanel[*commands.Container]{
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 6ea2ee6c..76609639 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -82,6 +82,12 @@ type guiState struct {
// if true, we show containers with an 'exited' status in the containers panel
ShowExitedContainers bool
+ // the number of lines to pass as `--tail` when reading a container's logs. Empty
+ // string means we show all of the logs. This starts out as whatever is configured
+ // in the user's config, and can be cycled through ContainerLogsTailPresets via a
+ // keybinding.
+ ContainerLogsTail string
+
ScreenMode WindowMaximisation
// Maintains the state of manual filtering i.e. typing in a substring
@@ -136,6 +142,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
ViewStack: []string{},
ShowExitedContainers: true,
+ ContainerLogsTail: config.UserConfig.Logs.Tail,
ScreenMode: getScreenMode(config),
}
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index df706024..32f30929 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -241,6 +241,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleContainersExecShell,
Description: gui.Tr.ExecShell,
},
+ {
+ ViewName: "containers",
+ Key: 't',
+ Modifier: gocui.ModNone,
+ Handler: gui.cycleContainerLogsTail,
+ Description: gui.Tr.CycleContainerLogsTail,
+ },
{
ViewName: "containers",
Key: 'c',
@@ -318,6 +325,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleServiceRenderLogsToMain,
Description: gui.Tr.ViewLogs,
},
+ {
+ ViewName: "services",
+ Key: 't',
+ Modifier: gocui.ModNone,
+ Handler: gui.cycleContainerLogsTail,
+ Description: gui.Tr.CycleContainerLogsTail,
+ },
{
ViewName: "services",
Key: 'U',
diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go
index edc894e9..4b7fac8b 100644
--- a/pkg/gui/services_panel.go
+++ b/pkg/gui/services_panel.go
@@ -53,7 +53,7 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
if service.Container == nil {
return "services-" + service.ID
}
- return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State
+ return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State + "-" + gui.State.ContainerLogsTail
},
},
ListPanel: panels.ListPanel[*commands.Service]{
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index d86b0c84..eedfc39f 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -65,6 +65,7 @@ type TranslationSet struct {
NextContext string
Attach string
ViewLogs string
+ CycleContainerLogsTail string
UpProject string
DownProject string
ServicesTitle string
@@ -196,6 +197,7 @@ func englishSet() TranslationSet {
NextContext: "next tab",
Attach: "attach",
ViewLogs: "view logs",
+ CycleContainerLogsTail: "cycle how many log lines are shown",
UpProject: "up project",
DownProject: "down project",
RemoveImage: "remove image",