diff --git a/logtail/logtail.go b/logtail/logtail.go index 4544af9d7..f17fd11d5 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -735,6 +735,8 @@ func (l *Logger) Write(buf []byte) (int, error) { if len(buf) == 0 { return 0, nil } + inLen := len(buf) // length as provided to us, before modifications to downstream writers + level, buf := parseAndRemoveLogLevel(buf) if l.stderr != nil && l.stderr != io.Discard && int64(level) <= atomic.LoadInt64(&l.stderrLevel) { if buf[len(buf)-1] == '\n' { @@ -752,7 +754,7 @@ func (l *Logger) Write(buf []byte) (int, error) { b := l.encodeLocked(buf, level) _, err := l.sendLocked(b) - return len(buf), err + return inLen, err } var ( diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index f145d7a35..f0d3f36b2 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -379,3 +379,30 @@ func TestEncode(t *testing.T) { } } } + +// Test that even if Logger.Write modifies the input buffer, we still return the +// length of the input buffer, not what we shrank it down to. Otherwise the +// caller will think we did a short write, violating the io.Writer contract. +func TestLoggerWriteResult(t *testing.T) { + buf := NewMemoryBuffer(100) + lg := &Logger{ + clock: tstest.NewClock(tstest.ClockOpts{Start: time.Unix(123, 0)}), + buffer: buf, + } + + const in = "[v1] foo" + n, err := lg.Write([]byte(in)) + if err != nil { + t.Fatal(err) + } + if got, want := n, len(in); got != want { + t.Errorf("Write = %v; want %v", got, want) + } + back, err := buf.TryReadLine() + if err != nil { + t.Fatal(err) + } + if got, want := string(back), `{"logtail": {"client_time": "1970-01-01T00:02:03Z"}, "v":1,"text": "foo"}`+"\n"; got != want { + t.Errorf("mismatch.\n got: %#q\nwant: %#q", back, want) + } +}