fix: deliver last log line when it has no trailing newline (#4846)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
TowyTowy
2026-07-20 03:38:46 +02:00
committed by GitHub
parent 95794779d6
commit f3e61606bd
4 changed files with 120 additions and 9 deletions
+9 -4
View File
@@ -41,9 +41,6 @@ func NewLogReader(r io.Reader, tty bool) *LogReader {
func (d *LogReader) Read() (string, container.StdType, error) {
message, stdType, err := d.readEvent()
if err != nil {
return "", 0, err
}
var std container.StdType
switch stdType {
@@ -53,10 +50,18 @@ func (d *LogReader) Read() (string, container.StdType, error) {
std = container.STDERR
}
// A final line without a trailing newline (e.g. a container's last write
// before exiting) arrives together with EOF. Return the partial message
// with the error instead of dropping it; the event generator emits the
// message before handling the error.
if err != nil {
return message, std, err
}
for !strings.HasSuffix(message, "\n") {
tail, _, err := d.readEvent()
if err != nil {
return "", std, err
return message, std, err
}
_, after, _ := strings.Cut(tail, " ")
+84
View File
@@ -0,0 +1,84 @@
package docker
import (
"bytes"
"encoding/binary"
"io"
"strings"
"testing"
"github.com/amir20/dozzle/internal/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func writeFrame(buf *bytes.Buffer, streamType byte, payload string) {
header := make([]byte, 8)
header[0] = streamType
binary.BigEndian.PutUint32(header[4:], uint32(len(payload)))
buf.Write(header)
buf.WriteString(payload)
}
func TestRead_multiplexed(t *testing.T) {
buf := &bytes.Buffer{}
writeFrame(buf, 1, "2024-01-01T00:00:00.000000000Z out\n")
writeFrame(buf, 2, "2024-01-01T00:00:01.000000000Z err\n")
reader := NewLogReader(buf, false)
message, std, err := reader.Read()
require.NoError(t, err)
assert.Equal(t, "2024-01-01T00:00:00.000000000Z out\n", message)
assert.Equal(t, container.STDOUT, std)
message, std, err = reader.Read()
require.NoError(t, err)
assert.Equal(t, "2024-01-01T00:00:01.000000000Z err\n", message)
assert.Equal(t, container.STDERR, std)
_, _, err = reader.Read()
assert.Equal(t, io.EOF, err)
}
func TestRead_lastLineWithoutNewline(t *testing.T) {
buf := &bytes.Buffer{}
writeFrame(buf, 1, "2024-01-01T00:00:00.000000000Z first\n")
writeFrame(buf, 1, "2024-01-01T00:00:01.000000000Z last line without newline")
reader := NewLogReader(buf, false)
message, _, err := reader.Read()
require.NoError(t, err)
assert.Equal(t, "2024-01-01T00:00:00.000000000Z first\n", message)
message, std, err := reader.Read()
assert.Equal(t, io.EOF, err)
assert.Equal(t, container.STDOUT, std)
assert.Equal(t, "2024-01-01T00:00:01.000000000Z last line without newline", message)
}
func TestRead_lastLineWithoutNewlineTTY(t *testing.T) {
input := "2024-01-01T00:00:00.000000000Z first\n2024-01-01T00:00:01.000000000Z last line without newline"
reader := NewLogReader(strings.NewReader(input), true)
message, _, err := reader.Read()
require.NoError(t, err)
assert.Equal(t, "2024-01-01T00:00:00.000000000Z first\n", message)
message, _, err = reader.Read()
assert.Equal(t, io.EOF, err)
assert.Equal(t, "2024-01-01T00:00:01.000000000Z last line without newline", message)
}
func TestRead_continuedFrames(t *testing.T) {
buf := &bytes.Buffer{}
writeFrame(buf, 1, "2024-01-01T00:00:00.000000000Z part one ")
writeFrame(buf, 1, "2024-01-01T00:00:00.000000000Z and part two\n")
reader := NewLogReader(buf, false)
message, _, err := reader.Read()
require.NoError(t, err)
assert.Equal(t, "2024-01-01T00:00:00.000000000Z part one and part two\n", message)
}
+4 -5
View File
@@ -18,10 +18,9 @@ func NewLogReader(reader io.ReadCloser) *LogReader {
}
func (r *LogReader) Read() (string, container.StdType, error) {
// A final line without a trailing newline arrives together with EOF.
// Return the partial line with the error instead of dropping it; the
// event generator emits the message before handling the error.
line, err := r.reader.ReadString('\n')
if err != nil {
return "", 0, err
}
return line, container.STDOUT, nil
return line, container.STDOUT, err
}
+23
View File
@@ -0,0 +1,23 @@
package k8s
import (
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRead_lastLineWithoutNewline(t *testing.T) {
input := "2024-01-01T00:00:00.000000000Z first\n2024-01-01T00:00:01.000000000Z last line without newline"
reader := NewLogReader(io.NopCloser(strings.NewReader(input)))
message, _, err := reader.Read()
require.NoError(t, err)
assert.Equal(t, "2024-01-01T00:00:00.000000000Z first\n", message)
message, _, err = reader.Read()
assert.Equal(t, io.EOF, err)
assert.Equal(t, "2024-01-01T00:00:01.000000000Z last line without newline", message)
}