@@ -37,8 +37,23 @@ var logConfig string
3737const (
3838 logFileName = "hello-world.log"
3939 logFilePath = utils .LinuxLogsFolderPath + "/" + logFileName
40+
41+ iisLogFileName = "iis-w3c.log"
42+ iisLogFilePath = utils .LinuxLogsFolderPath + "/" + iisLogFileName
43+ iisService = "iis-w3c"
44+ // Distinctive substrings from consecutive IIS W3C records. A concatenated
45+ // intake message contains both.
46+ iisGetToken = "GET /ZenIT/Service/v13/core/Consent"
47+ iisPostToken = "POST /ZenIT/Service/v13/core/Logger"
4048)
4149
50+ // iisW3CRecords is a timestamped #Date header plus two 10.1.48.10 records.
51+ // CombiningAggregator only concatenates aggregate lines onto an open
52+ // startGroup, so the header is required to reproduce the regression.
53+ const iisW3CRecords = `#Date: 2026-08-11 10:34:49
54+ 2026-08-11 10:34:49 W3SVC1 10.1.48.10 GET /ZenIT/Service/v13/core/Consent land=DE 443 ndl\SVC-Zenit-NDL0167 10.1.48.122 Mozilla/5.0 200 0 0 19571 1051
55+ 2026-08-11 10:34:50 W3SVC1 10.1.48.10 POST /ZenIT/Service/v13/core/Logger - 443 ndl\SVC-Zenit-NDL0167 10.1.48.122 Mozilla/5.0 204 0 0 504 6`
56+
4257// TestLinuxVMFileTailingSuite runs the E2E test suite for the log agent with a Linux VM and fake intake.
4358func TestLinuxVMFileTailingSuite (t * testing.T ) {
4459 options := []e2e.SuiteOption {
@@ -61,12 +76,13 @@ func (s *LinuxFakeintakeSuite) BeforeTest(suiteName, testName string) {
6176
6277 // Ensure no logs are present in fakeintake before testing starts
6378 s .EventuallyWithT (func (c * assert.CollectT ) {
64- logs , err := s .Env ().FakeIntake .Client ().FilterLogs ("hello" )
65- require .NoError (c , err , "Unable to filter logs by the service 'hello'." )
66- // If logs are found, print their content for debugging
67- if ! assert .Empty (c , logs , "Logs were found when none were expected." ) {
68- cat , _ := s .Env ().RemoteHost .Execute (fmt .Sprintf ("cat %s && cat %s/hello-world-2.log" , logFilePath , utils .LinuxLogsFolderPath ))
69- s .T ().Logf ("Logs detected when none were expected: %v" , cat )
79+ for _ , service := range []string {"hello" , iisService } {
80+ logs , err := s .Env ().FakeIntake .Client ().FilterLogs (service )
81+ require .NoError (c , err , "Unable to filter logs by the service '%s'." , service )
82+ if ! assert .Empty (c , logs , "Logs were found for service '%s' when none were expected." , service ) {
83+ cat , _ := s .Env ().RemoteHost .Execute (fmt .Sprintf ("cat %s %s %s/hello-world-2.log 2>/dev/null || true" , logFilePath , iisLogFilePath , utils .LinuxLogsFolderPath ))
84+ s .T ().Logf ("Logs detected when none were expected: %v" , cat )
85+ }
7086 }
7187 }, 2 * time .Minute , 10 * time .Second )
7288
@@ -226,3 +242,42 @@ func (s *LinuxFakeintakeSuite) testLogRecreateRotation() {
226242 // Check intake for new logs
227243 utils .CheckLogsExpected (s .T (), s .Env ().FakeIntake , "hello" , "hello-world-new-content" , []string {})
228244}
245+
246+ // TestIISW3CRecordsStaySeparate writes consecutive IIS W3C records through
247+ // file tailing with auto multiline enabled and asserts fakeintake receives
248+ // them as separate messages. The unit pipeline test constructs the
249+ // preprocessor directly and cannot catch a wiring or config regression.
250+ func (s * LinuxFakeintakeSuite ) TestIISW3CRecordsStaySeparate () {
251+ t := s .T ()
252+
253+ s .Env ().RemoteHost .MustExecute ("sudo touch " + iisLogFilePath )
254+ output , err := s .Env ().RemoteHost .Execute (fmt .Sprintf ("sudo chmod +r %s && echo true" , iisLogFilePath ))
255+ require .NoError (t , err , "Unable to adjust permissions for the log file '%s'." , iisLogFilePath )
256+ require .Equal (t , "true" , strings .TrimSpace (output ), "Unable to adjust permissions for the log file '%s'." , iisLogFilePath )
257+
258+ utils .AssertAgentTailerOK (s , iisLogFileName )
259+ utils .AppendLog (s , iisLogFileName , iisW3CRecords , 1 )
260+
261+ s .EventuallyWithT (func (c * assert.CollectT ) {
262+ logs , err := s .Env ().FakeIntake .Client ().FilterLogs (iisService )
263+ require .NoError (c , err , "Unable to filter logs by the service '%s'." , iisService )
264+
265+ var getOnly , postOnly , combined int
266+ for _ , log := range logs {
267+ hasGet := strings .Contains (log .Message , iisGetToken )
268+ hasPost := strings .Contains (log .Message , iisPostToken )
269+ switch {
270+ case hasGet && hasPost :
271+ combined ++
272+ case hasGet :
273+ getOnly ++
274+ case hasPost :
275+ postOnly ++
276+ }
277+ }
278+
279+ require .GreaterOrEqual (c , getOnly , 1 , "GET IIS record was not received as its own message; got %d logs for service %s" , len (logs ), iisService )
280+ require .GreaterOrEqual (c , postOnly , 1 , "POST IIS record was not received as its own message; got %d logs for service %s" , len (logs ), iisService )
281+ assert .Zero (c , combined , "GET and POST IIS records were concatenated into one intake message" )
282+ }, 2 * time .Minute , 10 * time .Second )
283+ }
0 commit comments