123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- package logger
- import (
- "bytes"
- "encoding/json"
- "errors"
- "log/slog"
- "strings"
- "testing"
- "time"
- "github.com/imgproxy/imgproxy/v3/testutil"
- "github.com/stretchr/testify/suite"
- )
- type FormatterJsonTestSuite struct {
- testutil.LazySuite
- buf testutil.LazyObj[*bytes.Buffer]
- config testutil.LazyObj[*Config]
- handler testutil.LazyObj[*Handler]
- logger testutil.LazyObj[*slog.Logger]
- }
- func (s *FormatterJsonTestSuite) SetupTest() {
- s.buf, _ = testutil.NewLazySuiteObj(
- s,
- func() (*bytes.Buffer, error) {
- return new(bytes.Buffer), nil
- },
- )
- s.config, _ = testutil.NewLazySuiteObj(
- s,
- func() (*Config, error) {
- cfg := NewDefaultConfig()
- cfg.Format = FormatJSON
- return &cfg, nil
- },
- )
- s.handler, _ = testutil.NewLazySuiteObj(
- s,
- func() (*Handler, error) {
- return NewHandler(s.buf(), s.config()), nil
- },
- )
- s.logger, _ = testutil.NewLazySuiteObj(
- s,
- func() (*slog.Logger, error) {
- return slog.New(s.handler()), nil
- },
- )
- }
- func (s *FormatterJsonTestSuite) SetupSubTest() {
- s.ResetLazyObjects()
- }
- func (s *FormatterJsonTestSuite) checkNextEntry(lvl string, msg map[string]any) {
- str, err := s.buf().ReadString('\n')
- s.Require().NoError(err)
- var parsed map[string]any
- err = json.Unmarshal([]byte(str), &parsed)
- s.Require().NoError(err)
- s.Require().IsType("", parsed["time"])
- s.Require().IsType("", parsed["level"])
- now := time.Now()
- t, err := time.ParseInLocation(time.RFC3339, parsed["time"].(string), now.Location())
- s.Require().NoError(err)
- s.Require().WithinDuration(time.Now(), t, time.Minute)
- s.Equal(lvl, parsed["level"].(string))
- // Remove time and level as they are not included in `msg`
- delete(parsed, "time")
- delete(parsed, "level")
- // Check the message
- s.Equal(msg, parsed)
- }
- func (s *FormatterJsonTestSuite) TestLevel() {
- type testEntry struct {
- level slog.Level
- levelName string
- message string
- }
- testEntries := []testEntry{
- {level: slog.LevelDebug, levelName: "DEBUG", message: "Debug message"},
- {level: slog.LevelInfo, levelName: "INFO", message: "Info message"},
- {level: slog.LevelWarn, levelName: "WARNING", message: "Warning message"},
- {level: slog.LevelError, levelName: "ERROR", message: "Error message"},
- {level: LevelCritical, levelName: "CRITICAL", message: "Critical message"},
- }
- testCases := []struct {
- level slog.Level
- entries []testEntry
- }{
- {level: slog.LevelDebug, entries: testEntries},
- {level: slog.LevelInfo, entries: testEntries[1:]},
- {level: slog.LevelWarn, entries: testEntries[2:]},
- {level: slog.LevelError, entries: testEntries[3:]},
- {level: LevelCritical, entries: testEntries[4:]},
- }
- for _, tc := range testCases {
- s.Run(tc.level.String(), func() {
- s.config().Level = tc.level
- for _, entry := range testEntries {
- s.logger().Log(s.T().Context(), entry.level, entry.message)
- }
- for _, entry := range tc.entries {
- s.checkNextEntry(entry.levelName, map[string]any{
- "msg": entry.message,
- })
- }
- })
- }
- }
- func (s *FormatterJsonTestSuite) TestAttributes() {
- s.logger().Info(
- "Test message",
- slog.String("string", "value"),
- slog.Int("int", -100),
- slog.Uint64("uint64", 200),
- slog.Float64("float64", 3.14),
- slog.Bool("bool", true),
- slog.Time("timearg", time.Date(1984, 1, 2, 3, 4, 5, 6, time.UTC)),
- slog.Duration("duration", time.Minute),
- slog.Any("err", errors.New("error value")),
- slog.Any("any", struct {
- Field1 string
- Field2 int
- }{"value", 42}),
- )
- s.checkNextEntry(
- "INFO",
- map[string]any{
- "msg": "Test message",
- "string": "value",
- "int": -100.0,
- "uint64": 200.0,
- "float64": 3.14,
- "bool": true,
- "timearg": "1984-01-02T03:04:05Z",
- "duration": float64(time.Minute),
- "err": "error value",
- "any": map[string]any{"Field1": "value", "Field2": 42.0},
- },
- )
- }
- func (s *FormatterJsonTestSuite) TestGroups() {
- s.Run("LastGroupNotEmpty", func() {
- s.logger().
- With(
- slog.String("string", "value"),
- slog.Int("int", -100),
- ).
- WithGroup("group1").
- With(
- slog.Uint64("uint64", 200),
- slog.Float64("float64", 3.14),
- ).
- WithGroup("group2").
- With(slog.Group("group3",
- slog.Bool("bool", true),
- slog.Time("timearg", time.Date(1984, 1, 2, 3, 4, 5, 6, time.UTC)),
- )).
- With(slog.Group("empty_group")).
- WithGroup("group4").
- Info(
- "Test message",
- slog.Duration("duration", time.Minute),
- slog.Any("any", struct {
- Field1 string
- Field2 int
- }{"value", 42}),
- )
- s.checkNextEntry(
- "INFO",
- map[string]any{
- "msg": "Test message",
- "string": "value",
- "int": -100.0,
- "group1": map[string]any{
- "uint64": 200.0,
- "float64": 3.14,
- "group2": map[string]any{
- "group3": map[string]any{
- "bool": true,
- "timearg": "1984-01-02T03:04:05Z",
- },
- "group4": map[string]any{
- "duration": float64(time.Minute),
- "any": map[string]any{"Field1": "value", "Field2": 42.0},
- },
- },
- },
- },
- )
- })
- s.Run("LastGroupsEmpty", func() {
- s.logger().
- With(
- slog.String("string", "value"),
- slog.Int("int", -100),
- ).
- WithGroup("group1").
- With(
- slog.Uint64("uint64", 200),
- slog.Float64("float64", 3.14),
- ).
- WithGroup("group2").
- With(slog.Group("group3",
- slog.Bool("bool", true),
- slog.Time("timearg", time.Date(1984, 1, 2, 3, 4, 5, 6, time.UTC)),
- )).
- With(slog.Group("empty_group")).
- WithGroup("group4").
- WithGroup("group5").
- Info("Test message")
- s.checkNextEntry(
- "INFO",
- map[string]any{
- "msg": "Test message",
- "string": "value",
- "int": -100.0,
- "group1": map[string]any{
- "uint64": 200.0,
- "float64": 3.14,
- "group2": map[string]any{
- "group3": map[string]any{
- "bool": true,
- "timearg": "1984-01-02T03:04:05Z",
- },
- },
- },
- },
- )
- })
- }
- func (s *FormatterJsonTestSuite) TestEscaping() {
- s.logger().Info(
- "Test message",
- "key", "value",
- "key 1", "value 1",
- `"key"`, `"value"`,
- `<key>`, `<value>`,
- "\nkey\n", "\nvalue\n",
- slog.Group("group name", slog.String("key", "value")),
- )
- s.checkNextEntry(
- "INFO",
- map[string]any{
- "msg": "Test message",
- "key": "value",
- "key 1": "value 1",
- `"key"`: `"value"`,
- `<key>`: `<value>`,
- "\nkey\n": "\nvalue\n",
- "group name": map[string]any{"key": "value"},
- },
- )
- }
- func (s *FormatterJsonTestSuite) TestSpecialFields() {
- s.logger().Info(
- "Test message",
- "stack", "stack value\nwith new lines",
- "key1", "value1",
- "error", errors.New("error value"),
- "key2", "value2",
- "source", "source value",
- "key3", "value3",
- slog.Group(
- "group",
- "stack", "stack in group",
- "error", "error in group",
- "source", "source in group",
- ),
- )
- expectedJSON := strings.Join([]string{
- `"msg":"Test message",`,
- `"key1":"value1",`,
- `"key2":"value2",`,
- `"key3":"value3",`,
- `"group":{`,
- `"stack":"stack in group",`,
- `"error":"error in group",`,
- `"source":"source in group"`,
- `},`,
- `"error":"error value",`,
- `"source":"source value",`,
- `"stack":"stack value\nwith new lines"`,
- "}\n",
- }, "")
- s.Require().Contains(s.buf().String(), expectedJSON)
- }
- func TestFormatterJson(t *testing.T) {
- suite.Run(t, new(FormatterJsonTestSuite))
- }
|