1
0

formatter_json_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. package logger
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "log/slog"
  7. "strings"
  8. "testing"
  9. "time"
  10. "github.com/imgproxy/imgproxy/v3/testutil"
  11. "github.com/stretchr/testify/suite"
  12. )
  13. type FormatterJsonTestSuite struct {
  14. testutil.LazySuite
  15. buf testutil.LazyObj[*bytes.Buffer]
  16. config testutil.LazyObj[*Config]
  17. handler testutil.LazyObj[*Handler]
  18. logger testutil.LazyObj[*slog.Logger]
  19. }
  20. func (s *FormatterJsonTestSuite) SetupTest() {
  21. s.buf, _ = testutil.NewLazySuiteObj(
  22. s,
  23. func() (*bytes.Buffer, error) {
  24. return new(bytes.Buffer), nil
  25. },
  26. )
  27. s.config, _ = testutil.NewLazySuiteObj(
  28. s,
  29. func() (*Config, error) {
  30. cfg := NewDefaultConfig()
  31. cfg.Format = FormatJSON
  32. return &cfg, nil
  33. },
  34. )
  35. s.handler, _ = testutil.NewLazySuiteObj(
  36. s,
  37. func() (*Handler, error) {
  38. return NewHandler(s.buf(), s.config()), nil
  39. },
  40. )
  41. s.logger, _ = testutil.NewLazySuiteObj(
  42. s,
  43. func() (*slog.Logger, error) {
  44. return slog.New(s.handler()), nil
  45. },
  46. )
  47. }
  48. func (s *FormatterJsonTestSuite) SetupSubTest() {
  49. s.ResetLazyObjects()
  50. }
  51. func (s *FormatterJsonTestSuite) checkNextEntry(lvl string, msg map[string]any) {
  52. str, err := s.buf().ReadString('\n')
  53. s.Require().NoError(err)
  54. var parsed map[string]any
  55. err = json.Unmarshal([]byte(str), &parsed)
  56. s.Require().NoError(err)
  57. s.Require().IsType("", parsed["time"])
  58. s.Require().IsType("", parsed["level"])
  59. now := time.Now()
  60. t, err := time.ParseInLocation(time.RFC3339, parsed["time"].(string), now.Location())
  61. s.Require().NoError(err)
  62. s.Require().WithinDuration(time.Now(), t, time.Minute)
  63. s.Equal(lvl, parsed["level"].(string))
  64. // Remove time and level as they are not included in `msg`
  65. delete(parsed, "time")
  66. delete(parsed, "level")
  67. // Check the message
  68. s.Equal(msg, parsed)
  69. }
  70. func (s *FormatterJsonTestSuite) TestLevel() {
  71. type testEntry struct {
  72. level slog.Level
  73. levelName string
  74. message string
  75. }
  76. testEntries := []testEntry{
  77. {level: slog.LevelDebug, levelName: "DEBUG", message: "Debug message"},
  78. {level: slog.LevelInfo, levelName: "INFO", message: "Info message"},
  79. {level: slog.LevelWarn, levelName: "WARNING", message: "Warning message"},
  80. {level: slog.LevelError, levelName: "ERROR", message: "Error message"},
  81. {level: LevelCritical, levelName: "CRITICAL", message: "Critical message"},
  82. }
  83. testCases := []struct {
  84. level slog.Level
  85. entries []testEntry
  86. }{
  87. {level: slog.LevelDebug, entries: testEntries},
  88. {level: slog.LevelInfo, entries: testEntries[1:]},
  89. {level: slog.LevelWarn, entries: testEntries[2:]},
  90. {level: slog.LevelError, entries: testEntries[3:]},
  91. {level: LevelCritical, entries: testEntries[4:]},
  92. }
  93. for _, tc := range testCases {
  94. s.Run(tc.level.String(), func() {
  95. s.config().Level = tc.level
  96. for _, entry := range testEntries {
  97. s.logger().Log(s.T().Context(), entry.level, entry.message)
  98. }
  99. for _, entry := range tc.entries {
  100. s.checkNextEntry(entry.levelName, map[string]any{
  101. "msg": entry.message,
  102. })
  103. }
  104. })
  105. }
  106. }
  107. func (s *FormatterJsonTestSuite) TestAttributes() {
  108. s.logger().Info(
  109. "Test message",
  110. slog.String("string", "value"),
  111. slog.Int("int", -100),
  112. slog.Uint64("uint64", 200),
  113. slog.Float64("float64", 3.14),
  114. slog.Bool("bool", true),
  115. slog.Time("timearg", time.Date(1984, 1, 2, 3, 4, 5, 6, time.UTC)),
  116. slog.Duration("duration", time.Minute),
  117. slog.Any("err", errors.New("error value")),
  118. slog.Any("any", struct {
  119. Field1 string
  120. Field2 int
  121. }{"value", 42}),
  122. )
  123. s.checkNextEntry(
  124. "INFO",
  125. map[string]any{
  126. "msg": "Test message",
  127. "string": "value",
  128. "int": -100.0,
  129. "uint64": 200.0,
  130. "float64": 3.14,
  131. "bool": true,
  132. "timearg": "1984-01-02T03:04:05Z",
  133. "duration": float64(time.Minute),
  134. "err": "error value",
  135. "any": map[string]any{"Field1": "value", "Field2": 42.0},
  136. },
  137. )
  138. }
  139. func (s *FormatterJsonTestSuite) TestGroups() {
  140. s.Run("LastGroupNotEmpty", func() {
  141. s.logger().
  142. With(
  143. slog.String("string", "value"),
  144. slog.Int("int", -100),
  145. ).
  146. WithGroup("group1").
  147. With(
  148. slog.Uint64("uint64", 200),
  149. slog.Float64("float64", 3.14),
  150. ).
  151. WithGroup("group2").
  152. With(slog.Group("group3",
  153. slog.Bool("bool", true),
  154. slog.Time("timearg", time.Date(1984, 1, 2, 3, 4, 5, 6, time.UTC)),
  155. )).
  156. With(slog.Group("empty_group")).
  157. WithGroup("group4").
  158. Info(
  159. "Test message",
  160. slog.Duration("duration", time.Minute),
  161. slog.Any("any", struct {
  162. Field1 string
  163. Field2 int
  164. }{"value", 42}),
  165. )
  166. s.checkNextEntry(
  167. "INFO",
  168. map[string]any{
  169. "msg": "Test message",
  170. "string": "value",
  171. "int": -100.0,
  172. "group1": map[string]any{
  173. "uint64": 200.0,
  174. "float64": 3.14,
  175. "group2": map[string]any{
  176. "group3": map[string]any{
  177. "bool": true,
  178. "timearg": "1984-01-02T03:04:05Z",
  179. },
  180. "group4": map[string]any{
  181. "duration": float64(time.Minute),
  182. "any": map[string]any{"Field1": "value", "Field2": 42.0},
  183. },
  184. },
  185. },
  186. },
  187. )
  188. })
  189. s.Run("LastGroupsEmpty", func() {
  190. s.logger().
  191. With(
  192. slog.String("string", "value"),
  193. slog.Int("int", -100),
  194. ).
  195. WithGroup("group1").
  196. With(
  197. slog.Uint64("uint64", 200),
  198. slog.Float64("float64", 3.14),
  199. ).
  200. WithGroup("group2").
  201. With(slog.Group("group3",
  202. slog.Bool("bool", true),
  203. slog.Time("timearg", time.Date(1984, 1, 2, 3, 4, 5, 6, time.UTC)),
  204. )).
  205. With(slog.Group("empty_group")).
  206. WithGroup("group4").
  207. WithGroup("group5").
  208. Info("Test message")
  209. s.checkNextEntry(
  210. "INFO",
  211. map[string]any{
  212. "msg": "Test message",
  213. "string": "value",
  214. "int": -100.0,
  215. "group1": map[string]any{
  216. "uint64": 200.0,
  217. "float64": 3.14,
  218. "group2": map[string]any{
  219. "group3": map[string]any{
  220. "bool": true,
  221. "timearg": "1984-01-02T03:04:05Z",
  222. },
  223. },
  224. },
  225. },
  226. )
  227. })
  228. }
  229. func (s *FormatterJsonTestSuite) TestEscaping() {
  230. s.logger().Info(
  231. "Test message",
  232. "key", "value",
  233. "key 1", "value 1",
  234. `"key"`, `"value"`,
  235. `<key>`, `<value>`,
  236. "\nkey\n", "\nvalue\n",
  237. slog.Group("group name", slog.String("key", "value")),
  238. )
  239. s.checkNextEntry(
  240. "INFO",
  241. map[string]any{
  242. "msg": "Test message",
  243. "key": "value",
  244. "key 1": "value 1",
  245. `"key"`: `"value"`,
  246. `<key>`: `<value>`,
  247. "\nkey\n": "\nvalue\n",
  248. "group name": map[string]any{"key": "value"},
  249. },
  250. )
  251. }
  252. func (s *FormatterJsonTestSuite) TestSpecialFields() {
  253. s.logger().Info(
  254. "Test message",
  255. "stack", "stack value\nwith new lines",
  256. "key1", "value1",
  257. "error", errors.New("error value"),
  258. "key2", "value2",
  259. "source", "source value",
  260. "key3", "value3",
  261. slog.Group(
  262. "group",
  263. "stack", "stack in group",
  264. "error", "error in group",
  265. "source", "source in group",
  266. ),
  267. )
  268. expectedJSON := strings.Join([]string{
  269. `"msg":"Test message",`,
  270. `"key1":"value1",`,
  271. `"key2":"value2",`,
  272. `"key3":"value3",`,
  273. `"group":{`,
  274. `"stack":"stack in group",`,
  275. `"error":"error in group",`,
  276. `"source":"source in group"`,
  277. `},`,
  278. `"error":"error value",`,
  279. `"source":"source value",`,
  280. `"stack":"stack value\nwith new lines"`,
  281. "}\n",
  282. }, "")
  283. s.Require().Contains(s.buf().String(), expectedJSON)
  284. }
  285. func TestFormatterJson(t *testing.T) {
  286. suite.Run(t, new(FormatterJsonTestSuite))
  287. }