Skip to content

Commit 6adccab

Browse files
Ahmet OeztuerkAhmet Oeztuerk
authored andcommitted
check_logfile: imporve file scanning and keep track of lines matched in each file
apply conditional filters to the lines early on while adding them, this was not the case before. now the count of lines being passing the filter is correct. add function to generate a detail string from files and the count of matched lines in the file. this can be added to the detailed syntax when needed. if there are no lines matched across all files, change the main output line to say "No matching lines found in files"
1 parent 4965342 commit 6adccab

2 files changed

Lines changed: 103 additions & 32 deletions

File tree

pkg/snclient/check_logfile.go

Lines changed: 93 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ func init() {
2222
var numReg = regexp.MustCompile(`\d+`)
2323

2424
type CheckLogFile struct {
25-
snc *Agent
26-
FilePath []string
27-
Paths string
28-
LineDelimiter string
29-
TimestampPattern string
30-
ColumnDelimiter string
31-
LabelPattern []string
32-
Offset string // Changed to string to detect if user provided it
25+
snc *Agent
26+
FilePathPatterns []string
27+
FilePathPatternsCS string
28+
LineDelimiter string
29+
TimestampPattern string
30+
ColumnDelimiter string
31+
LabelPattern []string
32+
Offset string // Changed to string to detect if user provided it
3333
}
3434

3535
type LogLine struct {
@@ -69,8 +69,8 @@ func (c *CheckLogFile) Build() *CheckData {
6969
emptySyntax: "%(status) - No files found",
7070
emptyState: CheckExitUnknown,
7171
args: map[string]CheckArgument{
72-
"file": {value: &c.FilePath, description: "The file that should be checked"},
73-
"files": {value: &c.Paths, description: "Comma separated list of files"},
72+
"file": {value: &c.FilePathPatterns, description: "The file that should be checked"},
73+
"files": {value: &c.FilePathPatternsCS, description: "Comma separated list of files"},
7474
"offset": {value: &c.Offset, description: "Starting position (in bytes) for scanning the file (0 for beginning). This overrides any saved offset"},
7575
"line-split": {value: &c.LineDelimiter, description: "Character string used to split a file into several lines (default \\n)"},
7676
"column-split": {value: &c.ColumnDelimiter, description: "Tab split default: \\t"},
@@ -104,8 +104,8 @@ func (c *CheckLogFile) Check(_ context.Context, snc *Agent, check *CheckData, _
104104
return nil, fmt.Errorf("module CheckLogFile is not enabled in /modules section")
105105
}
106106

107-
c.FilePath = append(c.FilePath, strings.Split(c.Paths, ",")...)
108-
if len(c.FilePath) == 0 {
107+
c.FilePathPatterns = append(c.FilePathPatterns, strings.Split(c.FilePathPatternsCS, ",")...)
108+
if len(c.FilePathPatterns) == 0 {
109109
return nil, fmt.Errorf("no file defined")
110110
}
111111

@@ -122,55 +122,112 @@ func (c *CheckLogFile) Check(_ context.Context, snc *Agent, check *CheckData, _
122122
}
123123
}
124124

125+
// patterns are for the file names/paths, not file contents!
125126
allowedPattern := c.getAllowedPattern()
126127

127-
totalLineCount := 0
128-
for _, fileName := range c.FilePath {
128+
totalLineIndexedCount := 0
129+
checkedFilesWithMatchedEntries := make(map[string]int, 0)
130+
131+
for _, fileName := range c.FilePathPatterns {
129132
if fileName == "" {
130133
continue
131134
}
132-
count := 0
135+
136+
lineIndexedInThisFilePattern := 0
133137
files, err := filepath.Glob(fileName)
134138
if err != nil {
135139
return nil, fmt.Errorf("could not get files for pattern %s, error was: %s", fileName, err.Error())
136140
}
141+
137142
for _, fileName := range files {
138143
if !c.matchPattern(fileName, allowedPattern) {
144+
log.Tracef("check_logfile rejecting file: %s as it does not any match patterns: %v ", fileName, allowedPattern)
145+
139146
return nil, fmt.Errorf("file %s does not match any allowed pattern", fileName)
140147
}
141-
tmpCount, err := c.addFile(fileName, check, patterns)
148+
149+
log.Debugf("check_logfile adding file: %s", fileName)
150+
entries, lineIndex, err := c.addFile(fileName, check, patterns)
142151
if err != nil {
143152
return nil, fmt.Errorf("error for file %s, error was: %s", fileName, err.Error())
144153
}
145-
count += tmpCount
154+
log.Debugf("check_logfile file: %s | returned entries: %v | lines indexed: %d", fileName, entries, lineIndex)
155+
156+
lineIndexedInThisFilePattern += lineIndex
157+
check.listData = append(check.listData, entries...)
158+
checkedFilesWithMatchedEntries[fileName] = len(entries)
146159
}
147-
totalLineCount += count
160+
161+
totalLineIndexedCount += lineIndexedInThisFilePattern
148162
}
163+
149164
check.details = map[string]string{
150-
"total": fmt.Sprintf("%d", totalLineCount),
165+
"total": fmt.Sprintf("%d", totalLineIndexedCount),
166+
"file_counts": c.buildFileCountsDetailString(checkedFilesWithMatchedEntries),
167+
}
168+
169+
if len(check.listData) == 0 {
170+
check.emptySyntax = fmt.Sprintf("%%(status) - No matching lines found in files (%s)", check.details["file_counts"])
151171
}
152172

153173
return check.Finalize()
154174
}
155175

156-
func (c *CheckLogFile) addFile(fileName string, check *CheckData, labels map[string]*regexp.Regexp) (int, error) {
176+
func (c *CheckLogFile) buildFileCountsDetailString(checkedFilesWithMatchedEntries map[string]int) (fileCountDetails string) {
177+
type kv struct {
178+
file string
179+
count int
180+
}
181+
sorted := make([]kv, 0, len(checkedFilesWithMatchedEntries))
182+
for file, count := range checkedFilesWithMatchedEntries {
183+
sorted = append(sorted, kv{file, count})
184+
}
185+
186+
slices.SortFunc(sorted, func(a, b kv) int {
187+
if a.file < b.file {
188+
return -1
189+
}
190+
if a.file > b.file {
191+
return 1
192+
}
193+
if a.count < b.count {
194+
return -1
195+
}
196+
if a.count > b.count {
197+
return 1
198+
}
199+
200+
return 0
201+
})
202+
203+
detailParts := make([]string, 0, len(sorted))
204+
for _, item := range sorted {
205+
detailParts = append(detailParts, fmt.Sprintf("%s: %d", item.file, item.count))
206+
}
207+
208+
fileCountDetails = strings.Join(detailParts, ", ")
209+
210+
return fileCountDetails
211+
}
212+
213+
func (c *CheckLogFile) addFile(fileName string, check *CheckData, labels map[string]*regexp.Regexp) (entries []map[string]string, lineIndex int, err error) {
157214
file, err := os.Open(fileName)
158215
if err != nil {
159-
return 0, fmt.Errorf("could not open file: %s error was: %s", fileName, err.Error())
216+
return entries, 0, fmt.Errorf("could not open file: %s error was: %s", fileName, err.Error())
160217
}
161218
defer file.Close()
162219

163220
info, err := file.Stat()
164221
if err != nil {
165-
return 0, fmt.Errorf("could not stat file %s: %s", fileName, err.Error())
222+
return entries, 0, fmt.Errorf("could not stat file %s: %s", fileName, err.Error())
166223
}
167224

168225
currentInode := getInode(fileName)
169226
currentSize := info.Size()
170227

171228
startOffset, err := c.getStartOffset(fileName, currentSize, currentInode)
172229
if err != nil {
173-
return 0, err
230+
return entries, 0, err
174231
}
175232

176233
saveState := true
@@ -188,25 +245,23 @@ func (c *CheckLogFile) addFile(fileName string, check *CheckData, labels map[str
188245
// seek to start offset
189246
if startOffset > 0 {
190247
if startOffset > currentSize {
191-
return 0, nil
248+
return entries, 0, nil
192249
}
193250
_, err = file.Seek(startOffset, 0)
194251
if err != nil {
195252
saveState = false
196253

197-
return 0, fmt.Errorf("failed to seek to offset %d in %s: %w", startOffset, fileName, err)
254+
return entries, 0, fmt.Errorf("failed to seek to offset %d in %s: %w", startOffset, fileName, err)
198255
}
199256
}
200257

201258
scanner := bufio.NewScanner(file)
202259
scanner.Split(c.getCustomSplitFunction())
203-
okReset := len(check.okThreshold) > 0
260+
okThresholdNotEmpty := len(check.okThreshold) > 0
204261
lineStorage := make([]map[string]string, 0)
205262

206263
columnNumbers := c.getRequiredColumnNumbers(check)
207264

208-
// filter each line
209-
var lineIndex int
210265
for lineIndex = 0; scanner.Scan(); lineIndex++ {
211266
line := scanner.Text()
212267
entry := map[string]string{
@@ -230,9 +285,16 @@ func (c *CheckLogFile) addFile(fileName string, check *CheckData, labels map[str
230285
}
231286
}
232287

288+
if !check.MatchMapCondition(check.filter, entry, false) {
289+
log.Tracef("file: %s , line : %s, did not match the filter set in the check, not ading to check.listData", fileName, line)
290+
291+
continue
292+
}
293+
233294
lineStorage = append(lineStorage, entry)
234-
// Do not check for OK with empty condition list, it would match all
235-
if okReset && check.MatchMapCondition(check.okThreshold, entry, true) {
295+
296+
// Do not check for OK condition if the OK condition list is empty, it would match everything
297+
if okThresholdNotEmpty && check.MatchMapCondition(check.okThreshold, entry, true) {
236298
// add and empty entry with the current line count to the list data to keep track of line count
237299
entry := map[string]string{
238300
"_count": fmt.Sprintf("%d", len(lineStorage)),
@@ -241,9 +303,8 @@ func (c *CheckLogFile) addFile(fileName string, check *CheckData, labels map[str
241303
lineStorage = make([]map[string]string, 0)
242304
}
243305
}
244-
check.listData = append(check.listData, lineStorage...)
245306

246-
return lineIndex, nil
307+
return lineStorage, lineIndex, nil
247308
}
248309

249310
func (c *CheckLogFile) getStartOffset(fileName string, currentSize int64, currentInode uint64) (int64, error) {

pkg/snclient/check_logfile_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,13 @@ func TestCheckLogFileColumnN(t *testing.T) {
116116

117117
StopTestAgent(t, snc)
118118
}
119+
120+
func TestCheckLogFileFileExistsButHasNoLines(t *testing.T) {
121+
snc := StartTestAgent(t, testLogfileConfig)
122+
123+
res := snc.RunCheck("check_logfile", []string{"files=./t/test*", "filter=line LIKE this-pattern-does-not-exist-in-the-test-files", "show-all"})
124+
assert.Equalf(t, CheckExitUnknown, res.State, "state UNKNOWN")
125+
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - No matching lines found in files ")
126+
127+
StopTestAgent(t, snc)
128+
}

0 commit comments

Comments
 (0)