Skip to content

Commit 856fd7f

Browse files
Adding a mtime style flag to filter out log files based on modification time
Signed-off-by: rakibhossainctr <rakib.hossain.ctr@sumologic.com>
1 parent 9b301c4 commit 856fd7f

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Flags:
6666
-v, --verbose Verbose output, useful for testing.
6767
--output-matching-string Include detailed information about each matching line in output
6868
--force-read-from-start Ignore cached file offset in state directory and read file(s) from beginning.
69+
-M, --mtime When multiple files match the log file expression, only monitor the file with the most recent modification time
6970
-h, --help help for sensu-check-log
7071
```
7172

@@ -91,6 +92,7 @@ Flags:
9192
|--missing-ok |CHECK_LOG_MISSING_OK |
9293
|--invert-thresholds |CHECK_LOG_INVERT_THRESHOLDS |
9394
|--reset-state |CHECK_LOG_RESET_STATE |
95+
|--use-latest-mtime |CHECK_LOG_USE_LATEST_MTIME |
9496

9597
### Event generation
9698

@@ -201,6 +203,22 @@ spec:
201203

202204
```
203205

206+
Example of configuring a check to monitor only the most recently modified log file when multiple files match the pattern (useful for log rotation scenarios):
207+
208+
```yml
209+
---
210+
type: CheckConfig
211+
api_version: core/v2
212+
metadata:
213+
name: sensu-check-log-latest
214+
spec:
215+
command: sensu-check-log -p /var/log/ -e "application.*\.log$" -m "(?i)error" -d /tmp/sensu-check-latest-log/ --use-latest-mtime
216+
stdin: true
217+
runtime_assets:
218+
- sensu/sensu-check-log
219+
220+
```
221+
204222

205223

206224
## Installation from source
@@ -217,6 +235,17 @@ go build
217235

218236
## Additional notes
219237

238+
### File Selection with Modification Time
239+
240+
When using the `--log-file-expr` option with wildcard patterns, multiple log files may match the expression. By default, all matching files are monitored. However, you can use the `--use-latest-mtime` flag to monitor only the file with the most recent modification time.
241+
242+
This feature is particularly useful in log rotation scenarios where:
243+
- Log files are rotated with timestamps (e.g., `app.log`, `app.log.1`, `app.log.2`)
244+
- You want to monitor only the actively written log file
245+
- Multiple log files exist but only the newest is relevant
246+
247+
The modification time comparison helps ensure that you're always monitoring the most current log file, similar to the `CompareByLastUpdate` feature found in other log monitoring tools.
248+
220249
## Contributing
221250

222251
For more information about contributing to this plugin, see [Contributing][1].

main.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import (
1212
"path/filepath"
1313
"regexp"
1414
"runtime"
15+
"sort"
1516
"strings"
17+
"time"
1618

1719
corev2 "github.com/sensu/core/v2"
1820
"github.com/sensu/sensu-plugin-sdk/sensu"
@@ -43,6 +45,7 @@ type Config struct {
4345
CriticalOnly bool
4446
CheckNameTemplate string
4547
VerboseResults bool
48+
UseLatestMtime bool
4649
}
4750

4851
var (
@@ -232,6 +235,14 @@ var (
232235
Usage: "Ignore cached file offset in state directory and read file(s) from beginning.",
233236
Value: &plugin.ForceReadFromStart,
234237
},
238+
&sensu.PluginConfigOption[bool]{
239+
Path: "use-latest-mtime",
240+
Env: "CHECK_LOG_MTIME",
241+
Argument: "mtime",
242+
Shorthand: "M",
243+
Usage: "When multiple files match the log file expression, only monitor the file with the most recent modification time",
244+
Value: &plugin.UseLatestMtime,
245+
},
235246
}
236247
)
237248

@@ -321,7 +332,7 @@ func checkArgs(event *corev2.Event) (int, error) {
321332
}
322333
if plugin.DryRun {
323334
plugin.Verbose = true
324-
fmt.Printf("LogFileExpr: %s StateDir: %s\n", plugin.LogFileExpr, plugin.StateDir)
335+
fmt.Printf("LogFileExpr: %s StateDir: %s UseLatestMtime: %t\n", plugin.LogFileExpr, plugin.StateDir, plugin.UseLatestMtime)
325336
}
326337
return sensu.CheckStateOK, nil
327338
}
@@ -393,11 +404,21 @@ func buildLogArray() ([]string, error) {
393404
}
394405

395406
if filepath.IsAbs(absLogPath) {
407+
// Collect all matching files with their modification times
408+
type fileWithMtime struct {
409+
path string
410+
mtime time.Time
411+
}
412+
var matchingFiles []fileWithMtime
413+
396414
e = filepath.Walk(absLogPath, func(path string, info os.FileInfo, err error) error {
397415
if err == nil && logRegExp.MatchString(path) {
398416
if filepath.IsAbs(path) {
399417
if !info.IsDir() {
400-
logs = append(logs, path)
418+
matchingFiles = append(matchingFiles, fileWithMtime{
419+
path: path,
420+
mtime: info.ModTime(),
421+
})
401422
}
402423
} else {
403424
return fmt.Errorf("path %s not absolute", path)
@@ -408,6 +429,24 @@ func buildLogArray() ([]string, error) {
408429
if e != nil {
409430
return nil, e
410431
}
432+
433+
// If use-latest-mtime flag is set, only keep the file with the most recent modification time
434+
if plugin.UseLatestMtime && len(matchingFiles) > 1 {
435+
// Sort by modification time, most recent first
436+
sort.Slice(matchingFiles, func(i, j int) bool {
437+
return matchingFiles[i].mtime.After(matchingFiles[j].mtime)
438+
})
439+
// Keep only the most recently modified file
440+
logs = append(logs, matchingFiles[0].path)
441+
if plugin.Verbose {
442+
fmt.Printf("Using latest modified file: %s (mtime: %v)\n", matchingFiles[0].path, matchingFiles[0].mtime)
443+
}
444+
} else {
445+
// Add all matching files
446+
for _, file := range matchingFiles {
447+
logs = append(logs, file.path)
448+
}
449+
}
411450
}
412451
}
413452
logs = removeDuplicates(logs)

main_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"runtime"
1212
"strings"
1313
"testing"
14+
"time"
1415

1516
corev2 "github.com/sensu/core/v2"
1617
"github.com/stretchr/testify/assert"
@@ -745,3 +746,44 @@ func TestProcessLogFileWithNegativeCachedOffset(t *testing.T) {
745746
assert.Equal(t, 0, matches)
746747
}
747748
}
749+
750+
func TestBuildLogArrayWithLatestMtime(t *testing.T) {
751+
// Setup test files with different mtimes
752+
err := os.MkdirAll("./testingdata/mtime-test", 0755)
753+
assert.NoError(t, err)
754+
defer os.RemoveAll("./testingdata/mtime-test")
755+
756+
// Create first file (older)
757+
file1 := "./testingdata/mtime-test/log1.log"
758+
err = os.WriteFile(file1, []byte("test log 1"), 0644)
759+
assert.NoError(t, err)
760+
761+
// Wait a bit to ensure different mtime
762+
time.Sleep(10 * time.Millisecond)
763+
764+
// Create second file (newer)
765+
file2 := "./testingdata/mtime-test/log2.log"
766+
err = os.WriteFile(file2, []byte("test log 2"), 0644)
767+
assert.NoError(t, err)
768+
769+
// Test without use-latest-mtime flag (should return both files)
770+
plugin.LogFile = ""
771+
plugin.LogPath = "./testingdata/mtime-test/"
772+
plugin.LogFileExpr = "log.*\\.log$"
773+
plugin.UseLatestMtime = false
774+
plugin.Verbose = false
775+
776+
logs, err := buildLogArray()
777+
assert.NoError(t, err)
778+
assert.Equal(t, 2, len(logs))
779+
780+
// Test with use-latest-mtime flag (should return only the newest file)
781+
plugin.UseLatestMtime = true
782+
783+
logs, err = buildLogArray()
784+
assert.NoError(t, err)
785+
assert.Equal(t, 1, len(logs))
786+
787+
// The returned file should be the newer one (log2.log)
788+
assert.Contains(t, logs[0], "log2.log")
789+
}

0 commit comments

Comments
 (0)