github.com/searKing/golang/go@v1.2.117/log/slog/glog_handler_test.go (about) 1 // Copyright 2023 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package slog 6 7 import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "io" 13 "log/slog" 14 "os" 15 "strings" 16 "testing" 17 "time" 18 ) 19 20 var testTime = time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC) 21 22 func TestGlogHandler(t *testing.T) { 23 getPid = func() int { return 0 } // set pid to zero for test 24 defer func() { getPid = os.Getpid }() 25 26 for _, test := range []struct { 27 name string 28 attr slog.Attr 29 wantKey, wantVal string 30 }{ 31 { 32 "unquoted", 33 slog.Int("a", 1), 34 "a", "1", 35 }, 36 { 37 "quoted", 38 slog.String("x = y", `qu"o`), 39 `x = y`, "`qu\"o`", 40 }, 41 { 42 "String method", 43 slog.Any("name", name{"Ren", "Hoek"}), 44 `name`, `Hoek, Ren`, 45 }, 46 { 47 "struct", 48 slog.Any("x", &struct{ A, b int }{A: 1, b: 2}), 49 `x`, `&{A:1 b:2}`, 50 }, 51 { 52 "TextMarshaler", 53 slog.Any("t", text{"abc"}), 54 `t`, "`text{\"abc\"}`", 55 }, 56 { 57 "TextMarshaler error", 58 slog.Any("t", text{""}), 59 `t`, `!ERROR:text: empty string`, 60 }, 61 { 62 "nil value", 63 slog.Any("a", nil), 64 `a`, `<nil>`, 65 }, 66 } { 67 t.Run(test.name, func(t *testing.T) { 68 for _, opts := range []struct { 69 name string 70 opts slog.HandlerOptions 71 wantPrefix string 72 modKey func(string) string 73 }{ 74 { 75 "none", 76 slog.HandlerOptions{}, 77 `I20000102 03:04:05.000000 0] a message`, 78 func(s string) string { return s }, 79 }, 80 { 81 "replace", 82 slog.HandlerOptions{ReplaceAttr: upperCaseKey}, 83 `I20000102 03:04:05.000000 0] a message`, 84 strings.ToUpper, 85 }, 86 } { 87 t.Run(opts.name, func(t *testing.T) { 88 var buf bytes.Buffer 89 h := NewGlogHandler(&buf, &opts.opts) 90 r := slog.NewRecord(testTime, slog.LevelInfo, "a message", 0) 91 r.AddAttrs(test.attr) 92 if err := h.Handle(context.Background(), r); err != nil { 93 t.Fatal(err) 94 } 95 got := buf.String() 96 // Remove final newline. 97 got = got[:len(got)-1] 98 want := opts.wantPrefix + ", " + opts.modKey(test.wantKey) + "=" + test.wantVal 99 if got != want { 100 t.Errorf("\ngot %s\nwant %s", got, want) 101 } 102 }) 103 } 104 }) 105 } 106 } 107 108 func TestGlogHumanHandler(t *testing.T) { 109 getPid = func() int { return 0 } // set pid to zero for test 110 defer func() { getPid = os.Getpid }() 111 112 for _, test := range []struct { 113 name string 114 attr slog.Attr 115 wantKey, wantVal string 116 }{ 117 { 118 "unquoted", 119 slog.Int("a", 1), 120 "a", "1", 121 }, 122 { 123 "quoted", 124 slog.String("x = y", `qu"o`), 125 `x = y`, "`qu\"o`", 126 }, 127 { 128 "String method", 129 slog.Any("name", name{"Ren", "Hoek"}), 130 `name`, `Hoek, Ren`, 131 }, 132 { 133 "struct", 134 slog.Any("x", &struct{ A, b int }{A: 1, b: 2}), 135 `x`, `&{A:1 b:2}`, 136 }, 137 { 138 "TextMarshaler", 139 slog.Any("t", text{"abc"}), 140 `t`, "`text{\"abc\"}`", 141 }, 142 { 143 "TextMarshaler error", 144 slog.Any("t", text{""}), 145 `t`, `!ERROR:text: empty string`, 146 }, 147 { 148 "nil value", 149 slog.Any("a", nil), 150 `a`, `<nil>`, 151 }, 152 { 153 "typed nil value", 154 slog.Any("a", (*text)(nil)), 155 `a`, `<nil>`, 156 }, 157 } { 158 t.Run(test.name, func(t *testing.T) { 159 for _, opts := range []struct { 160 name string 161 opts slog.HandlerOptions 162 wantPrefix string 163 modKey func(string) string 164 }{ 165 { 166 "none", 167 slog.HandlerOptions{}, 168 `[INFO ][20000102 03:04:05.000000] [0] a message`, 169 func(s string) string { return s }, 170 }, 171 { 172 "replace", 173 slog.HandlerOptions{ReplaceAttr: upperCaseKey}, 174 `[INFO ][20000102 03:04:05.000000] [0] a message`, 175 strings.ToUpper, 176 }, 177 } { 178 t.Run(opts.name, func(t *testing.T) { 179 var buf bytes.Buffer 180 h := NewGlogHumanHandler(&buf, &opts.opts) 181 r := slog.NewRecord(testTime, slog.LevelInfo, "a message", 0) 182 r.AddAttrs(test.attr) 183 if err := h.Handle(context.Background(), r); err != nil { 184 t.Fatal(err) 185 } 186 got := buf.String() 187 // Remove final newline. 188 got = got[:len(got)-1] 189 want := opts.wantPrefix + ", " + opts.modKey(test.wantKey) + "=" + test.wantVal 190 if got != want { 191 t.Errorf("\ngot %s\nwant %s", got, want) 192 } 193 }) 194 } 195 }) 196 } 197 } 198 199 // for testing fmt.Sprint 200 type name struct { 201 First, Last string 202 } 203 204 func (n name) String() string { return n.Last + ", " + n.First } 205 206 // for testing TextMarshaler 207 type text struct { 208 s string 209 } 210 211 func (t text) Error() string { return t.s } // should be ignored 212 213 func (t text) MarshalText() ([]byte, error) { 214 if t.s == "" { 215 return nil, errors.New("text: empty string") 216 } 217 return []byte(fmt.Sprintf("text{%q}", t.s)), nil 218 } 219 220 func TestTextHandlerPreformatted(t *testing.T) { 221 var buf bytes.Buffer 222 var h slog.Handler = slog.NewTextHandler(&buf, nil) 223 h = h.WithAttrs([]slog.Attr{slog.Duration("dur", time.Minute), slog.Bool("b", true)}) 224 // Also test omitting time. 225 r := slog.NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "m", 0) 226 r.AddAttrs(slog.Int("a", 1)) 227 if err := h.Handle(context.Background(), r); err != nil { 228 t.Fatal(err) 229 } 230 got := strings.TrimSuffix(buf.String(), "\n") 231 want := `level=INFO msg=m dur=1m0s b=true a=1` 232 if got != want { 233 t.Errorf("got %s, want %s", got, want) 234 } 235 } 236 237 func TestTextHandlerAlloc(t *testing.T) { 238 r := slog.NewRecord(time.Now(), slog.LevelInfo, "msg", 0) 239 for i := 0; i < 10; i++ { 240 r.AddAttrs(slog.Int("x = y", i)) 241 } 242 var h slog.Handler = slog.NewTextHandler(io.Discard, nil) 243 wantAllocs(t, 0, func() { h.Handle(context.Background(), r) }) 244 245 h = h.WithGroup("s") 246 r.AddAttrs(slog.Group("g", slog.Int("a", 1))) 247 wantAllocs(t, 0, func() { h.Handle(context.Background(), r) }) 248 } 249 250 func TestNeedsQuoting(t *testing.T) { 251 for _, test := range []struct { 252 in string 253 want bool 254 }{ 255 {"", true}, 256 {"ab", false}, 257 {"a=b", true}, 258 {`"ab"`, true}, 259 {"\a\b", true}, 260 {"a\tb", true}, 261 {"µåπ", false}, 262 {"a b", true}, 263 {"badutf8\xF6", true}, 264 } { 265 got := needsQuoting(test.in, false) 266 if got != test.want { 267 t.Errorf("%q: got %t, want %t", test.in, got, test.want) 268 } 269 } 270 }