log_test.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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_FieldIf(t *testing.T) {
  173. t.Cleanup(resetState)
  174. var out bytes.Buffer
  175. SetOutput(&out)
  176. SetLevel(DebugLevel)
  177. SetFormat(JSONFormat)
  178. Time(time.Unix(11, 0).UTC()).
  179. FieldIf("trace_field", "manager", TraceLevel). // This is not logged
  180. Field("tag", "manager").
  181. Debug("trace_field is not logged")
  182. SetLevel(TraceLevel)
  183. Time(time.Unix(12, 0).UTC()).
  184. FieldIf("trace_field", "manager", TraceLevel). // Now it is logged
  185. Field("tag", "manager").
  186. Debug("trace_field is logged")
  187. expected := `{"time":"1970-01-01T00:00:11Z","level":"DEBUG","message":"trace_field is not logged","tag":"manager"}
  188. {"time":"1970-01-01T00:00:12Z","level":"DEBUG","message":"trace_field is logged","tag":"manager","trace_field":"manager"}
  189. `
  190. require.Equal(t, expected, out.String())
  191. }
  192. func TestLog_UsingStdLogger_JSON(t *testing.T) {
  193. t.Cleanup(resetState)
  194. var out bytes.Buffer
  195. SetOutput(&out)
  196. SetFormat(JSONFormat)
  197. log.Println("Some other library is using the standard Go logger")
  198. require.Contains(t, out.String(), `,"level":"INFO","message":"Some other library is using the standard Go logger","tag":"stdlog"}`+"\n")
  199. }
  200. func TestLog_UsingStdLogger_Text(t *testing.T) {
  201. t.Cleanup(resetState)
  202. var out bytes.Buffer
  203. SetOutput(&out)
  204. log.Println("Some other library is using the standard Go logger")
  205. require.Contains(t, out.String(), `Some other library is using the standard Go logger`+"\n")
  206. require.NotContains(t, out.String(), `{`)
  207. }
  208. func TestLog_File(t *testing.T) {
  209. t.Cleanup(resetState)
  210. logfile := filepath.Join(t.TempDir(), "ntfy.log")
  211. f, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY, 0600)
  212. require.Nil(t, err)
  213. SetOutput(f)
  214. SetFormat(JSONFormat)
  215. require.True(t, IsFile())
  216. require.Equal(t, logfile, File())
  217. Time(time.Unix(11, 0).UTC()).Field("this_one", "11").Info("this is logged")
  218. require.Nil(t, f.Close())
  219. f, err = os.Open(logfile)
  220. require.Nil(t, err)
  221. contents, err := io.ReadAll(f)
  222. require.Nil(t, err)
  223. require.Equal(t, `{"time":"1970-01-01T00:00:11Z","level":"INFO","message":"this is logged","this_one":"11"}`+"\n", string(contents))
  224. }
  225. type fakeError struct {
  226. Code int
  227. Message string
  228. }
  229. func (e fakeError) Error() string {
  230. return e.Message
  231. }
  232. func (e fakeError) Context() Context {
  233. return Context{
  234. "error": e.Message,
  235. "error_code": e.Code,
  236. }
  237. }
  238. type fakeVisitor struct {
  239. UserID string
  240. IP string
  241. contextCalled bool
  242. }
  243. func (v *fakeVisitor) Context() Context {
  244. v.contextCalled = true
  245. return Context{
  246. "user_id": v.UserID,
  247. "visitor_ip": v.IP,
  248. }
  249. }
  250. func resetState() {
  251. SetLevel(DefaultLevel)
  252. SetFormat(DefaultFormat)
  253. SetOutput(DefaultOutput)
  254. ResetLevelOverrides()
  255. }