log_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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_LevelOverride_ManyOnSameField(t *testing.T) {
  156. t.Cleanup(resetState)
  157. var out bytes.Buffer
  158. SetOutput(&out)
  159. SetFormat(JSONFormat)
  160. SetLevelOverride("tag", "manager", DebugLevel)
  161. SetLevelOverride("tag", "publish", DebugLevel)
  162. Time(time.Unix(11, 0).UTC()).Field("tag", "manager").Debug("this is logged")
  163. Time(time.Unix(12, 0).UTC()).Field("tag", "no-match").Debug("this is not logged")
  164. Time(time.Unix(13, 0).UTC()).Field("tag", "publish").Info("this is also logged")
  165. expected := `{"time":"1970-01-01T00:00:11Z","level":"DEBUG","message":"this is logged","tag":"manager"}
  166. {"time":"1970-01-01T00:00:13Z","level":"INFO","message":"this is also logged","tag":"publish"}
  167. `
  168. require.Equal(t, expected, out.String())
  169. require.False(t, IsFile())
  170. require.Equal(t, "", File())
  171. }
  172. func TestLog_UsingStdLogger_JSON(t *testing.T) {
  173. t.Cleanup(resetState)
  174. var out bytes.Buffer
  175. SetOutput(&out)
  176. SetFormat(JSONFormat)
  177. log.Println("Some other library is using the standard Go logger")
  178. require.Contains(t, out.String(), `,"level":"INFO","message":"Some other library is using the standard Go logger","tag":"stdlog"}`+"\n")
  179. }
  180. func TestLog_UsingStdLogger_Text(t *testing.T) {
  181. t.Cleanup(resetState)
  182. var out bytes.Buffer
  183. SetOutput(&out)
  184. log.Println("Some other library is using the standard Go logger")
  185. require.Contains(t, out.String(), `Some other library is using the standard Go logger`+"\n")
  186. require.NotContains(t, out.String(), `{`)
  187. }
  188. func TestLog_File(t *testing.T) {
  189. t.Cleanup(resetState)
  190. logfile := filepath.Join(t.TempDir(), "ntfy.log")
  191. f, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY, 0600)
  192. require.Nil(t, err)
  193. SetOutput(f)
  194. SetFormat(JSONFormat)
  195. require.True(t, IsFile())
  196. require.Equal(t, logfile, File())
  197. Time(time.Unix(11, 0).UTC()).Field("this_one", "11").Info("this is logged")
  198. require.Nil(t, f.Close())
  199. f, err = os.Open(logfile)
  200. require.Nil(t, err)
  201. contents, err := io.ReadAll(f)
  202. require.Nil(t, err)
  203. require.Equal(t, `{"time":"1970-01-01T00:00:11Z","level":"INFO","message":"this is logged","this_one":"11"}`+"\n", string(contents))
  204. }
  205. type fakeError struct {
  206. Code int
  207. Message string
  208. }
  209. func (e fakeError) Error() string {
  210. return e.Message
  211. }
  212. func (e fakeError) Context() Context {
  213. return Context{
  214. "error": e.Message,
  215. "error_code": e.Code,
  216. }
  217. }
  218. type fakeVisitor struct {
  219. UserID string
  220. IP string
  221. contextCalled bool
  222. }
  223. func (v *fakeVisitor) Context() Context {
  224. v.contextCalled = true
  225. return Context{
  226. "user_id": v.UserID,
  227. "visitor_ip": v.IP,
  228. }
  229. }
  230. func resetState() {
  231. SetLevel(DefaultLevel)
  232. SetFormat(DefaultFormat)
  233. SetOutput(DefaultOutput)
  234. ResetLevelOverrides()
  235. }