mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 09:54:45 +00:00
fix: deliver last log line when it has no trailing newline (#4846)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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, " ")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user