github.com/phsym/zeroslog@v0.1.1-0.20240224183259-0b7a5ea94339/zerolog_test.go (about)

     1  package zeroslog
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"log/slog"
    11  	"net"
    12  	"reflect"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"maps"
    19  
    20  	"testing/slogtest"
    21  
    22  	"github.com/rs/zerolog"
    23  )
    24  
    25  type stringer struct{}
    26  
    27  func (stringer) String() string {
    28  	return "stringer"
    29  }
    30  
    31  type marshaller struct{ err error }
    32  
    33  func (m marshaller) MarshalText() (text []byte, err error) {
    34  	return []byte("marshaller"), m.err
    35  }
    36  
    37  type jsoner struct {
    38  	foo string
    39  	err error
    40  }
    41  
    42  func (j jsoner) MarshalJSON() ([]byte, error) {
    43  	return []byte(fmt.Sprintf(`{"foo": %q}`, j.foo)), j.err
    44  }
    45  
    46  type unknown struct{ Foo string }
    47  
    48  var (
    49  	now = time.Now()
    50  
    51  	attrs = []slog.Attr{
    52  		slog.String("titi", "toto"),
    53  		slog.String("tata", "tutu"),
    54  		slog.Int("foo", 12),
    55  		slog.Uint64("bar", 42),
    56  		slog.Duration("dur", 3*time.Second),
    57  		slog.Bool("bool", true),
    58  		slog.Float64("float", 23.7),
    59  		slog.Time("thetime", now),
    60  		slog.Any("err", errors.New("yo")),
    61  		slog.Group("empty"),
    62  		slog.Group("group", slog.String("bar", "baz")),
    63  		slog.Any("ip", net.IP{192, 168, 1, 2}),
    64  		slog.Any("ipnet", net.IPNet{IP: net.IP{192, 168, 1, 0}, Mask: net.IPv4Mask(255, 255, 255, 0)}),
    65  		slog.Any("mac", net.HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}),
    66  		slog.Any("stringer", stringer{}),
    67  		slog.Any("marshaller", &marshaller{}),
    68  		slog.Any("marshaller-err", &marshaller{err: errors.New("failure")}),
    69  		slog.Any("unknown", unknown{Foo: "bar"}),
    70  		slog.Any("json", &jsoner{foo: "bar"}),
    71  		slog.Any("json-err", &jsoner{err: errors.New("failure")}),
    72  	}
    73  
    74  	exp = map[string]any{
    75  		"titi":           "toto",
    76  		"tata":           "tutu",
    77  		"foo":            12.0,
    78  		"bar":            42.0,
    79  		"dur":            3000.0,
    80  		"bool":           true,
    81  		"float":          23.7,
    82  		"thetime":        now.Format(time.RFC3339),
    83  		"err":            "yo",
    84  		"group":          map[string]any{"bar": "baz"},
    85  		"ip":             "192.168.1.2",
    86  		"ipnet":          "192.168.1.0/24",
    87  		"mac":            "00:00:5e:00:53:01",
    88  		"stringer":       "stringer",
    89  		"marshaller":     "marshaller",
    90  		"marshaller-err": "!ERROR:failure",
    91  		"unknown":        map[string]any{"Foo": "bar"},
    92  		"json":           map[string]any{"foo": "bar"},
    93  		"json-err":       "!ERROR:failure",
    94  	}
    95  
    96  	levels = []struct {
    97  		zlvl zerolog.Level
    98  		slvl slog.Level
    99  	}{
   100  		{zerolog.TraceLevel, slog.LevelDebug - 1},
   101  		{zerolog.DebugLevel, slog.LevelDebug},
   102  		{zerolog.InfoLevel, slog.LevelInfo},
   103  		{zerolog.WarnLevel, slog.LevelWarn},
   104  		{zerolog.WarnLevel, slog.LevelWarn + 1},
   105  		{zerolog.WarnLevel, slog.LevelError - 1},
   106  		{zerolog.ErrorLevel, slog.LevelError},
   107  		{zerolog.ErrorLevel, slog.LevelError + 1},
   108  	}
   109  )
   110  
   111  func TestZerolog_Levels(t *testing.T) {
   112  	out := bytes.Buffer{}
   113  	for _, lvl := range levels {
   114  		t.Run(lvl.slvl.String(), func(t *testing.T) {
   115  			hdl := NewJsonHandler(&out, &HandlerOptions{Level: lvl.slvl})
   116  			for _, l := range levels {
   117  				enabled := l.slvl >= lvl.slvl
   118  				if hdl.Enabled(nil, l.slvl) != enabled {
   119  					t.Fatalf("Level %s enablement status unexpected", l.slvl)
   120  				}
   121  				hdl.Handle(nil, slog.NewRecord(time.Now(), l.slvl, "foobar", 0))
   122  				if enabled {
   123  					m := map[string]any{}
   124  					if err := json.NewDecoder(&out).Decode(&m); err != nil {
   125  						t.Fatalf("Failed to json decode log output: %s", err.Error())
   126  					}
   127  					if m[zerolog.LevelFieldName] != l.zlvl.String() {
   128  						t.Fatalf("Unexpected value for field %s. Got %s but expected %s", zerolog.LevelFieldName, m[zerolog.LevelFieldName], l.zlvl.String())
   129  					}
   130  				}
   131  			}
   132  
   133  		})
   134  	}
   135  }
   136  
   137  func TestZerolog_Levels_NoOption(t *testing.T) {
   138  	out := bytes.Buffer{}
   139  	for _, lvl := range levels {
   140  		t.Run(lvl.slvl.String(), func(t *testing.T) {
   141  			hdl := NewHandler(zerolog.New(&out).Level(lvl.zlvl), nil)
   142  			for _, l := range levels {
   143  				enabled := l.zlvl >= lvl.zlvl
   144  				if hdl.Enabled(nil, l.slvl) != enabled {
   145  					t.Fatalf("Level %s enablement status unexpected", l.slvl)
   146  				}
   147  				hdl.Handle(nil, slog.NewRecord(time.Now(), l.slvl, "foobar", 0))
   148  				m := map[string]any{}
   149  				err := json.NewDecoder(&out).Decode(&m)
   150  				if enabled {
   151  					if err != nil {
   152  						t.Fatalf("Failed to json decode log output: %s", err.Error())
   153  					}
   154  					if m[zerolog.LevelFieldName] != l.zlvl.String() {
   155  						t.Fatalf("Unexpected value for field %s. Got %s but expected %s", zerolog.LevelFieldName, m[zerolog.LevelFieldName], l.zlvl.String())
   156  					}
   157  				} else {
   158  					if !errors.Is(err, io.EOF) {
   159  						t.Fatalf("Expected io.EOF error but got %s", err)
   160  					}
   161  				}
   162  			}
   163  
   164  		})
   165  	}
   166  }
   167  
   168  func TestZerolog_NoGroup(t *testing.T) {
   169  	out := bytes.Buffer{}
   170  	hdl := NewJsonHandler(&out, nil).
   171  		WithAttrs([]slog.Attr{slog.String("attr", "the attr")})
   172  
   173  	if !hdl.Enabled(nil, slog.LevelError) {
   174  		t.Errorf("Level %s must be enabled", slog.LevelError)
   175  	}
   176  	if hdl.Enabled(nil, slog.LevelDebug) {
   177  		t.Errorf("Level %s must be disabled", slog.LevelDebug)
   178  	}
   179  
   180  	rec := slog.NewRecord(now, slog.LevelError, "foobar", 0)
   181  	rec.AddAttrs(attrs...)
   182  	hdl.Handle(nil, rec)
   183  
   184  	expected := maps.Clone(exp)
   185  	expected[zerolog.LevelFieldName] = zerolog.LevelErrorValue
   186  	expected[zerolog.MessageFieldName] = "foobar"
   187  	expected[zerolog.TimestampFieldName] = now.Format(time.RFC3339)
   188  	expected["attr"] = "the attr"
   189  
   190  	m := map[string]any{}
   191  	if err := json.NewDecoder(&out).Decode(&m); err != nil {
   192  		t.Fatalf("Failed to json decode log output: %s", err.Error())
   193  	}
   194  	if !reflect.DeepEqual(expected, m) {
   195  		t.Fatalf("Unexpected fields. Got %v, expected %v", m, expected)
   196  	}
   197  }
   198  
   199  func TestZerolog_Group(t *testing.T) {
   200  	out := bytes.Buffer{}
   201  	hdl := NewJsonHandler(&out, nil).
   202  		WithAttrs([]slog.Attr{slog.String("attr", "the attr")}).
   203  		WithGroup("testgroup").
   204  		WithAttrs([]slog.Attr{slog.String("attr", "the attr")}).
   205  		WithGroup("subgroup")
   206  
   207  	if !hdl.Enabled(nil, slog.LevelError) {
   208  		t.Errorf("Level %s must be enabled", slog.LevelError)
   209  	}
   210  	if hdl.Enabled(nil, slog.LevelDebug) {
   211  		t.Errorf("Level %s must be disabled", slog.LevelDebug)
   212  	}
   213  
   214  	rec := slog.NewRecord(now, slog.LevelWarn, "foobar", 0)
   215  	rec.AddAttrs(attrs...)
   216  	hdl.Handle(nil, rec)
   217  
   218  	expected := map[string]any{
   219  		zerolog.LevelFieldName:     zerolog.LevelWarnValue,
   220  		zerolog.MessageFieldName:   "foobar",
   221  		zerolog.TimestampFieldName: now.Format(time.RFC3339),
   222  		"attr":                     "the attr",
   223  		"testgroup": map[string]any{
   224  			"attr":     "the attr",
   225  			"subgroup": maps.Clone(exp),
   226  		},
   227  	}
   228  
   229  	m := map[string]any{}
   230  	if err := json.NewDecoder(&out).Decode(&m); err != nil {
   231  		t.Fatalf("Failed to json decode log output: %s", err.Error())
   232  	}
   233  	if !reflect.DeepEqual(expected, m) {
   234  		t.Fatalf("Unexpected fields. Got %v, expected %v", m, expected)
   235  	}
   236  }
   237  
   238  func TestZerolog_AddSource(t *testing.T) {
   239  	out := bytes.Buffer{}
   240  	hdl := NewJsonHandler(&out, &HandlerOptions{AddSource: true})
   241  	pc, file, line, _ := runtime.Caller(0)
   242  	hdl.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "foobar", pc))
   243  	m := map[string]any{}
   244  	if err := json.NewDecoder(&out).Decode(&m); err != nil {
   245  		t.Fatalf("Failed to json decode log output: %s", err.Error())
   246  	}
   247  	if m[zerolog.CallerFieldName].(string) != fmt.Sprintf("%s:%d", file, line) {
   248  		t.Fatalf("Unexpected field %s: %s", zerolog.CallerFieldName, m[zerolog.CallerFieldName].(string))
   249  	}
   250  }
   251  
   252  func TestZerolog_ConsoleHandler(t *testing.T) {
   253  	out := bytes.Buffer{}
   254  	hdl := NewConsoleHandler(&out, nil)
   255  	hdl.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "foobar", 0))
   256  	txt := out.String()
   257  	if !strings.Contains(txt, "foobar") || !strings.Contains(txt, "INF") {
   258  		t.Errorf("Unexpected console output %q", txt)
   259  	}
   260  }
   261  
   262  // TestHandler uses slogtest.TestHandler from stdlib to validate
   263  // the zerolog handler implementation.
   264  func TestHandler(t *testing.T) {
   265  	out := bytes.Buffer{}
   266  	dec := json.NewDecoder(&out)
   267  	hdl := NewJsonHandler(&out, &HandlerOptions{Level: slog.LevelDebug})
   268  	err := slogtest.TestHandler(hdl, func() []map[string]any {
   269  		results := []map[string]any{}
   270  		m := map[string]any{}
   271  		for dec.Decode(&m) != io.EOF {
   272  			results = append(results, m)
   273  			m = map[string]any{}
   274  		}
   275  		return results
   276  	})
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  }