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  }