log_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package log
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "github.com/stretchr/testify/require"
  6. "io"
  7. "log"
  8. "os"
  9. "path/filepath"
  10. "testing"
  11. "time"
  12. )
  13. func TestMain(m *testing.M) {
  14. exitCode := m.Run()
  15. resetState()
  16. SetLevel(ErrorLevel) // For other modules!
  17. os.Exit(exitCode)
  18. }
  19. func TestLog_TagContextFieldFields(t *testing.T) {
  20. t.Cleanup(resetState)
  21. v := &fakeVisitor{
  22. UserID: "u_abc",
  23. IP: "1.2.3.4",
  24. }
  25. err := &fakeError{
  26. Code: 123,
  27. Message: "some error",
  28. }
  29. var out bytes.Buffer
  30. SetOutput(&out)
  31. SetFormat(JSONFormat)
  32. SetLevelOverride("tag", "stripe", DebugLevel)
  33. SetLevelOverride("number", "5", DebugLevel)
  34. Tag("mytag").
  35. Field("field2", 123).
  36. Field("field1", "value1").
  37. Time(time.Unix(123, 999000000).UTC()).
  38. Info("hi there %s", "phil")
  39. Tag("not-stripe").
  40. Debug("this message will not appear")
  41. With(v).
  42. Fields(Context{
  43. "stripe_customer_id": "acct_123",
  44. "stripe_subscription_id": "sub_123",
  45. }).
  46. Tag("stripe").
  47. Err(err).
  48. Time(time.Unix(456, 123000000).UTC()).
  49. Debug("Subscription status %s", "active")
  50. Field("number", 5).
  51. Time(time.Unix(777, 001000000).UTC()).
  52. Debug("The number 5 is an int, but the level override is a string")
  53. expected := `{"time":"1970-01-01T00:02:03.999Z","level":"INFO","message":"hi there phil","field1":"value1","field2":123,"tag":"mytag"}
  54. {"time":"1970-01-01T00:07:36.123Z","level":"DEBUG","message":"Subscription status active","error":"some error","error_code":123,"stripe_customer_id":"acct_123","stripe_subscription_id":"sub_123","tag":"stripe","user_id":"u_abc","visitor_ip":"1.2.3.4"}
  55. {"time":"1970-01-01T00:12:57Z","level":"DEBUG","message":"The number 5 is an int, but the level override is a string","number":5}
  56. `
  57. require.Equal(t, expected, out.String())
  58. }
  59. func TestLog_NoAllocIfNotPrinted(t *testing.T) {
  60. t.Cleanup(resetState)
  61. v := &fakeVisitor{
  62. UserID: "u_abc",
  63. IP: "1.2.3.4",
  64. }
  65. var out bytes.Buffer
  66. SetOutput(&out)
  67. SetFormat(JSONFormat)
  68. // Do not log, do not call contexters (because global level is INFO)
  69. v.contextCalled = false
  70. ev := With(v)
  71. ev.Debug("some message")
  72. require.False(t, v.contextCalled)
  73. require.Equal(t, "", ev.Timestamp)
  74. require.Equal(t, Level(0), ev.Level)
  75. require.Equal(t, "", ev.Message)
  76. require.Nil(t, ev.fields)
  77. // Logged because info level, contexters called
  78. v.contextCalled = false
  79. ev = With(v).Time(time.Unix(1111, 0).UTC())
  80. ev.Info("some message")
  81. require.True(t, v.contextCalled)
  82. require.NotNil(t, ev.fields)
  83. require.Equal(t, "1.2.3.4", ev.fields["visitor_ip"])
  84. // Not logged, but contexters called, because overrides exist
  85. SetLevel(DebugLevel)
  86. SetLevelOverride("tag", "overridetag", TraceLevel)
  87. v.contextCalled = false
  88. ev = Tag("sometag").Field("field", "value").With(v).Time(time.Unix(123, 0).UTC())
  89. ev.Trace("some debug message")
  90. require.True(t, v.contextCalled) // If there are overrides, we must call the context to determine the filter fields
  91. require.Equal(t, "", ev.Timestamp)
  92. require.Equal(t, Level(0), ev.Level)
  93. require.Equal(t, "", ev.Message)
  94. require.Equal(t, 4, len(ev.fields))
  95. require.Equal(t, "value", ev.fields["field"])
  96. require.Equal(t, "sometag", ev.fields["tag"])
  97. // Logged because of override tag, and contexters called
  98. v.contextCalled = false
  99. ev = Tag("overridetag").Field("field", "value").With(v).Time(time.Unix(123, 0).UTC())
  100. ev.Trace("some trace message")
  101. require.True(t, v.contextCalled)
  102. require.Equal(t, "1970-01-01T00:02:03Z", ev.Timestamp)
  103. require.Equal(t, TraceLevel, ev.Level)
  104. require.Equal(t, "some trace message", ev.Message)
  105. // Logged because of field override, and contexters called
  106. ResetLevelOverrides()
  107. SetLevelOverride("visitor_ip", "1.2.3.4", TraceLevel)
  108. v.contextCalled = false
  109. ev = With(v).Time(time.Unix(124, 0).UTC())
  110. ev.Trace("some trace message with override")
  111. require.True(t, v.contextCalled)
  112. require.Equal(t, "1970-01-01T00:02:04Z", ev.Timestamp)
  113. require.Equal(t, TraceLevel, ev.Level)
  114. require.Equal(t, "some trace message with override", ev.Message)
  115. expected := `{"time":"1970-01-01T00:18:31Z","level":"INFO","message":"some message","user_id":"u_abc","visitor_ip":"1.2.3.4"}
  116. {"time":"1970-01-01T00:02:03Z","level":"TRACE","message":"some trace message","field":"value","tag":"overridetag","user_id":"u_abc","visitor_ip":"1.2.3.4"}
  117. {"time":"1970-01-01T00:02:04Z","level":"TRACE","message":"some trace message with override","user_id":"u_abc","visitor_ip":"1.2.3.4"}
  118. `
  119. require.Equal(t, expected, out.String())
  120. }
  121. func TestLog_Timing(t *testing.T) {
  122. t.Cleanup(resetState)
  123. var out bytes.Buffer
  124. SetOutput(&out)
  125. SetFormat(JSONFormat)
  126. Timing(func() { time.Sleep(300 * time.Millisecond) }).
  127. Time(time.Unix(12, 0).UTC()).
  128. Info("A thing that takes a while")
  129. var ev struct {
  130. TimeTakenMs int64 `json:"time_taken_ms"`
  131. }
  132. require.Nil(t, json.Unmarshal(out.Bytes(), &ev))
  133. require.True(t, ev.TimeTakenMs >= 300)
  134. require.Contains(t, out.String(), `{"time":"1970-01-01T00:00:12Z","level":"INFO","message":"A thing that takes a while","time_taken_ms":`)
  135. }
  136. func TestLog_LevelOverrideAny(t *testing.T) {
  137. t.Cleanup(resetState)
  138. var out bytes.Buffer
  139. SetOutput(&out)
  140. SetFormat(JSONFormat)
  141. SetLevelOverride("this_one", "", DebugLevel)
  142. SetLevelOverride("time_taken_ms", "", TraceLevel)
  143. Time(time.Unix(11, 0).UTC()).Field("this_one", "11").Debug("this is logged")
  144. Time(time.Unix(12, 0).UTC()).Field("not_this", "11").Debug("this is not logged")
  145. Time(time.Unix(13, 0).UTC()).Field("this_too", "11").Info("this is also logged")
  146. Time(time.Unix(14, 0).UTC()).Field("time_taken_ms", 0).Info("this is also logged")
  147. expected := `{"time":"1970-01-01T00:00:11Z","level":"DEBUG","message":"this is logged","this_one":"11"}
  148. {"time":"1970-01-01T00:00:13Z","level":"INFO","message":"this is also logged","this_too":"11"}
  149. {"time":"1970-01-01T00:00:14Z","level":"INFO","message":"this is also logged","time_taken_ms":0}
  150. `
  151. require.Equal(t, expected, out.String())
  152. require.False(t, IsFile())
  153. require.Equal(t, "", File())
  154. }
  155. func TestLog_UsingStdLogger_JSON(t *testing.T) {
  156. t.Cleanup(resetState)
  157. var out bytes.Buffer
  158. SetOutput(&out)
  159. SetFormat(JSONFormat)
  160. log.Println("Some other library is using the standard Go logger")
  161. require.Contains(t, out.String(), `,"level":"INFO","message":"Some other library is using the standard Go logger","tag":"stdlog"}`+"\n")
  162. }
  163. func TestLog_UsingStdLogger_Text(t *testing.T) {
  164. t.Cleanup(resetState)
  165. var out bytes.Buffer
  166. SetOutput(&out)
  167. log.Println("Some other library is using the standard Go logger")
  168. require.Contains(t, out.String(), `Some other library is using the standard Go logger`+"\n")
  169. require.NotContains(t, out.String(), `{`)
  170. }
  171. func TestLog_File(t *testing.T) {
  172. t.Cleanup(resetState)
  173. logfile := filepath.Join(t.TempDir(), "ntfy.log")
  174. f, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY, 0600)
  175. require.Nil(t, err)
  176. SetOutput(f)
  177. SetFormat(JSONFormat)
  178. require.True(t, IsFile())
  179. require.Equal(t, logfile, File())
  180. Time(time.Unix(11, 0).UTC()).Field("this_one", "11").Info("this is logged")
  181. require.Nil(t, f.Close())
  182. f, err = os.Open(logfile)
  183. require.Nil(t, err)
  184. contents, err := io.ReadAll(f)
  185. require.Nil(t, err)
  186. require.Equal(t, `{"time":"1970-01-01T00:00:11Z","level":"INFO","message":"this is logged","this_one":"11"}`+"\n", string(contents))
  187. }
  188. type fakeError struct {
  189. Code int
  190. Message string
  191. }
  192. func (e fakeError) Error() string {
  193. return e.Message
  194. }
  195. func (e fakeError) Context() Context {
  196. return Context{
  197. "error": e.Message,
  198. "error_code": e.Code,
  199. }
  200. }
  201. type fakeVisitor struct {
  202. UserID string
  203. IP string
  204. contextCalled bool
  205. }
  206. func (v *fakeVisitor) Context() Context {
  207. v.contextCalled = true
  208. return Context{
  209. "user_id": v.UserID,
  210. "visitor_ip": v.IP,
  211. }
  212. }
  213. func resetState() {
  214. SetLevel(DefaultLevel)
  215. SetFormat(DefaultFormat)
  216. SetOutput(DefaultOutput)
  217. ResetLevelOverrides()
  218. }