logtail_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package logtail
  5. import (
  6. "context"
  7. "encoding/json"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/http/httptest"
  12. "strings"
  13. "testing"
  14. "time"
  15. "tailscale.com/tstest"
  16. )
  17. func TestFastShutdown(t *testing.T) {
  18. ctx, cancel := context.WithCancel(context.Background())
  19. cancel()
  20. testServ := httptest.NewServer(http.HandlerFunc(
  21. func(w http.ResponseWriter, r *http.Request) {}))
  22. defer testServ.Close()
  23. l := NewLogger(Config{
  24. BaseURL: testServ.URL,
  25. }, t.Logf)
  26. err := l.Shutdown(ctx)
  27. if err != nil {
  28. t.Error(err)
  29. }
  30. }
  31. // maximum number of times a test will call l.Write()
  32. const logLines = 3
  33. type LogtailTestServer struct {
  34. srv *httptest.Server // Log server
  35. uploaded chan []byte
  36. }
  37. func NewLogtailTestHarness(t *testing.T) (*LogtailTestServer, *Logger) {
  38. ts := LogtailTestServer{}
  39. // max channel backlog = 1 "started" + #logLines x "log line" + 1 "closed"
  40. ts.uploaded = make(chan []byte, 2+logLines)
  41. ts.srv = httptest.NewServer(http.HandlerFunc(
  42. func(w http.ResponseWriter, r *http.Request) {
  43. body, err := ioutil.ReadAll(r.Body)
  44. if err != nil {
  45. t.Error("failed to read HTTP request")
  46. }
  47. ts.uploaded <- body
  48. }))
  49. t.Cleanup(ts.srv.Close)
  50. l := NewLogger(Config{BaseURL: ts.srv.URL}, t.Logf)
  51. // There is always an initial "logtail started" message
  52. body := <-ts.uploaded
  53. if !strings.Contains(string(body), "started") {
  54. t.Errorf("unknown start logging statement: %q", string(body))
  55. }
  56. return &ts, l
  57. }
  58. func TestDrainPendingMessages(t *testing.T) {
  59. ts, l := NewLogtailTestHarness(t)
  60. for i := 0; i < logLines; i++ {
  61. l.Write([]byte("log line"))
  62. }
  63. // all of the "log line" messages usually arrive at once, but poll if needed.
  64. body := ""
  65. for i := 0; i <= logLines; i++ {
  66. body += string(<-ts.uploaded)
  67. count := strings.Count(body, "log line")
  68. if count == logLines {
  69. break
  70. }
  71. // if we never find count == logLines, the test will eventually time out.
  72. }
  73. err := l.Shutdown(context.Background())
  74. if err != nil {
  75. t.Error(err)
  76. }
  77. }
  78. func TestEncodeAndUploadMessages(t *testing.T) {
  79. ts, l := NewLogtailTestHarness(t)
  80. tests := []struct {
  81. name string
  82. log string
  83. want string
  84. }{
  85. {
  86. "plain text",
  87. "log line",
  88. "log line",
  89. },
  90. {
  91. "simple JSON",
  92. `{"text": "log line"}`,
  93. "log line",
  94. },
  95. }
  96. for _, tt := range tests {
  97. io.WriteString(l, tt.log)
  98. body := <-ts.uploaded
  99. data := unmarshalOne(t, body)
  100. got := data["text"]
  101. if got != tt.want {
  102. t.Errorf("%s: got %q; want %q", tt.name, got.(string), tt.want)
  103. }
  104. ltail, ok := data["logtail"]
  105. if ok {
  106. logtailmap := ltail.(map[string]interface{})
  107. _, ok = logtailmap["client_time"]
  108. if !ok {
  109. t.Errorf("%s: no client_time present", tt.name)
  110. }
  111. } else {
  112. t.Errorf("%s: no logtail map present", tt.name)
  113. }
  114. }
  115. err := l.Shutdown(context.Background())
  116. if err != nil {
  117. t.Error(err)
  118. }
  119. }
  120. func TestEncodeSpecialCases(t *testing.T) {
  121. ts, l := NewLogtailTestHarness(t)
  122. // -------------------------------------------------------------------------
  123. // JSON log message already contains a logtail field.
  124. io.WriteString(l, `{"logtail": "LOGTAIL", "text": "text"}`)
  125. body := <-ts.uploaded
  126. data := unmarshalOne(t, body)
  127. errorHasLogtail, ok := data["error_has_logtail"]
  128. if ok {
  129. if errorHasLogtail != "LOGTAIL" {
  130. t.Errorf("error_has_logtail: got:%q; want:%q",
  131. errorHasLogtail, "LOGTAIL")
  132. }
  133. } else {
  134. t.Errorf("no error_has_logtail field: %v", data)
  135. }
  136. // -------------------------------------------------------------------------
  137. // special characters
  138. io.WriteString(l, "\b\f\n\r\t"+`"\`)
  139. bodytext := string(<-ts.uploaded)
  140. // json.Unmarshal would unescape the characters, we have to look at the encoded text
  141. escaped := strings.Contains(bodytext, `\b\f\n\r\t\"\`)
  142. if !escaped {
  143. t.Errorf("special characters got %s", bodytext)
  144. }
  145. // -------------------------------------------------------------------------
  146. // skipClientTime to omit the logtail metadata
  147. l.skipClientTime = true
  148. io.WriteString(l, "text")
  149. body = <-ts.uploaded
  150. data = unmarshalOne(t, body)
  151. _, ok = data["logtail"]
  152. if ok {
  153. t.Errorf("skipClientTime: unexpected logtail map present: %v", data)
  154. }
  155. // -------------------------------------------------------------------------
  156. // lowMem + long string
  157. l.skipClientTime = false
  158. l.lowMem = true
  159. longStr := strings.Repeat("0", 512)
  160. io.WriteString(l, longStr)
  161. body = <-ts.uploaded
  162. data = unmarshalOne(t, body)
  163. text, ok := data["text"]
  164. if !ok {
  165. t.Errorf("lowMem: no text %v", data)
  166. }
  167. if n := len(text.(string)); n > 300 {
  168. t.Errorf("lowMem: got %d chars; want <300 chars", n)
  169. }
  170. // -------------------------------------------------------------------------
  171. err := l.Shutdown(context.Background())
  172. if err != nil {
  173. t.Error(err)
  174. }
  175. }
  176. var sink []byte
  177. func TestLoggerEncodeTextAllocs(t *testing.T) {
  178. lg := &Logger{timeNow: time.Now}
  179. inBuf := []byte("some text to encode")
  180. err := tstest.MinAllocsPerRun(t, 1, func() {
  181. sink = lg.encodeText(inBuf, false)
  182. })
  183. if err != nil {
  184. t.Fatal(err)
  185. }
  186. }
  187. func TestLoggerWriteLength(t *testing.T) {
  188. lg := &Logger{
  189. timeNow: time.Now,
  190. buffer: NewMemoryBuffer(1024),
  191. }
  192. inBuf := []byte("some text to encode")
  193. n, err := lg.Write(inBuf)
  194. if err != nil {
  195. t.Error(err)
  196. }
  197. if n != len(inBuf) {
  198. t.Errorf("logger.Write wrote %d bytes, expected %d", n, len(inBuf))
  199. }
  200. }
  201. func TestParseAndRemoveLogLevel(t *testing.T) {
  202. tests := []struct {
  203. log string
  204. wantLevel int
  205. wantLog string
  206. }{
  207. {
  208. "no level",
  209. 0,
  210. "no level",
  211. },
  212. {
  213. "[v1] level 1",
  214. 1,
  215. "level 1",
  216. },
  217. {
  218. "level 1 [v1] ",
  219. 1,
  220. "level 1 ",
  221. },
  222. {
  223. "[v2] level 2",
  224. 2,
  225. "level 2",
  226. },
  227. {
  228. "level [v2] 2",
  229. 2,
  230. "level 2",
  231. },
  232. {
  233. "[v3] no level 3",
  234. 0,
  235. "[v3] no level 3",
  236. },
  237. }
  238. for _, tt := range tests {
  239. gotLevel, gotLog := parseAndRemoveLogLevel([]byte(tt.log))
  240. if gotLevel != tt.wantLevel {
  241. t.Errorf("parseAndRemoveLogLevel(%q): got:%d; want %d",
  242. tt.log, gotLevel, tt.wantLevel)
  243. }
  244. if string(gotLog) != tt.wantLog {
  245. t.Errorf("parseAndRemoveLogLevel(%q): got:%q; want %q",
  246. tt.log, gotLog, tt.wantLog)
  247. }
  248. }
  249. }
  250. func TestPublicIDUnmarshalText(t *testing.T) {
  251. const hexStr = "6c60a9e0e7af57170bb1347b2d477e4cbc27d4571a4923b21651456f931e3d55"
  252. x := []byte(hexStr)
  253. var id PublicID
  254. if err := id.UnmarshalText(x); err != nil {
  255. t.Fatal(err)
  256. }
  257. if id.String() != hexStr {
  258. t.Errorf("String = %q; want %q", id.String(), hexStr)
  259. }
  260. err := tstest.MinAllocsPerRun(t, 0, func() {
  261. var id PublicID
  262. if err := id.UnmarshalText(x); err != nil {
  263. t.Fatal(err)
  264. }
  265. })
  266. if err != nil {
  267. t.Fatal(err)
  268. }
  269. }
  270. func unmarshalOne(t *testing.T, body []byte) map[string]interface{} {
  271. t.Helper()
  272. var entries []map[string]interface{}
  273. err := json.Unmarshal(body, &entries)
  274. if err != nil {
  275. t.Error(err)
  276. }
  277. if len(entries) != 1 {
  278. t.Fatalf("expected one entry, got %d", len(entries))
  279. }
  280. return entries[0]
  281. }