Repository: abahmed/kwatch
Pull request #317 (2024-06-17 21:19:41)

Ignore log patterns
This builds on the work done in #270. It allows alerts to be ignored if the pod logs contain certain patterns. In my case, I was regularly receiving alerts about pods (for 3rd party applications) that terminated themselves when they lost a leader election. Since this is a normal part of their operation, I don't need to be alerted about this event every time it happens.

Changes proposed in this pull request:
- add a new `ignoreLogPatterns` config option
- compile those patterns into regular expressions
- check to see if pod logs match any of the specified patterns



Patch:
diff --git a/config/config.go b/config/config.go
index 8f81fbd..f3aaa2e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -41,6 +41,9 @@ type Config struct {
 	// IgnorePodNames optional list of pod name regexp patterns to ignore
 	IgnorePodNames []string `yaml:"ignorePodNames"`
 
+	// IgnoreLogPatterns optional list of regexp patterns to ignore
+	IgnoreLogPatterns []string `yaml:"ignoreLogPatterns"`
+
 	// Alert is a map contains a map of each provider configuration
 	// e.g. {"slack": {"webhook": "URL"}}
 	Alert map[string]map[string]interface{} `yaml:"alert"`
@@ -58,6 +61,10 @@ type Config struct {
 	// Patterns are compiled from IgnorePodNames after populating
 	// IgnorePodNames configuration
 	IgnorePodNamePatterns []*regexp.Regexp
+
+	// Patterns are compiled from IgnoreLogPatterns after populating
+	// IgnoreLogPatterns configuration
+	IgnoreLogPatternsCompiled []*regexp.Regexp
 }
 
 // App confing struct
diff --git a/config/loadConfig.go b/config/loadConfig.go
index 94bfc8d..053fc8d 100644
--- a/config/loadConfig.go
+++ b/config/loadConfig.go
@@ -50,11 +50,18 @@ func LoadConfig() (*Config, error) {
 
 	// Prepare ignored pod name patters
 	config.IgnorePodNamePatterns, err =
-		getCompiledIgnorePodNamePatterns(config.IgnorePodNames)
+		getCompiledIgnorePatterns(config.IgnorePodNames)
 	if err != nil {
 		logrus.Errorf("Failed to compile pod name pattern: %s", err.Error())
 	}
 
+	// Prepare ignored log patterns
+	config.IgnoreLogPatternsCompiled, err =
+		getCompiledIgnorePatterns(config.IgnoreLogPatterns)
+	if err != nil {
+		logrus.Errorf("Failed to compile log pattern: %s", err.Error())
+	}
+
 	// Parse proxy config
 	if len(config.App.ProxyURL) > 0 {
 		os.Setenv("HTTPS_PROXY", config.App.ProxyURL)
@@ -77,7 +84,7 @@ func getAllowForbidSlices(items []string) (allow []string, forbid []string) {
 	return allow, forbid
 }
 
-func getCompiledIgnorePodNamePatterns(patterns []string) (compiledPatterns []*regexp.Regexp, err error) {
+func getCompiledIgnorePatterns(patterns []string) (compiledPatterns []*regexp.Regexp, err error) {
 	compiledPatterns = make([]*regexp.Regexp, 0)
 
 	for _, pattern := range patterns {
diff --git a/filter/containerLogsFilter.go b/filter/containerLogsFilter.go
index 420db3a..e6e74a6 100644
--- a/filter/containerLogsFilter.go
+++ b/filter/containerLogsFilter.go
@@ -2,6 +2,7 @@ package filter
 
 import (
 	"github.com/abahmed/kwatch/util"
+	"github.com/sirupsen/logrus"
 )
 
 type ContainerLogsFilter struct{}
@@ -26,6 +27,15 @@ func (f ContainerLogsFilter) Execute(ctx *Context) bool {
 		previousLogs,
 		ctx.Config.MaxRecentLogLines)
 
+	for _, pattern := range ctx.Config.IgnoreLogPatternsCompiled {
+		if pattern.MatchString(logs) {
+			logrus.Infof(
+				"skipping container %s logs as it matches the ignore log pattern",
+				container.Name)
+			return true
+		}
+	}
+
 	ctx.Container.Logs = logs
 	return false
 }


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 9c30c84..e4068f5 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -74,6 +74,7 @@ func TestConfigFromFile(t *testing.T) {
 		Namespaces:        []string{"default", "!kwatch"},
 		Reasons:           []string{"default", "!kwatch"},
 		IgnorePodNames:    []string{"my-fancy-pod-[.*"},
+		IgnoreLogPatterns: []string{"leaderelection lost"},
 		App: App{
 			ProxyURL:    "https://localhost",
 			ClusterName: "development",
@@ -99,23 +100,25 @@ func TestConfigFromFile(t *testing.T) {
 	assert.NotNil(err)
 }
 
-func TestGetCompiledIgnorePodNamePatterns(t *testing.T) {
+func TestGetCompiledIgnorePatterns(t *testing.T) {
 	assert := assert.New(t)
 
 	validPatterns := []string{
 		"my-fancy-pod-[0-9]",
+		"leaderelection lost",
 	}
 
-	compiledPatterns, err := getCompiledIgnorePodNamePatterns(validPatterns)
+	compiledPatterns, err := getCompiledIgnorePatterns(validPatterns)
 
 	assert.Nil(err)
 	assert.True(compiledPatterns[0].MatchString("my-fancy-pod-8"))
+	assert.True(compiledPatterns[1].MatchString(`controllermanager.go:272] "leaderelection lost"`))
 
 	invalidPatterns := []string{
 		"my-fancy-pod-[.*",
 	}
 
-	compiledPatterns, err = getCompiledIgnorePodNamePatterns(invalidPatterns)
+	compiledPatterns, err = getCompiledIgnorePatterns(invalidPatterns)
 
 	assert.NotNil(err)
 }