...

Source file src/github.com/go-logr/logr/funcr/slogsink_test.go

Documentation: github.com/go-logr/logr/funcr

     1  //go:build go1.21
     2  // +build go1.21
     3  
     4  /*
     5  Copyright 2021 The logr 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 funcr
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"log/slog"
    26  	"path/filepath"
    27  	"runtime"
    28  	"testing"
    29  
    30  	"github.com/go-logr/logr"
    31  	"github.com/go-logr/logr/internal/testhelp"
    32  )
    33  
    34  func TestSlogSink(t *testing.T) {
    35  	testCases := []struct {
    36  		name      string
    37  		withAttrs []any
    38  		withGroup string
    39  		args      []any
    40  		expect    string
    41  	}{{
    42  		name:   "just msg",
    43  		args:   makeKV(),
    44  		expect: `{"logger":"","level":0,"msg":"msg"}`,
    45  	}, {
    46  		name:   "primitives",
    47  		args:   makeKV("int", 1, "str", "ABC", "bool", true),
    48  		expect: `{"logger":"","level":0,"msg":"msg","int":1,"str":"ABC","bool":true}`,
    49  	}, {
    50  		name:      "with attrs",
    51  		withAttrs: makeKV("attrInt", 1, "attrStr", "ABC", "attrBool", true),
    52  		args:      makeKV("int", 2),
    53  		expect:    `{"logger":"","level":0,"msg":"msg","attrInt":1,"attrStr":"ABC","attrBool":true,"int":2}`,
    54  	}, {
    55  		name:      "with group",
    56  		withGroup: "groupname",
    57  		args:      makeKV("int", 1, "str", "ABC", "bool", true),
    58  		expect:    `{"logger":"","level":0,"msg":"msg","groupname":{"int":1,"str":"ABC","bool":true}}`,
    59  	}, {
    60  		name:      "with attrs and group",
    61  		withAttrs: makeKV("attrInt", 1, "attrStr", "ABC"),
    62  		withGroup: "groupname",
    63  		args:      makeKV("int", 3, "bool", true),
    64  		expect:    `{"logger":"","level":0,"msg":"msg","attrInt":1,"attrStr":"ABC","groupname":{"int":3,"bool":true}}`,
    65  	}}
    66  
    67  	for _, tc := range testCases {
    68  		t.Run(tc.name, func(t *testing.T) {
    69  			capt := &capture{}
    70  			logger := logr.New(newSink(capt.Func, NewFormatterJSON(Options{})))
    71  			slogger := slog.New(logr.ToSlogHandler(logger))
    72  			if len(tc.withAttrs) > 0 {
    73  				slogger = slogger.With(tc.withAttrs...)
    74  			}
    75  			if tc.withGroup != "" {
    76  				slogger = slogger.WithGroup(tc.withGroup)
    77  			}
    78  			slogger.Info("msg", tc.args...)
    79  			if capt.log != tc.expect {
    80  				t.Errorf("\nexpected %q\n     got %q", tc.expect, capt.log)
    81  			}
    82  		})
    83  	}
    84  }
    85  
    86  func TestSlogSinkGroups(t *testing.T) {
    87  	testCases := []struct {
    88  		name   string
    89  		fn     func(slogger *slog.Logger)
    90  		expect string
    91  	}{{
    92  		name: "no group",
    93  		fn: func(slogger *slog.Logger) {
    94  			slogger.
    95  				Info("msg", "k", "v")
    96  		},
    97  		expect: `{"logger":"","level":0,"msg":"msg","k":"v"}`,
    98  	}, {
    99  		name: "1 group with leaf args",
   100  		fn: func(slogger *slog.Logger) {
   101  			slogger.
   102  				WithGroup("g1").
   103  				Info("msg", "k", "v")
   104  		},
   105  		expect: `{"logger":"","level":0,"msg":"msg","g1":{"k":"v"}}`,
   106  	}, {
   107  		name: "1 group without leaf args",
   108  		fn: func(slogger *slog.Logger) {
   109  			slogger.
   110  				WithGroup("g1").
   111  				Info("msg")
   112  		},
   113  		expect: `{"logger":"","level":0,"msg":"msg"}`,
   114  	}, {
   115  		name: "1 group with value without leaf args",
   116  		fn: func(slogger *slog.Logger) {
   117  			slogger.
   118  				WithGroup("g1").With("k1", 1).
   119  				Info("msg")
   120  		},
   121  		expect: `{"logger":"","level":0,"msg":"msg","g1":{"k1":1}}`,
   122  	}, {
   123  		name: "2 groups with values no leaf args",
   124  		fn: func(slogger *slog.Logger) {
   125  			slogger.
   126  				WithGroup("g1").With("k1", 1).
   127  				WithGroup("g2").With("k2", 2).
   128  				Info("msg")
   129  		},
   130  		expect: `{"logger":"","level":0,"msg":"msg","g1":{"k1":1,"g2":{"k2":2}}}`,
   131  	}, {
   132  		name: "3 empty groups with no values or leaf args",
   133  		fn: func(slogger *slog.Logger) {
   134  			slogger.
   135  				WithGroup("g1").
   136  				WithGroup("g2").
   137  				WithGroup("g3").
   138  				Info("msg")
   139  		},
   140  		expect: `{"logger":"","level":0,"msg":"msg"}`,
   141  	}, {
   142  		name: "3 empty groups with no values but with leaf args",
   143  		fn: func(slogger *slog.Logger) {
   144  			slogger.
   145  				WithGroup("g1").
   146  				WithGroup("g2").
   147  				WithGroup("g3").
   148  				Info("msg", "k", "v")
   149  		},
   150  		expect: `{"logger":"","level":0,"msg":"msg","g1":{"g2":{"g3":{"k":"v"}}}}`,
   151  	}, {
   152  		name: "multiple groups with and without values",
   153  		fn: func(slogger *slog.Logger) {
   154  			slogger.
   155  				With("k0", 0).
   156  				WithGroup("g1").
   157  				WithGroup("g2").
   158  				WithGroup("g3").With("k3", 3).
   159  				WithGroup("g4").
   160  				WithGroup("g5").
   161  				WithGroup("g6").With("k6", 6).
   162  				WithGroup("g7").
   163  				WithGroup("g8").
   164  				WithGroup("g9").
   165  				Info("msg")
   166  		},
   167  		expect: `{"logger":"","level":0,"msg":"msg","k0":0,"g1":{"g2":{"g3":{"k3":3,"g4":{"g5":{"g6":{"k6":6}}}}}}}`,
   168  	}}
   169  
   170  	for _, tc := range testCases {
   171  		t.Run(tc.name, func(t *testing.T) {
   172  			capt := &capture{}
   173  			logger := logr.New(newSink(capt.Func, NewFormatterJSON(Options{})))
   174  			slogger := slog.New(logr.ToSlogHandler(logger))
   175  			tc.fn(slogger)
   176  			if capt.log != tc.expect {
   177  				t.Errorf("\nexpected: `%s`\n     got: `%s`", tc.expect, capt.log)
   178  			}
   179  		})
   180  	}
   181  }
   182  
   183  func TestSlogSinkWithCaller(t *testing.T) {
   184  	capt := &capture{}
   185  	logger := logr.New(newSink(capt.Func, NewFormatterJSON(Options{LogCaller: All})))
   186  	slogger := slog.New(logr.ToSlogHandler(logger))
   187  	slogger.Error("msg", "int", 1)
   188  	_, file, line, _ := runtime.Caller(0)
   189  	expect := fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d},"msg":"msg","error":null,"int":1}`, filepath.Base(file), line-1)
   190  	if capt.log != expect {
   191  		t.Errorf("\nexpected %q\n     got %q", expect, capt.log)
   192  	}
   193  }
   194  
   195  func TestRunSlogTests(t *testing.T) {
   196  	fn := func(buffer *bytes.Buffer) slog.Handler {
   197  		printfn := func(obj string) {
   198  			fmt.Fprintln(buffer, obj)
   199  		}
   200  		opts := Options{
   201  			LogTimestamp: true,
   202  			Verbosity:    10,
   203  			RenderBuiltinsHook: func(kvList []any) []any {
   204  				mappedKVList := make([]any, len(kvList))
   205  				for i := 0; i < len(kvList); i += 2 {
   206  					key := kvList[i]
   207  					switch key {
   208  					case "ts":
   209  						mappedKVList[i] = "time"
   210  					default:
   211  						mappedKVList[i] = key
   212  					}
   213  					mappedKVList[i+1] = kvList[i+1]
   214  				}
   215  				return mappedKVList
   216  			},
   217  		}
   218  		logger := NewJSON(printfn, opts)
   219  		return logr.ToSlogHandler(logger)
   220  	}
   221  	exceptions := []string{
   222  		"a Handler should ignore a zero Record.Time", // Time is generated by sink.
   223  	}
   224  	testhelp.RunSlogTests(t, fn, exceptions...)
   225  }
   226  
   227  func TestLogrSlogConversion(t *testing.T) {
   228  	f := New(func(_, _ string) {}, Options{})
   229  	f2 := logr.FromSlogHandler(logr.ToSlogHandler(f))
   230  	if want, got := f, f2; got != want {
   231  		t.Helper()
   232  		t.Errorf("Expected %T %+v, got instead: %T %+v", want, want, got, got)
   233  	}
   234  }
   235  

View as plain text