Skip to content

Commit 6e0e9f3

Browse files
authored
Merge pull request kubernetes#109194 from pohly/log-tests
log tests
2 parents 5f40fb0 + f45322e commit 6e0e9f3

File tree

11 files changed

+1421
-2
lines changed

11 files changed

+1421
-2
lines changed

hack/logcheck.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,14 @@ structured k8s.io/kubernetes/cmd/kubelet/.*
2020
structured k8s.io/kubernetes/pkg/kubelet/.*
2121
structured k8s.io/kubernetes/pkg/proxy/.*
2222
structured k8s.io/kubernetes/pkg/scheduler/.*
23+
24+
# The following packages have been migrated to contextual logging.
25+
# Packages matched here do not have to be listed above because
26+
# "contextual" implies "structured".
27+
# TODO next: contextual k8s.io/kubernetes/pkg/scheduler/.*
28+
29+
# As long as contextual logging is alpha or beta, all WithName, WithValues,
30+
# NewContext calls have to go through klog. Once it is GA, we can lift
31+
# this restriction. Whether we then do a global search/replace remains
32+
# to be decided.
33+
with-helpers .*

staging/src/k8s.io/component-base/logs/example/cmd/logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func NewLoggerCommand() *cobra.Command {
5858
}
5959

6060
// Initialize contextual logging.
61-
logger := klog.Background().WithName("example").WithValues("foo", "bar")
61+
logger := klog.LoggerWithValues(klog.LoggerWithName(klog.Background(), "example"), "foo", "bar")
6262
ctx := klog.NewContext(context.Background(), logger)
6363

6464
runLogger(ctx)

staging/src/k8s.io/component-base/logs/json/json_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func TestZapLoggerInfo(t *testing.T) {
7878
writer := zapcore.AddSync(&buffer)
7979
sampleInfoLogger, _ := NewJSONLogger(0, writer, nil, nil)
8080
for _, name := range data.names {
81+
// nolint:logcheck // This intentionally ignore the feature gate and always tests with a name.
8182
sampleInfoLogger = sampleInfoLogger.WithName(name)
8283
}
8384
// nolint:logcheck // The linter cannot and doesn't need to check the key/value pairs.

staging/src/k8s.io/component-base/logs/json/klog_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/stretchr/testify/assert"
2929
"go.uber.org/zap/zapcore"
3030

31+
"k8s.io/component-base/config"
3132
"k8s.io/klog/v2"
3233
)
3334

@@ -239,7 +240,9 @@ func TestKlogIntegration(t *testing.T) {
239240
t.Run(tc.name, func(t *testing.T) {
240241
var buffer bytes.Buffer
241242
writer := zapcore.AddSync(&buffer)
242-
logger, _ := NewJSONLogger(100, writer, nil, nil)
243+
// This level is high enough to enable all log messages from this test.
244+
verbosityLevel := config.VerbosityLevel(100)
245+
logger, _ := NewJSONLogger(verbosityLevel, writer, nil, nil)
243246
klog.SetLogger(logger)
244247
defer klog.ClearLogger()
245248

staging/src/k8s.io/component-base/logs/options_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ func testContextualLogging(t *testing.T, enabled bool) {
128128
defer klog.EnableContextualLogging(true)
129129

130130
ctx := context.Background()
131+
// nolint:logcheck // This intentionally adds a name independently of the feature gate.
131132
logger := klog.NewKlogr().WithName("contextual")
133+
// nolint:logcheck // This intentionally creates a new context independently of the feature gate.
132134
ctx = logr.NewContext(ctx, logger)
133135
if enabled {
134136
assert.Equal(t, logger, klog.FromContext(ctx), "FromContext")
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
Copyright 2021 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 contextuallogging
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"testing"
23+
"time"
24+
25+
"github.com/go-logr/logr"
26+
27+
"k8s.io/klog/v2"
28+
)
29+
30+
func init() {
31+
klog.InitFlags(nil)
32+
}
33+
34+
// BenchmarkRecursion measures the overhead of adding calling a function
35+
// recursively with just the depth parameter.
36+
func BenchmarkRecursion(b *testing.B) {
37+
for depth := 10; depth <= 100000; depth *= 10 {
38+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
39+
for i := 0; i < b.N; i++ {
40+
recurse(depth)
41+
}
42+
})
43+
}
44+
}
45+
46+
//go:noinline
47+
func recurse(depth int) {
48+
if depth == 0 {
49+
logr.Discard().Info("hello world")
50+
return
51+
}
52+
recurse(depth - 1)
53+
}
54+
55+
// BenchmarkRecursionWithLogger measures the overhead of adding a logr.Logger
56+
// parameter.
57+
func BenchmarkRecursionWithLogger(b *testing.B) {
58+
logger := logr.Discard()
59+
60+
for depth := 10; depth <= 100000; depth *= 10 {
61+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
62+
for i := 0; i < b.N; i++ {
63+
recurseWithLogger(logger, depth)
64+
}
65+
})
66+
}
67+
}
68+
69+
//go:noinline
70+
func recurseWithLogger(logger logr.Logger, depth int) {
71+
if depth == 0 {
72+
return
73+
}
74+
recurseWithLogger(logger, depth-1)
75+
}
76+
77+
// BenchmarkRecursionWithContext measures the overhead of adding a context
78+
// parameter.
79+
func BenchmarkRecursionWithContext(b *testing.B) {
80+
logger := logr.Discard()
81+
// nolint:logcheck // Intentionally using NewContext unconditionally here.
82+
ctx := logr.NewContext(context.Background(), logger)
83+
84+
for depth := 10; depth <= 100000; depth *= 10 {
85+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
86+
for i := 0; i < b.N; i++ {
87+
recurseWithContext(ctx, depth)
88+
}
89+
})
90+
}
91+
}
92+
93+
//go:noinline
94+
func recurseWithContext(ctx context.Context, depth int) {
95+
if depth == 0 {
96+
return
97+
}
98+
recurseWithContext(ctx, depth-1)
99+
}
100+
101+
// BenchmarkRecursionWithLogger measures the overhead of adding a logr.Logger
102+
// parameter and using it once.
103+
func BenchmarkRecursionWithLoggerAndLog(b *testing.B) {
104+
logger := logr.Discard()
105+
106+
for depth := 10; depth <= 100000; depth *= 10 {
107+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
108+
for i := 0; i < b.N; i++ {
109+
recurseWithLoggerAndLog(logger, depth)
110+
}
111+
})
112+
}
113+
}
114+
115+
//go:noinline
116+
func recurseWithLoggerAndLog(logger logr.Logger, depth int) {
117+
if depth == 0 {
118+
logger.Info("hello world")
119+
return
120+
}
121+
recurseWithLoggerAndLog(logger, depth-1)
122+
}
123+
124+
// BenchmarkRecursionWithContext measures the overhead of adding a context
125+
// parameter and using it once to retrieve and call a logger.
126+
func BenchmarkRecursionWithContextAndLog(b *testing.B) {
127+
logger := logr.Discard()
128+
// nolint:logcheck // Intentionally using NewContext unconditionally here.
129+
ctx := logr.NewContext(context.Background(), logger)
130+
131+
for depth := 10; depth <= 100000; depth *= 10 {
132+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
133+
for i := 0; i < b.N; i++ {
134+
recurseWithContextAndLog(ctx, depth)
135+
}
136+
})
137+
}
138+
}
139+
140+
//go:noinline
141+
func recurseWithContextAndLog(ctx context.Context, depth int) {
142+
if depth == 0 {
143+
logger := logr.FromContextOrDiscard(ctx)
144+
logger.Info("hello world")
145+
return
146+
}
147+
recurseWithContextAndLog(ctx, depth-1)
148+
}
149+
150+
// BenchmarkNestedContextWithTimeouts benchmarks how quickly a function can be
151+
// called that creates a new context at each call with context.WithTimeout.
152+
func BenchmarkNestedContextWithTimeouts(b *testing.B) {
153+
ctx := context.Background()
154+
155+
for depth := 1; depth <= 10000; depth *= 10 {
156+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
157+
for i := 0; i < b.N; i++ {
158+
nestedContextWithTimeout(ctx, depth)
159+
}
160+
})
161+
}
162+
}
163+
164+
//go:noinline
165+
func nestedContextWithTimeout(ctx context.Context, depth int) {
166+
if depth == 0 {
167+
return
168+
}
169+
ctx, cancel := context.WithTimeout(ctx, time.Hour)
170+
defer cancel()
171+
nestedContextWithTimeout(ctx, depth-1)
172+
}
173+
174+
// BenchmarkNestedContextWithTimeouts benchmarks how quickly a function can be
175+
// called that creates a new context at each call with context.WithTimeout
176+
// and then looks up a logger.
177+
func BenchmarkNestedContextWithTimeoutsAndLookup(b *testing.B) {
178+
// nolint:logcheck // Intentionally using NewContext unconditionally here.
179+
ctx := logr.NewContext(context.Background(), logr.Discard())
180+
181+
for depth := 1; depth <= 10000; depth *= 10 {
182+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
183+
for i := 0; i < b.N; i++ {
184+
nestedContextWithTimeoutAndLookup(ctx, depth)
185+
}
186+
})
187+
}
188+
}
189+
190+
//go:noinline
191+
func nestedContextWithTimeoutAndLookup(ctx context.Context, depth int) {
192+
if depth == 0 {
193+
logr.FromContextOrDiscard(ctx)
194+
return
195+
}
196+
ctx, cancel := context.WithTimeout(ctx, time.Hour)
197+
defer cancel()
198+
nestedContextWithTimeoutAndLookup(ctx, depth-1)
199+
}
200+
201+
var logger logr.Logger
202+
203+
// BenchmarkNestedContextWithTimeouts benchmarks how quickly FromContextOrDiscard
204+
// can look up a logger in nested contexts where WithTimeouts is used to
205+
// created those nested contexts.
206+
func BenchmarkLookupWithTimeouts(b *testing.B) {
207+
for depth := 1; depth <= 10000; depth *= 10 {
208+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
209+
// nolint:logcheck // Intentionally using NewContext unconditionally here.
210+
ctx := logr.NewContext(context.Background(), logr.Discard())
211+
for i := 0; i < depth; i++ {
212+
ctx2, cancel := context.WithTimeout(ctx, time.Hour)
213+
defer cancel()
214+
ctx = ctx2
215+
}
216+
b.ResetTimer()
217+
for i := 0; i < b.N; i++ {
218+
logger = logr.FromContextOrDiscard(ctx)
219+
}
220+
})
221+
}
222+
}
223+
224+
type keyT struct{}
225+
226+
var key keyT
227+
228+
// BenchmarkNestedContextWithTimeouts benchmarks how quickly FromContextOrDiscard
229+
// can look up a logger in nested contexts where WithValue is used to
230+
// created those nested contexts.
231+
func BenchmarkLookupWithValues(b *testing.B) {
232+
for depth := 1; depth <= 10000; depth *= 10 {
233+
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
234+
// nolint:logcheck // Intentionally using NewContext unconditionally here.
235+
ctx := logr.NewContext(context.Background(), logr.Discard())
236+
for i := 0; i < depth; i++ {
237+
ctx = context.WithValue(ctx, key, depth)
238+
}
239+
b.ResetTimer()
240+
for i := 0; i < b.N; i++ {
241+
logger = logr.FromContextOrDiscard(ctx)
242+
}
243+
})
244+
}
245+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
Copyright 2021 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 json
18+
19+
import (
20+
"io"
21+
"os"
22+
"strings"
23+
"testing"
24+
25+
"github.com/go-logr/logr"
26+
"go.uber.org/zap/zapcore"
27+
28+
"k8s.io/component-base/config"
29+
logsjson "k8s.io/component-base/logs/json"
30+
"k8s.io/klog/v2/test"
31+
)
32+
33+
func init() {
34+
test.InitKlog()
35+
}
36+
37+
// TestJsonOutput tests the JSON logger, directly and as backend for klog.
38+
func TestJSONOutput(t *testing.T) {
39+
newLogger := func(out io.Writer, v int, vmodule string) logr.Logger {
40+
logger, _ := logsjson.NewJSONLogger(config.VerbosityLevel(v), logsjson.AddNopSync(out), nil,
41+
&zapcore.EncoderConfig{
42+
MessageKey: "msg",
43+
CallerKey: "caller",
44+
NameKey: "logger",
45+
EncodeDuration: zapcore.StringDurationEncoder,
46+
EncodeCaller: zapcore.ShortCallerEncoder,
47+
})
48+
return logger
49+
}
50+
51+
// If Go modules are turned off (for example, as in "make test-integration"),
52+
// references to klog like k8s.io/klog/v2.ObjectRef.MarshalLog become
53+
// k8s.io/kubernetes/vendor/k8s.io/klog/v2.ObjectRef.MarshalLog.
54+
injectVendor := func(mapping map[string]string) map[string]string {
55+
if os.Getenv("GO111MODULE") != "off" {
56+
return mapping
57+
}
58+
for key, value := range mapping {
59+
mapping[key] = strings.ReplaceAll(value, "k8s.io/klog/v2", "k8s.io/kubernetes/vendor/k8s.io/klog/v2")
60+
}
61+
return mapping
62+
}
63+
64+
t.Run("direct", func(t *testing.T) {
65+
test.Output(t, test.OutputConfig{
66+
NewLogger: newLogger,
67+
ExpectedOutputMapping: injectVendor(test.ZaprOutputMappingDirect()),
68+
})
69+
})
70+
71+
t.Run("klog-backend", func(t *testing.T) {
72+
test.Output(t, test.OutputConfig{
73+
NewLogger: newLogger,
74+
AsBackend: true,
75+
ExpectedOutputMapping: injectVendor(test.ZaprOutputMappingIndirect()),
76+
})
77+
})
78+
}

0 commit comments

Comments
 (0)