Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Flags:
-v, --verbose Verbose output, useful for testing.
--output-matching-string Include detailed information about each matching line in output
--force-read-from-start Ignore cached file offset in state directory and read file(s) from beginning.
-M, --mtime When multiple files match the log file expression, only monitor the file with the most recent modification time
-h, --help help for sensu-check-log
```

Expand All @@ -91,6 +92,7 @@ Flags:
|--missing-ok |CHECK_LOG_MISSING_OK |
|--invert-thresholds |CHECK_LOG_INVERT_THRESHOLDS |
|--reset-state |CHECK_LOG_RESET_STATE |
|--mtime |CHECK_LOG_MTIME |

### Event generation

Expand Down Expand Up @@ -201,8 +203,6 @@ spec:

```



## Installation from source

The preferred way of installing and deploying this plugin is to use it as an Asset. If you would
Expand All @@ -217,6 +217,16 @@ go build

## Additional notes

### File Selection with Modification Time

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 `--mtime` flag to monitor only the file with the most recent modification time.

This feature is particularly useful in log rotation scenarios where:
- Log files are rotated with timestamps (e.g., `app.log`, `app.log.1`, `app.log.2`)
- You want to monitor only the actively written log file
- Multiple log files exist but only the newest is relevant


## Contributing

For more information about contributing to this plugin, see [Contributing][1].
Expand Down
43 changes: 41 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"time"

corev2 "github.com/sensu/core/v2"
"github.com/sensu/sensu-plugin-sdk/sensu"
Expand Down Expand Up @@ -43,6 +45,7 @@ type Config struct {
CriticalOnly bool
CheckNameTemplate string
VerboseResults bool
UseLatestMtime bool
}

var (
Expand Down Expand Up @@ -232,6 +235,14 @@ var (
Usage: "Ignore cached file offset in state directory and read file(s) from beginning.",
Value: &plugin.ForceReadFromStart,
},
&sensu.PluginConfigOption[bool]{
Path: "mtime",
Env: "CHECK_LOG_MTIME",
Argument: "mtime",
Shorthand: "M",
Usage: "When multiple files match the log file expression, only monitor the file with the most recent modification time",
Value: &plugin.UseLatestMtime,
},
}
)

Expand Down Expand Up @@ -321,7 +332,7 @@ func checkArgs(event *corev2.Event) (int, error) {
}
if plugin.DryRun {
plugin.Verbose = true
fmt.Printf("LogFileExpr: %s StateDir: %s\n", plugin.LogFileExpr, plugin.StateDir)
fmt.Printf("LogFileExpr: %s StateDir: %s UseLatestMtime: %t\n", plugin.LogFileExpr, plugin.StateDir, plugin.UseLatestMtime)
}
return sensu.CheckStateOK, nil
}
Expand Down Expand Up @@ -393,11 +404,21 @@ func buildLogArray() ([]string, error) {
}

if filepath.IsAbs(absLogPath) {
// Collect all matching files with their modification times
type fileWithMtime struct {
path string
mtime time.Time
}
var matchingFiles []fileWithMtime

e = filepath.Walk(absLogPath, func(path string, info os.FileInfo, err error) error {
if err == nil && logRegExp.MatchString(path) {
if filepath.IsAbs(path) {
if !info.IsDir() {
logs = append(logs, path)
matchingFiles = append(matchingFiles, fileWithMtime{
path: path,
mtime: info.ModTime(),
})
}
} else {
return fmt.Errorf("path %s not absolute", path)
Expand All @@ -408,6 +429,24 @@ func buildLogArray() ([]string, error) {
if e != nil {
return nil, e
}

// If mtime flag is set, only keep the file with the most recent modification time
if plugin.UseLatestMtime && len(matchingFiles) > 1 {
// Sort by modification time, most recent first
sort.Slice(matchingFiles, func(i, j int) bool {
return matchingFiles[i].mtime.After(matchingFiles[j].mtime)
})
// Keep only the most recently modified file
logs = append(logs, matchingFiles[0].path)
if plugin.Verbose {
fmt.Printf("Using latest modified file: %s (mtime: %v)\n", matchingFiles[0].path, matchingFiles[0].mtime)
}
} else {
// Add all matching files
for _, file := range matchingFiles {
logs = append(logs, file.path)
}
}
}
}
logs = removeDuplicates(logs)
Expand Down
42 changes: 42 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"runtime"
"strings"
"testing"
"time"

corev2 "github.com/sensu/core/v2"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -745,3 +746,44 @@ func TestProcessLogFileWithNegativeCachedOffset(t *testing.T) {
assert.Equal(t, 0, matches)
}
}

func TestBuildLogArrayWithLatestMtime(t *testing.T) {
// Setup test files with different mtimes
err := os.MkdirAll("./testingdata/mtime-test", 0755)
assert.NoError(t, err)
defer os.RemoveAll("./testingdata/mtime-test")

// Create first file (older)
file1 := "./testingdata/mtime-test/log1.log"
err = os.WriteFile(file1, []byte("test log 1"), 0644)
assert.NoError(t, err)

// Wait a bit to ensure different mtime
time.Sleep(10 * time.Millisecond)

// Create second file (newer)
file2 := "./testingdata/mtime-test/log2.log"
err = os.WriteFile(file2, []byte("test log 2"), 0644)
assert.NoError(t, err)

// Test without use-latest-mtime flag (should return both files)
plugin.LogFile = ""
plugin.LogPath = "./testingdata/mtime-test/"
plugin.LogFileExpr = "log.*\\.log$"
plugin.UseLatestMtime = false
plugin.Verbose = false

logs, err := buildLogArray()
assert.NoError(t, err)
assert.Equal(t, 2, len(logs))

// Test with use-latest-mtime flag (should return only the newest file)
plugin.UseLatestMtime = true

logs, err = buildLogArray()
assert.NoError(t, err)
assert.Equal(t, 1, len(logs))

// The returned file should be the newer one (log2.log)
assert.Contains(t, logs[0], "log2.log")
}
Loading