Skip to content

Commit 3d175ee

Browse files
authored
[AGNTLOG-74] Fix Logs File Launcher Blocking AD (#36803)
<!-- * Contributors are encouraged to read our [CONTRIBUTING](/CONTRIBUTING.md) documentation. * Both Contributor and Reviewer Checklists are available at https://datadoghq.dev/datadog-agent/guidelines/contributing/#pull-requests. * The pull request: * Should only fix one issue or add one feature at a time. * Must update the test suite for the relevant functionality. * Should pass all status checks before being reviewed or merged. * Commit titles should be prefixed with general area of pull request's change. * Please fill the below sections if possible with relevant information or links. --> ### What does this PR do? Moves `FilesToTail()` to a go function to run concurrently alongside the rest of the launcher. This allows the launcher to run without being blocked when gathering file tailers for sources that can take a while to fetch (such as those on remote file shares). ### Motivation In the previous implementation, the `scan` function was called synchronously by the file launcher. If the scan operation took a long time, this synchronous call would block the rest of the launcher leading to missed sourcs.. Now, the launcher will proceed while `FilesToTail()` runs in another thread so it won't miss sources anymore. ### Describe how you validated your changes <!-- Validate your changes before merge, ensuring that: * Your PR is tested by static / unit / integrations / e2e tests * Your PR description details which e2e tests cover your changes, if any * The PR description contains details of how you validated your changes. If you validated changes manually and not through automated tests, add context on why automated tests did not fit your changes validation. If you want additional validation by a second person, you can ask reviewers to do it. Describe how to set up an environment for manual tests in the PR description. Manual validation is expected to happen on every commit before merge. Any manual validation step should then map to an automated test. Manual validation should not substitute automation, minus exceptions not supported by test tooling yet. --> 1. Benchmarked scan to confirm that increasing numbers of files were the culprit 2. Built and ran the agent ### Possible Drawbacks / Trade-offs None that I can think of. ### Additional Notes <!-- * Anything else we should know when reviewing? * Include benchmarking information here whenever possible. * Include info about alternatives that were considered and why the proposed version was chosen. --> Would like more assistance QA'ing as I'm not very familiar with concurrency.
1 parent 73e7543 commit 3d175ee

6 files changed

Lines changed: 163 additions & 90 deletions

File tree

comp/logs/agent/agentimpl/agent_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ func (suite *AgentTestSuite) SetupTest() {
104104
suite.configOverrides["logs_config.run_path"] = suite.testDir
105105
// Shorter grace period for tests.
106106
suite.configOverrides["logs_config.stop_grace_period"] = 1
107+
// Set a short scan period to allow it to run in the time period of the tcp and http tests
108+
suite.configOverrides["logs_config.file_scan_period"] = 1
107109

108110
fakeTagger := taggerfxmock.SetupFakeTagger(suite.T())
109111
suite.tagger = fakeTagger

pkg/logs/launchers/file/launcher.go

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package file
88

99
import (
10+
"context"
1011
"regexp"
1112
"slices"
1213
"time"
@@ -52,10 +53,12 @@ type Launcher struct {
5253
// set to true if we want to use `ContainersLogsDir` to validate that a new
5354
// pod log file is being attached to the correct containerID.
5455
// Feature flag defaulting to false, use `logs_config.validate_pod_container_id`.
55-
validatePodContainerID bool
56-
scanPeriod time.Duration
57-
flarecontroller *flareController.FlareController
58-
tagger tagger.Component
56+
validatePodContainerID bool
57+
scanPeriod time.Duration
58+
flarecontroller *flareController.FlareController
59+
tagger tagger.Component
60+
filesChan chan []*tailer.File
61+
filesTailedBetweenScans []*tailer.File
5962
// Stores pertinent information about old tailer when rotation occurs and fingerprinting isn't possible
6063
oldInfoMap map[string]*oldTailerInfo
6164
fingerprinter *tailer.Fingerprinter
@@ -92,6 +95,7 @@ func NewLauncher(tailingLimit int, tailerSleepDuration time.Duration, validatePo
9295
scanPeriod: scanPeriod,
9396
flarecontroller: flarecontroller,
9497
tagger: tagger,
98+
filesChan: make(chan []*tailer.File, 1),
9599
oldInfoMap: make(map[string]*oldTailerInfo),
96100
fingerprinter: tailer.NewFingerprinter(fingerprintConfig),
97101
}
@@ -121,17 +125,33 @@ func (s *Launcher) run() {
121125
close(s.done)
122126
}()
123127

128+
ctx, cancel := context.WithCancel(context.Background())
124129
for {
125130
select {
126131
case source := <-s.addedSources:
127132
s.addSource(source)
128133
case source := <-s.removedSources:
129134
s.removeSource(source)
130135
case <-scanTicker.C:
136+
137+
activeSourcesCopy := make([]*sources.LogSource, len(s.activeSources))
138+
copy(activeSourcesCopy, s.activeSources)
139+
140+
// Clear files tailed between scans before starting new FilesToTail
141+
s.filesTailedBetweenScans = s.filesTailedBetweenScans[:0]
142+
143+
scanTicker.Stop()
144+
go func() {
145+
s.filesChan <- s.fileProvider.FilesToTail(ctx, s.validatePodContainerID, activeSourcesCopy, s.registry)
146+
}()
147+
case files := <-s.filesChan:
131148
s.cleanUpRotatedTailers()
132-
// check if there are new files to tail, tailers to stop and tailer to restart because of file rotation
133-
s.scan()
149+
150+
s.resolveActiveTailers(files)
151+
scanTicker.Reset(s.scanPeriod)
134152
case <-s.stop:
153+
// Cancel the context passed to fileProvider.FilesToTail
154+
cancel()
135155
// no more file should be tailed
136156
s.cleanup()
137157
return
@@ -158,12 +178,23 @@ func (s *Launcher) cleanup() {
158178
s.oldInfoMap = make(map[string]*oldTailerInfo)
159179
}
160180

161-
// scan checks all the files we're expected to tail, compares them to the currently tailed files,
162-
// and triggers the required updates.
163-
// For instance, when a file is logrotated, its tailer will keep tailing the rotated file.
164-
// The Scanner needs to stop that previous tailer, and start a new one for the new file.
165-
func (s *Launcher) scan() {
166-
files := s.fileProvider.FilesToTail(s.validatePodContainerID, s.activeSources, s.registry)
181+
// resolveActiveTailers checks all the files we're expected to tail, compares them to the
182+
// currently tailed files, and triggers the required updates. For instance,
183+
// when a file is logrotated, its tailer will keep tailing the rotated file.
184+
// The Scanner needs to stop that previous tailer, and start a new one for the
185+
// new file.
186+
func (s *Launcher) resolveActiveTailers(files []*tailer.File) {
187+
// resolveActiveTailers() receives the files parameter from FilesToTail(),
188+
// which is called in the main run loop of launcher. FilesToTail() is always
189+
// executed concurrently. It is therefore possible that addSource() can be
190+
// called while FilesToTail() is still running. Since FilesToTail() is only
191+
// passed a copy of activeSources it is possible that it would miss new
192+
// sources added by addsource() therefore scan() would unschedule a tailer
193+
// added during a concurrent scan. In order to mitigate that possibility, any
194+
// tailers started while FilesToTail() is running need to be merged with the
195+
// result of FilesToTail() to prevent scan() from unscheudling them.
196+
files = append(files, s.filesTailedBetweenScans...)
197+
s.filesTailedBetweenScans = s.filesTailedBetweenScans[:0]
167198
filesTailed := make(map[string]bool)
168199
var allFiles []string
169200

@@ -329,6 +360,7 @@ func (s *Launcher) launchTailers(source *sources.LogSource) {
329360
log.Warnf("Could not collect files: %v", err)
330361
return
331362
}
363+
332364
for _, file := range files {
333365
if s.tailers.Count() >= s.tailingLimit {
334366
return
@@ -361,7 +393,10 @@ func (s *Launcher) launchTailers(source *sources.LogSource) {
361393
source.Config.TailingMode = mode.String()
362394
}
363395

364-
s.startNewTailer(file, mode, fingerprint)
396+
newTailerStarted := s.startNewTailer(file, mode, fingerprint)
397+
if newTailerStarted {
398+
s.filesTailedBetweenScans = append(s.filesTailedBetweenScans, file)
399+
}
365400
}
366401
}
367402

pkg/logs/launchers/file/launcher_test.go

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package file
99

1010
import (
11+
"context"
1112
"fmt"
1213
"os"
1314
"strings"
@@ -173,7 +174,7 @@ func (suite *BaseLauncherTestSuite) SetupTest() {
173174
suite.s.registry = auditorMock.NewMockRegistry()
174175
suite.s.activeSources = append(suite.s.activeSources, suite.source)
175176
status.InitStatus(cfg, util.CreateSources([]*sources.LogSource{suite.source}))
176-
suite.s.scan()
177+
suite.s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
177178
}
178179

179180
func (suite *BaseLauncherTestSuite) TearDownTest() {
@@ -204,7 +205,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithoutLogRotation() {
204205
msg = <-suite.outputChan
205206
suite.Equal("hello world", string(msg.GetContent()))
206207

207-
s.scan()
208+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
208209
newTailer, _ = s.tailers.Get(getScanKey(suite.testPath, suite.source))
209210
// testing that launcher did not have to create a new tailer
210211
suite.True(tailer == newTailer)
@@ -232,7 +233,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithLogRotation() {
232233
os.Rename(suite.testPath, suite.testRotatedPath)
233234
f, err := os.Create(suite.testPath)
234235
suite.Nil(err)
235-
s.scan()
236+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
236237
newTailer, _ = s.tailers.Get(getScanKey(suite.testPath, suite.source))
237238
suite.True(tailer != newTailer)
238239

@@ -273,7 +274,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithLogRotationAndChecksum_R
273274
suite.Nil(err)
274275
suite.Nil(suite.testFile.Sync())
275276

276-
s.scan()
277+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
277278

278279
// Read message to confirm tailer is working
279280
msg := <-suite.outputChan
@@ -310,7 +311,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithLogRotationAndChecksum_R
310311
suite.Nil(f.Sync())
311312
defer f.Close()
312313

313-
s.scan()
314+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
314315

315316
newTailer, _ := s.tailers.Get(getScanKey(suite.testPath, suite.source))
316317
suite.True(tailer != newTailer, "A new tailer should have been created due to content change")
@@ -354,7 +355,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithLogRotationAndChecksum_N
354355
suite.Nil(err)
355356
suite.Nil(suite.testFile.Sync())
356357

357-
s.scan()
358+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
358359

359360
// Read message
360361
msg := <-suite.outputChan
@@ -376,7 +377,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithLogRotationAndChecksum_N
376377
suite.False(didRotate, "Should not detect rotation when writing to the same file")
377378

378379
// Scan again - should not trigger any rotation logic
379-
s.scan()
380+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
380381

381382
// Verify the same tailer is still being used
382383
newTailer, _ := s.tailers.Get(getScanKey(suite.testPath, suite.source))
@@ -409,7 +410,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithLogRotationCopyTruncate(
409410
suite.Nil(err)
410411

411412
suite.Nil(suite.testFile.Sync())
412-
s.scan()
413+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
413414

414415
newTailer, _ = s.tailers.Get(getScanKey(suite.testPath, suite.source))
415416
suite.True(tailer != newTailer)
@@ -427,13 +428,13 @@ func (suite *BaseLauncherTestSuite) TestLauncherScanWithFileRemovedAndCreated()
427428
// remove file
428429
err = os.Remove(suite.testPath)
429430
suite.Nil(err)
430-
s.scan()
431+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
431432
suite.Equal(tailerLen-1, s.tailers.Count())
432433

433434
// create file
434435
_, err = os.Create(suite.testPath)
435436
suite.Nil(err)
436-
s.scan()
437+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
437438
suite.Equal(tailerLen, s.tailers.Count())
438439
}
439440

@@ -495,7 +496,8 @@ func runLauncherScanStartNewTailerTest(t *testing.T, testDirs []string) {
495496
file.Close()
496497

497498
// test scan from beginning
498-
launcher.scan()
499+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
500+
499501
assert.Equal(t, 1, launcher.tailers.Count())
500502
msg = <-outputChan
501503
assert.Equal(t, "hello", string(msg.GetContent()))
@@ -538,7 +540,8 @@ func runLauncherScanStartNewTailerForEmptyFileTest(t *testing.T, testDirs []stri
538540
_, err := os.Create(fmt.Sprintf("%s/test.log", testDir))
539541
assert.Nil(t, err)
540542

541-
launcher.scan()
543+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
544+
542545
assert.Equal(t, 0, launcher.tailers.Count())
543546
}
544547

@@ -586,7 +589,8 @@ func runLauncherScanStartNewTailerWithOneLineTest(t *testing.T, testDirs []strin
586589
file.Close()
587590

588591
// test scan from beginning
589-
launcher.scan()
592+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
593+
590594
assert.Equal(t, 1, launcher.tailers.Count())
591595
}
592596

@@ -637,7 +641,8 @@ func runLauncherScanStartNewTailerWithLongLineTest(t *testing.T, testDirs []stri
637641
file.Close()
638642

639643
// test scan from beginning
640-
launcher.scan()
644+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
645+
641646
assert.Equal(t, 1, launcher.tailers.Count())
642647
}
643648

@@ -896,7 +901,7 @@ func runLauncherScanWithTooManyFilesTest(t *testing.T, testDirs []string) {
896901
defer status.Clear()
897902

898903
// test at scan
899-
launcher.scan()
904+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
900905
assert.Equal(t, 2, launcher.tailers.Count())
901906
// Confirm that all of the files have been keepalive'd even if they are not tailed
902907
assert.Equal(t, 3, len(launcher.registry.(*auditorMock.Registry).KeepAlives))
@@ -905,7 +910,7 @@ func runLauncherScanWithTooManyFilesTest(t *testing.T, testDirs []string) {
905910
err = os.Remove(path)
906911
assert.Nil(t, err)
907912

908-
launcher.scan()
913+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
909914
assert.Equal(t, 2, launcher.tailers.Count())
910915
}
911916

@@ -1014,14 +1019,14 @@ func runLauncherScanRecentFilesWithRemovalTest(t *testing.T, testDirs []string)
10141019
launcher := createLauncher()
10151020
defer status.Clear()
10161021

1017-
launcher.scan()
1022+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
10181023
assert.Equal(t, 2, launcher.tailers.Count())
10191024
assert.True(t, launcher.tailers.Contains(path("1.log")))
10201025
assert.True(t, launcher.tailers.Contains(path("2.log")))
10211026

10221027
// When ... the newest file gets rm'd
10231028
rmFile("2.log")
1024-
launcher.scan()
1029+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
10251030

10261031
// Then the next 2 most recently modified should be tailed
10271032
assert.Equal(t, 2, launcher.tailers.Count())
@@ -1080,14 +1085,14 @@ func runLauncherScanRecentFilesWithNewFilesTest(t *testing.T, testDirs []string)
10801085
launcher := createLauncher()
10811086
defer status.Clear()
10821087

1083-
launcher.scan()
1088+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
10841089
assert.Equal(t, 2, launcher.tailers.Count())
10851090
assert.True(t, launcher.tailers.Contains(path("1.log")))
10861091
assert.True(t, launcher.tailers.Contains(path("2.log")))
10871092

10881093
// When ... a newer file appears
10891094
createFile("7.log", baseTime.Add(time.Second*8))
1090-
launcher.scan()
1095+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
10911096

10921097
// Then it should be tailed
10931098
assert.Equal(t, 2, launcher.tailers.Count())
@@ -1096,7 +1101,7 @@ func runLauncherScanRecentFilesWithNewFilesTest(t *testing.T, testDirs []string)
10961101

10971102
// When ... an even newer file appears
10981103
createFile("a.log", baseTime.Add(time.Second*10))
1099-
launcher.scan()
1104+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
11001105

11011106
// Then it should be tailed
11021107
assert.Equal(t, 2, launcher.tailers.Count())
@@ -1150,7 +1155,7 @@ func runLauncherFileRotationTest(t *testing.T, testDirs []string) {
11501155
launcher := createLauncher()
11511156
defer status.Clear()
11521157

1153-
launcher.scan()
1158+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
11541159
assert.Equal(t, 2, launcher.tailers.Count())
11551160
assert.Equal(t, 0, len(launcher.rotatedTailers))
11561161
assert.True(t, launcher.tailers.Contains(path("c.log")))
@@ -1168,7 +1173,7 @@ func runLauncherFileRotationTest(t *testing.T, testDirs []string) {
11681173
assert.Nil(t, err)
11691174
assert.True(t, didRotate)
11701175

1171-
launcher.scan()
1176+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
11721177
assert.Equal(t, launcher.tailers.Count(), 2)
11731178
assert.Equal(t, 1, len(launcher.rotatedTailers))
11741179
assert.True(t, launcher.tailers.Contains(path("c.log")))
@@ -1223,14 +1228,14 @@ func runLauncherFileDetectionSingleScanTest(t *testing.T, testDirs []string) {
12231228
launcher := createLauncher()
12241229
defer status.Clear()
12251230

1226-
launcher.scan()
1231+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
12271232
assert.Equal(t, 2, launcher.tailers.Count())
12281233
assert.True(t, launcher.tailers.Contains(path("a.log")))
12291234
assert.True(t, launcher.tailers.Contains(path("b.log")))
12301235

12311236
createFile("z.log")
12321237

1233-
launcher.scan()
1238+
launcher.resolveActiveTailers(launcher.fileProvider.FilesToTail(context.Background(), launcher.validatePodContainerID, launcher.activeSources, launcher.registry))
12341239
assert.Equal(t, launcher.tailers.Count(), 2)
12351240
assert.True(t, launcher.tailers.Contains(path("z.log")))
12361241
assert.True(t, launcher.tailers.Contains(path("b.log")))
@@ -1264,7 +1269,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherDoesNotCreateTailerForTruncatedU
12641269
suite.Nil(err)
12651270
suite.Nil(suite.testFile.Sync())
12661271

1267-
s.scan()
1272+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
12681273

12691274
// Read message to confirm tailer is working
12701275
msg := <-suite.outputChan
@@ -1287,7 +1292,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherDoesNotCreateTailerForTruncatedU
12871292
suite.True(didRotate, "Should detect rotation when file becomes empty (fingerprint = 0)")
12881293

12891294
// Now test the launcher's behavior: it should NOT create a new tailer for the undersized file
1290-
s.scan()
1295+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
12911296

12921297
// Verify no new tailer was created for the undersized file
12931298
// The old tailer should be removed but no new one should be created
@@ -1323,7 +1328,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherDoesNotCreateTailerForRotatedUnd
13231328
suite.Nil(err)
13241329
suite.Nil(suite.testFile.Sync())
13251330

1326-
s.scan()
1331+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
13271332

13281333
// Read message to confirm tailer is working
13291334
msg := <-suite.outputChan
@@ -1350,7 +1355,7 @@ func (suite *BaseLauncherTestSuite) TestLauncherDoesNotCreateTailerForRotatedUnd
13501355
suite.True(didRotate, "Should detect rotation when original file is moved and new file is created")
13511356

13521357
// Now test the launcher's behavior: it should NOT create a new tailer for the undersized file
1353-
s.scan()
1358+
s.resolveActiveTailers(suite.s.fileProvider.FilesToTail(context.Background(), suite.s.validatePodContainerID, suite.s.activeSources, suite.s.registry))
13541359

13551360
// Verify no new tailer was created for the undersized file
13561361
// The old tailer should be removed but no new one should be created

0 commit comments

Comments
 (0)