@@ -282,6 +282,7 @@ func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate feature
 | 
				
			|||||||
	if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
 | 
						if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
 | 
				
			||||||
		return fmt.Errorf("internal error while setting klog vmodule: %v", err)
 | 
							return fmt.Errorf("internal error while setting klog vmodule: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						setSlogDefaultLogger()
 | 
				
			||||||
	klog.StartFlushDaemon(c.FlushFrequency.Duration.Duration)
 | 
						klog.StartFlushDaemon(c.FlushFrequency.Duration.Duration)
 | 
				
			||||||
	klog.EnableContextualLogging(p.ContextualLoggingEnabled)
 | 
						klog.EnableContextualLogging(p.ContextualLoggingEnabled)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					//go:build !go1.21
 | 
				
			||||||
 | 
					// +build !go1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func setSlogDefaultLogger() {
 | 
				
			||||||
 | 
						// Do nothing when build with Go < 1.21.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					//go:build go1.21
 | 
				
			||||||
 | 
					// +build go1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log/slog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-logr/logr"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setSlogDefaultLogger sets the global slog default logger to the same default
 | 
				
			||||||
 | 
					// that klog currently uses.
 | 
				
			||||||
 | 
					func setSlogDefaultLogger() {
 | 
				
			||||||
 | 
						// klog.Background() always returns a valid logr.Logger, regardless of
 | 
				
			||||||
 | 
						// how logging was configured. We just need to turn it into a
 | 
				
			||||||
 | 
						// slog.Handler. SetDefault then needs a slog.Logger.
 | 
				
			||||||
 | 
						handler := logr.ToSlogHandler(klog.Background())
 | 
				
			||||||
 | 
						slog.SetDefault(slog.New(handler))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,6 +5,9 @@ component-base/logs and what effect the different command line options have.
 | 
				
			|||||||
Like most Kubernetes components, `cmd` uses Cobra and pflags. `stdlib` uses
 | 
					Like most Kubernetes components, `cmd` uses Cobra and pflags. `stdlib` uses
 | 
				
			||||||
just plain Go libraries. `test` contains a unit test with per-test output.
 | 
					just plain Go libraries. `test` contains a unit test with per-test output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`slog2k8s` shows how an application using `log/slog` from Go 1.21 can include
 | 
				
			||||||
 | 
					packages from Kubernetes. `k8s2slog` is the other direction.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Below we can see examples of how some features work.
 | 
					Below we can see examples of how some features work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Default
 | 
					## Default
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					//go:build go1.21
 | 
				
			||||||
 | 
					// +build go1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2018 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log/slog"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
 | 
						"k8s.io/component-base/cli"
 | 
				
			||||||
 | 
						"k8s.io/component-base/featuregate"
 | 
				
			||||||
 | 
						"k8s.io/component-base/logs"
 | 
				
			||||||
 | 
						logsapi "k8s.io/component-base/logs/api/v1"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ "k8s.io/component-base/logs/json/register"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var featureGate = featuregate.NewFeatureGate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						runtime.Must(logsapi.AddFeatureGates(featureGate))
 | 
				
			||||||
 | 
						command := NewLoggerCommand()
 | 
				
			||||||
 | 
						code := cli.Run(command)
 | 
				
			||||||
 | 
						os.Exit(code)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewLoggerCommand() *cobra.Command {
 | 
				
			||||||
 | 
						c := logsapi.NewLoggingConfiguration()
 | 
				
			||||||
 | 
						cmd := &cobra.Command{
 | 
				
			||||||
 | 
							Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
								// This configures the global logger in klog *and* slog, if compiled
 | 
				
			||||||
 | 
								// with Go >= 1.21.
 | 
				
			||||||
 | 
								logs.InitLogs()
 | 
				
			||||||
 | 
								if err := logsapi.ValidateAndApply(c, featureGate); err != nil {
 | 
				
			||||||
 | 
									fmt.Fprintf(os.Stderr, "%v\n", err)
 | 
				
			||||||
 | 
									os.Exit(1)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if len(args) > 0 {
 | 
				
			||||||
 | 
									fmt.Fprintf(os.Stderr, "Unexpected additional command line arguments:\n    %s\n", strings.Join(args, "\n    "))
 | 
				
			||||||
 | 
									os.Exit(1)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Produce some output. Special types used by Kubernetes work.
 | 
				
			||||||
 | 
								podRef := klog.KObj(&metav1.ObjectMeta{Name: "some-pod", Namespace: "some-namespace"})
 | 
				
			||||||
 | 
								podRefs := klog.KObjSlice([]interface{}{
 | 
				
			||||||
 | 
									&metav1.ObjectMeta{Name: "some-pod", Namespace: "some-namespace"},
 | 
				
			||||||
 | 
									nil,
 | 
				
			||||||
 | 
									&metav1.ObjectMeta{Name: "other-pod"},
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								slog.Info("slog.Info", "pod", podRef, "pods", podRefs)
 | 
				
			||||||
 | 
								klog.InfoS("klog.InfoS", "pod", podRef, "pods", podRefs)
 | 
				
			||||||
 | 
								klog.Background().Info("klog.Background+logr.Logger.Info")
 | 
				
			||||||
 | 
								klog.FromContext(context.Background()).Info("klog.FromContext+logr.Logger.Info")
 | 
				
			||||||
 | 
								slogLogger := slog.Default()
 | 
				
			||||||
 | 
								slogLogger.Info("slog.Default+slog.Logger.Info")
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := logsapi.AddFeatureGates(featureGate); err != nil {
 | 
				
			||||||
 | 
							// Shouldn't happen.
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						featureGate.AddFlag(cmd.Flags())
 | 
				
			||||||
 | 
						logsapi.AddFlags(c, cmd.Flags())
 | 
				
			||||||
 | 
						return cmd
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					//go:build go1.21
 | 
				
			||||||
 | 
					// +build go1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// slog2k8s demonstrates how an application using log/slog for logging
 | 
				
			||||||
 | 
					// can include Kubernetes packages.
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"log/slog"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						options := slog.HandlerOptions{AddSource: true}
 | 
				
			||||||
 | 
						textHandler := slog.NewTextHandler(os.Stderr, &options)
 | 
				
			||||||
 | 
						textLogger := slog.New(textHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use text output as default logger.
 | 
				
			||||||
 | 
						slog.SetDefault(textLogger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// This also needs to be done through klog to ensure that all code
 | 
				
			||||||
 | 
						// using klog uses the text handler. klog.Background/TODO/FromContext
 | 
				
			||||||
 | 
						// will return a thin wrapper around the textHandler, so all that klog
 | 
				
			||||||
 | 
						// still does is manage the global default and retrieval from contexts.
 | 
				
			||||||
 | 
						klog.SetSlogLogger(textLogger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						textLogger.Info("slog.Logger.Info")
 | 
				
			||||||
 | 
						klog.InfoS("klog.InfoS")
 | 
				
			||||||
 | 
						klog.Background().Info("klog.Background+logr.Logger.Info")
 | 
				
			||||||
 | 
						klog.FromContext(context.Background()).Info("klog.FromContext+logr.Logger.Info")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/fs"
 | 
						"io/fs"
 | 
				
			||||||
 | 
						"log/slog"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
@@ -29,6 +30,8 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-logr/logr"
 | 
				
			||||||
 | 
						"go.uber.org/zap/zapcore"
 | 
				
			||||||
	"k8s.io/component-base/featuregate"
 | 
						"k8s.io/component-base/featuregate"
 | 
				
			||||||
	logsapi "k8s.io/component-base/logs/api/v1"
 | 
						logsapi "k8s.io/component-base/logs/api/v1"
 | 
				
			||||||
	_ "k8s.io/component-base/logs/json/register"
 | 
						_ "k8s.io/component-base/logs/json/register"
 | 
				
			||||||
@@ -93,6 +96,43 @@ func BenchmarkEncoding(b *testing.B) {
 | 
				
			|||||||
					b.Fatalf("Unexpected error configuring logging: %v", err)
 | 
										b.Fatalf("Unexpected error configuring logging: %v", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				logger := klog.Background()
 | 
									logger := klog.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Edit and run with this if branch enabled to use slog instead of zapr for JSON.
 | 
				
			||||||
 | 
									if format == "json" && false {
 | 
				
			||||||
 | 
										var level slog.LevelVar
 | 
				
			||||||
 | 
										level.Set(slog.Level(-3)) // hack
 | 
				
			||||||
 | 
										logger = logr.FromSlogHandler(slog.NewJSONHandler(output, &slog.HandlerOptions{
 | 
				
			||||||
 | 
											AddSource: true,
 | 
				
			||||||
 | 
											Level:     &level,
 | 
				
			||||||
 | 
											ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
 | 
				
			||||||
 | 
												switch a.Key {
 | 
				
			||||||
 | 
												case slog.TimeKey:
 | 
				
			||||||
 | 
													// Could be user-supplied "time".
 | 
				
			||||||
 | 
													if a.Value.Kind() == slog.KindTime {
 | 
				
			||||||
 | 
														return slog.Float64("ts", float64(a.Value.Time().UnixMicro())/1000)
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												case slog.LevelKey:
 | 
				
			||||||
 | 
													level := a.Value.Any().(slog.Level)
 | 
				
			||||||
 | 
													if level >= slog.LevelError {
 | 
				
			||||||
 | 
														// No verbosity on errors.
 | 
				
			||||||
 | 
														return slog.Attr{}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													if level >= 0 {
 | 
				
			||||||
 | 
														return slog.Int("v", 0)
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													return slog.Int("v", int(-level))
 | 
				
			||||||
 | 
												case slog.SourceKey:
 | 
				
			||||||
 | 
													caller := zapcore.EntryCaller{
 | 
				
			||||||
 | 
														Defined: true,
 | 
				
			||||||
 | 
														File:    a.Value.String(),
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													return slog.String("caller", caller.TrimmedPath())
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												return a
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				b.ResetTimer()
 | 
									b.ResetTimer()
 | 
				
			||||||
				start := time.Now()
 | 
									start := time.Now()
 | 
				
			||||||
				total := int64(0)
 | 
									total := int64(0)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user