123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- package logger
- import (
- "bytes"
- "errors"
- "fmt"
- "log/slog"
- "strings"
- "testing"
- "time"
- "github.com/imgproxy/imgproxy/v3/testutil"
- "github.com/stretchr/testify/suite"
- )
- type FormatterStructuredTestSuite struct {
- testutil.LazySuite
- buf testutil.LazyObj[*bytes.Buffer]
- config testutil.LazyObj[*Config]
- handler testutil.LazyObj[*Handler]
- logger testutil.LazyObj[*slog.Logger]
- }
- func (s *FormatterStructuredTestSuite) 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 = FormatStructured
- 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 *FormatterStructuredTestSuite) SetupSubTest() {
- s.ResetLazyObjects()
- }
- func (s *FormatterStructuredTestSuite) checkNextEntry(lvl, msg string) {
- str, err := s.buf().ReadString('\n')
- s.Require().NoError(err)
- const timeLen = 19 + 7 // +7 for key, separator, and quotes
- lvlLen := len(lvl) + 10 // +10 for key, separator, quotes, and spaces
- prefixLen := timeLen + lvlLen
- s.Require().GreaterOrEqual(len(str), prefixLen)
- timePart := str[:timeLen]
- levelPart := str[timeLen:prefixLen]
- now := time.Now()
- t, err := time.ParseInLocation(time.DateTime, timePart[6:timeLen-1], now.Location())
- s.Require().NoError(err)
- s.Require().WithinDuration(time.Now(), t, time.Minute)
- s.Equal(` level="`+lvl+`" `, levelPart)
- // Check the message
- s.Equal(msg+"\n", str[prefixLen:])
- }
- func (s *FormatterStructuredTestSuite) 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, fmt.Sprintf(`msg="%s"`, entry.message))
- }
- })
- }
- }
- func (s *FormatterStructuredTestSuite) 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("time", 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",
- strings.Join([]string{
- `msg="Test message"`,
- `string="value"`,
- `int=-100`,
- `uint64=200`,
- `float64=3.14`,
- `bool=true`,
- `time="1984-01-02 03:04:05"`,
- `duration="1m0s"`,
- `err="error value"`,
- `any="{Field1:value Field2:42}"`,
- }, " "),
- )
- }
- func (s *FormatterStructuredTestSuite) 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("time", 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",
- strings.Join([]string{
- `msg="Test message"`,
- `string="value"`,
- `int=-100`,
- `group1.uint64=200`,
- `group1.float64=3.14`,
- `group1.group2.group3.bool=true`,
- `group1.group2.group3.time="1984-01-02 03:04:05"`,
- `group1.group2.group4.duration="1m0s"`,
- `group1.group2.group4.any="{Field1:value Field2:42}"`,
- }, " "),
- )
- })
- 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("time", 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",
- strings.Join([]string{
- `msg="Test message"`,
- `string="value"`,
- `int=-100`,
- `group1.uint64=200`,
- `group1.float64=3.14`,
- `group1.group2.group3.bool=true`,
- `group1.group2.group3.time="1984-01-02 03:04:05"`,
- }, " "),
- )
- })
- }
- func (s *FormatterStructuredTestSuite) TestQuoting() {
- s.logger().Info(
- "Test message",
- "key", "value",
- "key with spaces", "value with spaces",
- `key"with"quotes`, `value"with"quotes`,
- "key\nwith\nnewlines", "value\nwith\nnewlines",
- slog.Group("group name", slog.String("key", "value")),
- )
- s.checkNextEntry(
- "INFO",
- strings.Join([]string{
- `msg="Test message"`,
- `key="value"`,
- `"key with spaces"="value with spaces"`,
- `"key\"with\"quotes"="value\"with\"quotes"`,
- `"key\nwith\nnewlines"="value\nwith\nnewlines"`,
- `"group name.key"="value"`,
- }, " "),
- )
- }
- func (s *FormatterStructuredTestSuite) 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",
- ),
- )
- s.checkNextEntry(
- "INFO",
- strings.Join([]string{
- `msg="Test message"`,
- `key1="value1"`,
- `key2="value2"`,
- `key3="value3"`,
- `group.stack="stack in group"`,
- `group.error="error in group"`,
- `group.source="source in group"`,
- `error="error value"`,
- `source="source value"`,
- `stack="stack value\nwith new lines"`,
- }, " "),
- )
- }
- func TestFormatterStructured(t *testing.T) {
- suite.Run(t, new(FormatterStructuredTestSuite))
- }
|