Skip to content

Commit 73e3c4c

Browse files
committed
Adjust log input and output plugins to support 1MB log lines and resolve issues with truncation logic
1 parent 4300f51 commit 73e3c4c

File tree

12 files changed

+283
-122
lines changed

12 files changed

+283
-122
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package constants
5+
6+
const (
7+
// DefaultMaxEventSize is the default maximum size for log events (1MB)
8+
DefaultMaxEventSize = 1024 * 1024
9+
10+
// PerEventHeaderBytes is the bytes required for metadata for each log event
11+
PerEventHeaderBytes = 200
12+
13+
// DefaultTruncateSuffix is the suffix added to truncated log messages
14+
DefaultTruncateSuffix = "[Truncated...]"
15+
)

plugins/inputs/logfile/fileconfig.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,10 @@ import (
1818

1919
"github.com/aws/amazon-cloudwatch-agent/internal/logscommon"
2020
"github.com/aws/amazon-cloudwatch-agent/logs"
21+
"github.com/aws/amazon-cloudwatch-agent/plugins/inputs/logfile/constants"
2122
"github.com/aws/amazon-cloudwatch-agent/profiler"
2223
)
2324

24-
const (
25-
defaultMaxEventSize = 1024 * 256 //256KB
26-
defaultTruncateSuffix = "[Truncated...]"
27-
)
28-
2925
// The file config presents the structure of configuration for a file to be tailed.
3026
type FileConfig struct {
3127
//The file path for input log file.
@@ -154,11 +150,10 @@ func (config *FileConfig) init() error {
154150
}
155151

156152
if config.MaxEventSize == 0 {
157-
config.MaxEventSize = defaultMaxEventSize
153+
config.MaxEventSize = constants.DefaultMaxEventSize - constants.PerEventHeaderBytes
158154
}
159-
160155
if config.TruncateSuffix == "" {
161-
config.TruncateSuffix = defaultTruncateSuffix
156+
config.TruncateSuffix = constants.DefaultTruncateSuffix
162157
}
163158
if config.RetentionInDays == 0 {
164159
config.RetentionInDays = -1

plugins/inputs/logfile/logfile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ const sampleConfig = `
8686
## Whether file is a named pipe
8787
pipe = false
8888
destination = "cloudwatchlogs"
89-
## Max size of each log event, defaults to 262144 (256KB)
90-
max_event_size = 262144
89+
## Max size of each log event, defaults to 1048576 (1MB)
90+
max_event_size = 1048576
9191
## Suffix to be added to truncated logline to indicate its truncation, defaults to "[Truncated...]"
9292
truncate_suffix = "[Truncated...]"
9393

plugins/inputs/logfile/tail/tail.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"gopkg.in/tomb.v1"
1919

2020
"github.com/aws/amazon-cloudwatch-agent/internal/state"
21+
"github.com/aws/amazon-cloudwatch-agent/plugins/inputs/logfile/constants"
2122
"github.com/aws/amazon-cloudwatch-agent/plugins/inputs/logfile/tail/watch"
2223
)
2324

@@ -306,8 +307,12 @@ func (tail *Tail) readlineUtf16() (string, error) {
306307
}
307308
cur = append(cur, nextByte)
308309
}
309-
// 262144 => 256KB
310-
if resSize+len(cur) >= 262144 {
310+
// Use MaxLineSize if configured, otherwise use DefaultMaxEventSize
311+
maxSize := constants.DefaultMaxEventSize // Use the constant defined in constants package
312+
if tail.MaxLineSize > 0 {
313+
maxSize = tail.MaxLineSize
314+
}
315+
if resSize+len(cur) >= maxSize {
311316
break
312317
}
313318
buf := make([]byte, len(cur))
@@ -534,8 +539,7 @@ func (tail *Tail) waitForChanges() error {
534539
func (tail *Tail) openReader() {
535540
tail.lk.Lock()
536541
if tail.MaxLineSize > 0 {
537-
// add 2 to account for newline characters
538-
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
542+
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize)
539543
} else {
540544
tail.reader = bufio.NewReader(tail.file)
541545
}

plugins/inputs/logfile/tail/tail_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import (
77
"fmt"
88
"log"
99
"os"
10+
"path/filepath"
1011
"strings"
1112
"testing"
1213
"time"
1314

1415
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
1517
)
1618

1719
const linesWrittenToFile int = 10
@@ -197,3 +199,100 @@ func tearDown(tmpfile *os.File) {
197199
exitOnDeletionCheckDuration = time.Minute
198200
exitOnDeletionWaitDuration = 5 * time.Minute
199201
}
202+
203+
func TestUtf16LineSize(t *testing.T) {
204+
tmpfile, err := os.CreateTemp("", "")
205+
require.NoError(t, err)
206+
defer os.Remove(tmpfile.Name())
207+
208+
// Create a UTF-16 BOM
209+
_, err = tmpfile.Write([]byte{0xFE, 0xFF})
210+
require.NoError(t, err)
211+
212+
// Create a tail with a small MaxLineSize
213+
maxLineSize := 100
214+
tail, err := TailFile(tmpfile.Name(), Config{
215+
MaxLineSize: maxLineSize,
216+
Follow: true,
217+
ReOpen: false,
218+
Poll: true,
219+
})
220+
require.NoError(t, err)
221+
defer tail.Stop()
222+
223+
// Write a UTF-16 encoded line that exceeds MaxLineSize when decoded
224+
// Each 'a' will be 2 bytes in UTF-16
225+
utf16Line := make([]byte, 0, maxLineSize*4)
226+
for i := 0; i < maxLineSize*2; i++ {
227+
utf16Line = append(utf16Line, 0x00, 'a')
228+
}
229+
utf16Line = append(utf16Line, 0x00, '\n')
230+
231+
_, err = tmpfile.Write(utf16Line)
232+
require.NoError(t, err)
233+
err = tmpfile.Sync()
234+
require.NoError(t, err)
235+
236+
// Read the line and verify it's truncated
237+
select {
238+
case line := <-tail.Lines:
239+
// The line should be truncated to maxLineSize
240+
assert.LessOrEqual(t, len(line.Text), maxLineSize)
241+
case <-time.After(1 * time.Second):
242+
t.Fatal("timeout waiting for line")
243+
}
244+
}
245+
246+
func TestTail_DefaultBuffer(t *testing.T) {
247+
// Test that default buffer works with normal-sized log lines
248+
tempDir := t.TempDir()
249+
filename := filepath.Join(tempDir, "test.log")
250+
251+
// Create a file with a normal-sized line (1KB - well within default buffer)
252+
normalContent := strings.Repeat("b", 1024) // 1KB
253+
err := os.WriteFile(filename, []byte(normalContent+"\n"), 0600)
254+
require.NoError(t, err)
255+
256+
tail, err := TailFile(filename, Config{
257+
Follow: false,
258+
MustExist: true,
259+
// MaxLineSize not set - should use default buffer
260+
})
261+
require.NoError(t, err)
262+
defer tail.Stop()
263+
264+
select {
265+
case line := <-tail.Lines:
266+
assert.NoError(t, line.Err)
267+
assert.Equal(t, normalContent, line.Text)
268+
case <-time.After(time.Second):
269+
t.Fatal("Timeout waiting for line")
270+
}
271+
}
272+
273+
func TestTail_1MBWithExplicitMaxLineSize(t *testing.T) {
274+
// Test that large lines work when MaxLineSize is explicitly set
275+
tempDir := t.TempDir()
276+
filename := filepath.Join(tempDir, "test.log")
277+
278+
// Create a file with a 512KB line
279+
largeContent := strings.Repeat("b", 512*1024) // 512KB
280+
err := os.WriteFile(filename, []byte(largeContent+"\n"), 0600)
281+
require.NoError(t, err)
282+
283+
tail, err := TailFile(filename, Config{
284+
Follow: false,
285+
MustExist: true,
286+
MaxLineSize: 1024 * 1024, // Explicitly set 1MB buffer
287+
})
288+
require.NoError(t, err)
289+
defer tail.Stop()
290+
291+
select {
292+
case line := <-tail.Lines:
293+
assert.NoError(t, line.Err)
294+
assert.Equal(t, largeContent, line.Text)
295+
case <-time.After(time.Second):
296+
t.Fatal("Timeout waiting for line")
297+
}
298+
}

plugins/inputs/logfile/tailersrc.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,6 @@ func (ts *tailerSrc) runTail() {
238238
} else {
239239
msgBuf.WriteString("\n")
240240
msgBuf.WriteString(text)
241-
if msgBuf.Len() > ts.maxEventSize {
242-
msgBuf.Truncate(ts.maxEventSize - len(ts.truncateSuffix))
243-
msgBuf.WriteString(ts.truncateSuffix)
244-
}
245241
fo.ShiftInt64(line.Offset)
246242
continue
247243
}

0 commit comments

Comments
 (0)