Skip to content

Commit a27eb82

Browse files
committed
✨ util: Warning handler that discards messages that match a regular expression
1 parent a08e4b4 commit a27eb82

File tree

3 files changed

+192
-0
lines changed

3 files changed

+192
-0
lines changed

util/apiwarnings/doc.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package apiwarnings defines warning handlers used with API clients.
18+
package apiwarnings

util/apiwarnings/handler.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package apiwarnings
18+
19+
import (
20+
"regexp"
21+
"sync"
22+
23+
"github.com/go-logr/logr"
24+
)
25+
26+
// DiscardMatchingHandlerOptions configures the handler created with
27+
// NewDiscardMatchingHandler.
28+
type DiscardMatchingHandlerOptions struct {
29+
// Deduplicate indicates a given warning message should only be written once.
30+
// Setting this to true in a long-running process handling many warnings can
31+
// result in increased memory use.
32+
Deduplicate bool
33+
34+
// Expressions is a slice of regular expressions used to discard warnings.
35+
// If the warning message matches any expression, it is not logged.
36+
Expressions []regexp.Regexp
37+
}
38+
39+
// NewDiscardMatchingHandler initializes and returns a new DiscardMatchingHandler.
40+
func NewDiscardMatchingHandler(l logr.Logger, opts DiscardMatchingHandlerOptions) *DiscardMatchingHandler {
41+
h := &DiscardMatchingHandler{logger: l, opts: opts}
42+
if opts.Deduplicate {
43+
h.logged = map[string]struct{}{}
44+
}
45+
return h
46+
}
47+
48+
// DiscardMatchingHandler is a handler that discards API server warnings
49+
// whose message matches any user-defined regular expressions.
50+
type DiscardMatchingHandler struct {
51+
// logger is used to log responses with the warning header
52+
logger logr.Logger
53+
// opts contain options controlling warning output
54+
opts DiscardMatchingHandlerOptions
55+
// loggedLock guards logged
56+
loggedLock sync.Mutex
57+
// used to keep track of already logged messages
58+
// and help in de-duplication.
59+
logged map[string]struct{}
60+
}
61+
62+
// HandleWarningHeader handles logging for responses from API server that are
63+
// warnings with code being 299 and uses a logr.Logger for its logging purposes.
64+
func (h *DiscardMatchingHandler) HandleWarningHeader(code int, _, message string) {
65+
if code != 299 || message == "" {
66+
return
67+
}
68+
69+
for _, exp := range h.opts.Expressions {
70+
if exp.MatchString(message) {
71+
return
72+
}
73+
}
74+
75+
if h.opts.Deduplicate {
76+
h.loggedLock.Lock()
77+
defer h.loggedLock.Unlock()
78+
79+
if _, alreadyLogged := h.logged[message]; alreadyLogged {
80+
return
81+
}
82+
h.logged[message] = struct{}{}
83+
}
84+
h.logger.Info(message)
85+
}

util/apiwarnings/handler_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package apiwarnings
18+
19+
import (
20+
"regexp"
21+
"testing"
22+
23+
"github.com/go-logr/logr/funcr"
24+
. "github.com/onsi/gomega"
25+
)
26+
27+
func TestDiscardMatchingHandler(t *testing.T) {
28+
tests := []struct {
29+
name string
30+
opts DiscardMatchingHandlerOptions
31+
code int
32+
message string
33+
wantLogged bool
34+
}{
35+
{
36+
name: "log, if warning does not match any expression",
37+
code: 299,
38+
message: "non-matching warning",
39+
opts: DiscardMatchingHandlerOptions{
40+
Expressions: []regexp.Regexp{},
41+
},
42+
wantLogged: true,
43+
},
44+
{
45+
name: "do not log, if warning matches at least one expression",
46+
code: 299,
47+
message: "matching warning",
48+
opts: DiscardMatchingHandlerOptions{
49+
Expressions: []regexp.Regexp{
50+
*regexp.MustCompile("^matching.*"),
51+
},
52+
},
53+
wantLogged: false,
54+
},
55+
{
56+
name: "do not log, if code is not 299",
57+
code: 0,
58+
message: "",
59+
opts: DiscardMatchingHandlerOptions{
60+
Expressions: []regexp.Regexp{},
61+
},
62+
wantLogged: false,
63+
},
64+
}
65+
for _, tt := range tests {
66+
t.Run(tt.name, func(t *testing.T) {
67+
g := NewWithT(t)
68+
logged := false
69+
h := NewDiscardMatchingHandler(
70+
funcr.New(func(_, _ string) {
71+
logged = true
72+
},
73+
funcr.Options{},
74+
),
75+
tt.opts,
76+
)
77+
h.HandleWarningHeader(tt.code, "", tt.message)
78+
g.Expect(logged).To(Equal(tt.wantLogged))
79+
})
80+
}
81+
}
82+
83+
func TestDiscardMatchingHandler_uninitialized(t *testing.T) {
84+
g := NewWithT(t)
85+
h := DiscardMatchingHandler{}
86+
g.Expect(func() {
87+
h.HandleWarningHeader(0, "", "")
88+
}).ToNot(Panic())
89+
}

0 commit comments

Comments
 (0)