Skip to content

Commit 40c4015

Browse files
committed
component-base/logs: add slog support
Integration of a slog-based package into a Kubernetes application is completely transparent when that package uses the global slog default logger because component-base will set that. When the package wants to be passed a logger instance, then one can be constructed with slogr.NewSlogHandler. Integration of a Kubernetes package into an application which uses a slog Logger is a bit more work when configuring logging. The main binary must call klog.SetLogger and needs logr/slogr to convert a slog.Handler to a logr.Logger.
1 parent e457683 commit 40c4015

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

staging/src/k8s.io/component-base/logs/api/v1/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate feature
282282
if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
283283
return fmt.Errorf("internal error while setting klog vmodule: %v", err)
284284
}
285+
setSlogDefaultLogger()
285286
klog.StartFlushDaemon(c.FlushFrequency.Duration.Duration)
286287
klog.EnableContextualLogging(p.ContextualLoggingEnabled)
287288
return nil
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//go:build !go1.21
2+
// +build !go1.21
3+
4+
/*
5+
Copyright 2023 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package v1
21+
22+
func setSlogDefaultLogger() {
23+
// Do nothing when build with Go < 1.21.
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
/*
5+
Copyright 2023 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package v1
21+
22+
import (
23+
"log/slog"
24+
25+
"github.com/go-logr/logr"
26+
"k8s.io/klog/v2"
27+
)
28+
29+
// setSlogDefaultLogger sets the global slog default logger to the same default
30+
// that klog currently uses.
31+
func setSlogDefaultLogger() {
32+
// klog.Background() always returns a valid logr.Logger, regardless of
33+
// how logging was configured. We just need to turn it into a
34+
// slog.Handler. SetDefault then needs a slog.Logger.
35+
handler := logr.ToSlogHandler(klog.Background())
36+
slog.SetDefault(slog.New(handler))
37+
}

staging/src/k8s.io/component-base/logs/example/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ component-base/logs and what effect the different command line options have.
55
Like most Kubernetes components, `cmd` uses Cobra and pflags. `stdlib` uses
66
just plain Go libraries. `test` contains a unit test with per-test output.
77

8+
`slog2k8s` shows how an application using `log/slog` from Go 1.21 can include
9+
packages from Kubernetes. `k8s2slog` is the other direction.
10+
811
Below we can see examples of how some features work.
912

1013
## Default
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
/*
5+
Copyright 2018 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package main
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"log/slog"
26+
"os"
27+
"strings"
28+
29+
"github.com/spf13/cobra"
30+
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
"k8s.io/apimachinery/pkg/util/runtime"
33+
"k8s.io/component-base/cli"
34+
"k8s.io/component-base/featuregate"
35+
"k8s.io/component-base/logs"
36+
logsapi "k8s.io/component-base/logs/api/v1"
37+
"k8s.io/klog/v2"
38+
39+
_ "k8s.io/component-base/logs/json/register"
40+
)
41+
42+
var featureGate = featuregate.NewFeatureGate()
43+
44+
func main() {
45+
runtime.Must(logsapi.AddFeatureGates(featureGate))
46+
command := NewLoggerCommand()
47+
code := cli.Run(command)
48+
os.Exit(code)
49+
}
50+
51+
func NewLoggerCommand() *cobra.Command {
52+
c := logsapi.NewLoggingConfiguration()
53+
cmd := &cobra.Command{
54+
Run: func(cmd *cobra.Command, args []string) {
55+
// This configures the global logger in klog *and* slog, if compiled
56+
// with Go >= 1.21.
57+
logs.InitLogs()
58+
if err := logsapi.ValidateAndApply(c, featureGate); err != nil {
59+
fmt.Fprintf(os.Stderr, "%v\n", err)
60+
os.Exit(1)
61+
}
62+
if len(args) > 0 {
63+
fmt.Fprintf(os.Stderr, "Unexpected additional command line arguments:\n %s\n", strings.Join(args, "\n "))
64+
os.Exit(1)
65+
}
66+
67+
// Produce some output. Special types used by Kubernetes work.
68+
podRef := klog.KObj(&metav1.ObjectMeta{Name: "some-pod", Namespace: "some-namespace"})
69+
podRefs := klog.KObjSlice([]interface{}{
70+
&metav1.ObjectMeta{Name: "some-pod", Namespace: "some-namespace"},
71+
nil,
72+
&metav1.ObjectMeta{Name: "other-pod"},
73+
})
74+
slog.Info("slog.Info", "pod", podRef, "pods", podRefs)
75+
klog.InfoS("klog.InfoS", "pod", podRef, "pods", podRefs)
76+
klog.Background().Info("klog.Background+logr.Logger.Info")
77+
klog.FromContext(context.Background()).Info("klog.FromContext+logr.Logger.Info")
78+
slogLogger := slog.Default()
79+
slogLogger.Info("slog.Default+slog.Logger.Info")
80+
},
81+
}
82+
if err := logsapi.AddFeatureGates(featureGate); err != nil {
83+
// Shouldn't happen.
84+
panic(err)
85+
}
86+
featureGate.AddFlag(cmd.Flags())
87+
logsapi.AddFlags(c, cmd.Flags())
88+
return cmd
89+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
/*
5+
Copyright 2023 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
// slog2k8s demonstrates how an application using log/slog for logging
21+
// can include Kubernetes packages.
22+
package main
23+
24+
import (
25+
"context"
26+
"log/slog"
27+
"os"
28+
29+
"k8s.io/klog/v2"
30+
)
31+
32+
func main() {
33+
options := slog.HandlerOptions{AddSource: true}
34+
textHandler := slog.NewTextHandler(os.Stderr, &options)
35+
textLogger := slog.New(textHandler)
36+
37+
// Use text output as default logger.
38+
slog.SetDefault(textLogger)
39+
40+
// This also needs to be done through klog to ensure that all code
41+
// using klog uses the text handler. klog.Background/TODO/FromContext
42+
// will return a thin wrapper around the textHandler, so all that klog
43+
// still does is manage the global default and retrieval from contexts.
44+
klog.SetSlogLogger(textLogger)
45+
46+
textLogger.Info("slog.Logger.Info")
47+
klog.InfoS("klog.InfoS")
48+
klog.Background().Info("klog.Background+logr.Logger.Info")
49+
klog.FromContext(context.Background()).Info("klog.FromContext+logr.Logger.Info")
50+
}

0 commit comments

Comments
 (0)