logging_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Copyright 2015 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package logging
  15. import (
  16. "errors"
  17. "io"
  18. "io/ioutil"
  19. "net/http"
  20. "net/http/httptest"
  21. "strings"
  22. "sync"
  23. "testing"
  24. "time"
  25. "golang.org/x/net/context"
  26. "golang.org/x/oauth2"
  27. "google.golang.org/cloud"
  28. )
  29. func TestLogPayload(t *testing.T) {
  30. lt := newLogTest(t)
  31. defer lt.ts.Close()
  32. tests := []struct {
  33. name string
  34. entry Entry
  35. want string
  36. }{
  37. {
  38. name: "string",
  39. entry: Entry{
  40. Time: time.Unix(0, 0),
  41. Payload: "some log string",
  42. },
  43. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"textPayload":"some log string"}]}`,
  44. },
  45. {
  46. name: "[]byte",
  47. entry: Entry{
  48. Time: time.Unix(0, 0),
  49. Payload: []byte("some log bytes"),
  50. },
  51. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"textPayload":"some log bytes"}]}`,
  52. },
  53. {
  54. name: "struct",
  55. entry: Entry{
  56. Time: time.Unix(0, 0),
  57. Payload: struct {
  58. Foo string `json:"foo"`
  59. Bar int `json:"bar,omitempty"`
  60. }{
  61. Foo: "foovalue",
  62. },
  63. },
  64. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"structPayload":{"foo":"foovalue"}}]}`,
  65. },
  66. {
  67. name: "map[string]interface{}",
  68. entry: Entry{
  69. Time: time.Unix(0, 0),
  70. Payload: map[string]interface{}{
  71. "string": "foo",
  72. "int": 42,
  73. },
  74. },
  75. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"structPayload":{"int":42,"string":"foo"}}]}`,
  76. },
  77. {
  78. name: "map[string]interface{}",
  79. entry: Entry{
  80. Time: time.Unix(0, 0),
  81. Payload: customJSONObject{},
  82. },
  83. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"structPayload":{"custom":"json"}}]}`,
  84. },
  85. }
  86. for _, tt := range tests {
  87. lt.startGetRequest()
  88. if err := lt.c.LogSync(tt.entry); err != nil {
  89. t.Errorf("%s: LogSync = %v", tt.name, err)
  90. continue
  91. }
  92. got := lt.getRequest()
  93. if got != tt.want {
  94. t.Errorf("%s: mismatch\n got: %s\nwant: %s\n", tt.name, got, tt.want)
  95. }
  96. }
  97. }
  98. func TestBufferInterval(t *testing.T) {
  99. lt := newLogTest(t)
  100. defer lt.ts.Close()
  101. lt.c.CommonLabels = map[string]string{
  102. "common1": "one",
  103. "common2": "two",
  104. }
  105. lt.c.BufferInterval = 1 * time.Millisecond // immediately, basically.
  106. lt.c.FlushAfter = 100 // but we'll only send 1
  107. lt.startGetRequest()
  108. lt.c.Logger(Debug).Printf("log line 1")
  109. got := lt.getRequest()
  110. want := `{"commonLabels":{"common1":"one","common2":"two"},"entries":[{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:01Z"},"textPayload":"log line 1\n"}]}`
  111. if got != want {
  112. t.Errorf(" got: %s\nwant: %s\n", got, want)
  113. }
  114. }
  115. func TestFlushAfter(t *testing.T) {
  116. lt := newLogTest(t)
  117. defer lt.ts.Close()
  118. lt.c.CommonLabels = map[string]string{
  119. "common1": "one",
  120. "common2": "two",
  121. }
  122. lt.c.BufferInterval = getRequestTimeout * 2
  123. lt.c.FlushAfter = 2
  124. lt.c.Logger(Debug).Printf("log line 1")
  125. lt.startGetRequest()
  126. lt.c.Logger(Debug).Printf("log line 2")
  127. got := lt.getRequest()
  128. want := `{"commonLabels":{"common1":"one","common2":"two"},"entries":[{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:01Z"},"textPayload":"log line 1\n"},{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:02Z"},"textPayload":"log line 2\n"}]}`
  129. if got != want {
  130. t.Errorf(" got: %s\nwant: %s\n", got, want)
  131. }
  132. }
  133. func TestFlush(t *testing.T) {
  134. lt := newLogTest(t)
  135. defer lt.ts.Close()
  136. lt.c.BufferInterval = getRequestTimeout * 2
  137. lt.c.FlushAfter = 100 // but we'll only send 1, requiring a Flush
  138. lt.c.Logger(Debug).Printf("log line 1")
  139. lt.startGetRequest()
  140. if err := lt.c.Flush(); err != nil {
  141. t.Fatal(err)
  142. }
  143. got := lt.getRequest()
  144. want := `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:01Z"},"textPayload":"log line 1\n"}]}`
  145. if got != want {
  146. t.Errorf(" got: %s\nwant: %s\n", got, want)
  147. }
  148. }
  149. func TestOverflow(t *testing.T) {
  150. lt := newLogTest(t)
  151. defer lt.ts.Close()
  152. lt.c.FlushAfter = 1
  153. lt.c.BufferLimit = 5
  154. lt.c.BufferInterval = 1 * time.Millisecond // immediately, basically.
  155. someErr := errors.New("some specific error value")
  156. lt.c.Overflow = func(c *Client, e Entry) error {
  157. return someErr
  158. }
  159. unblock := make(chan bool, 1)
  160. inHandler := make(chan bool, 1)
  161. lt.handlerc <- http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  162. inHandler <- true
  163. <-unblock
  164. ioutil.ReadAll(r.Body)
  165. io.WriteString(w, "{}") // WriteLogEntriesResponse
  166. })
  167. lt.c.Logger(Debug).Printf("log line 1")
  168. <-inHandler
  169. lt.c.Logger(Debug).Printf("log line 2")
  170. lt.c.Logger(Debug).Printf("log line 3")
  171. lt.c.Logger(Debug).Printf("log line 4")
  172. lt.c.Logger(Debug).Printf("log line 5")
  173. queued, inFlight := lt.c.stats()
  174. if want := 4; queued != want {
  175. t.Errorf("queued = %d; want %d", queued, want)
  176. }
  177. if want := 1; inFlight != want {
  178. t.Errorf("inFlight = %d; want %d", inFlight, want)
  179. }
  180. if err := lt.c.Log(Entry{Payload: "to overflow"}); err != someErr {
  181. t.Errorf("Log(overflow Log entry) = %v; want someErr", err)
  182. }
  183. lt.startGetRequest()
  184. unblock <- true
  185. got := lt.getRequest()
  186. want := `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:02Z"},"textPayload":"log line 2\n"},{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:03Z"},"textPayload":"log line 3\n"},{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:04Z"},"textPayload":"log line 4\n"},{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:05Z"},"textPayload":"log line 5\n"}]}`
  187. if got != want {
  188. t.Errorf(" got: %s\nwant: %s\n", got, want)
  189. }
  190. if err := lt.c.Flush(); err != nil {
  191. t.Fatal(err)
  192. }
  193. queued, inFlight = lt.c.stats()
  194. if want := 0; queued != want {
  195. t.Errorf("queued = %d; want %d", queued, want)
  196. }
  197. if want := 0; inFlight != want {
  198. t.Errorf("inFlight = %d; want %d", inFlight, want)
  199. }
  200. }
  201. func (c *Client) stats() (queued, inFlight int) {
  202. c.mu.Lock()
  203. defer c.mu.Unlock()
  204. return len(c.queued), c.inFlight
  205. }
  206. type customJSONObject struct{}
  207. func (customJSONObject) MarshalJSON() ([]byte, error) {
  208. return []byte(`{"custom":"json"}`), nil
  209. }
  210. type logTest struct {
  211. t *testing.T
  212. ts *httptest.Server
  213. c *Client
  214. handlerc chan<- http.Handler
  215. bodyc chan string
  216. }
  217. func newLogTest(t *testing.T) *logTest {
  218. handlerc := make(chan http.Handler, 1)
  219. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  220. select {
  221. case h := <-handlerc:
  222. h.ServeHTTP(w, r)
  223. default:
  224. slurp, _ := ioutil.ReadAll(r.Body)
  225. t.Errorf("Unexpected HTTP request received: %s", slurp)
  226. w.WriteHeader(500)
  227. io.WriteString(w, "unexpected HTTP request")
  228. }
  229. }))
  230. c, err := NewClient(context.Background(), "PROJ-ID", "LOG-NAME",
  231. cloud.WithEndpoint(ts.URL),
  232. cloud.WithTokenSource(dummyTokenSource{}), // prevent DefaultTokenSource
  233. )
  234. if err != nil {
  235. t.Fatal(err)
  236. }
  237. var clock struct {
  238. sync.Mutex
  239. now time.Time
  240. }
  241. c.timeNow = func() time.Time {
  242. clock.Lock()
  243. defer clock.Unlock()
  244. if clock.now.IsZero() {
  245. clock.now = time.Unix(0, 0)
  246. }
  247. clock.now = clock.now.Add(1 * time.Second)
  248. return clock.now
  249. }
  250. return &logTest{
  251. t: t,
  252. ts: ts,
  253. c: c,
  254. handlerc: handlerc,
  255. }
  256. }
  257. func (lt *logTest) startGetRequest() {
  258. bodyc := make(chan string, 1)
  259. lt.bodyc = bodyc
  260. lt.handlerc <- http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  261. slurp, err := ioutil.ReadAll(r.Body)
  262. if err != nil {
  263. bodyc <- "ERROR: " + err.Error()
  264. } else {
  265. bodyc <- string(slurp)
  266. }
  267. io.WriteString(w, "{}") // a complete WriteLogEntriesResponse JSON struct
  268. })
  269. }
  270. const getRequestTimeout = 5 * time.Second
  271. func (lt *logTest) getRequest() string {
  272. if lt.bodyc == nil {
  273. lt.t.Fatalf("getRequest called without previous startGetRequest")
  274. }
  275. select {
  276. case v := <-lt.bodyc:
  277. return strings.TrimSpace(v)
  278. case <-time.After(getRequestTimeout):
  279. lt.t.Fatalf("timeout waiting for request")
  280. panic("unreachable")
  281. }
  282. }
  283. // dummyTokenSource returns fake oauth2 tokens for local testing.
  284. type dummyTokenSource struct{}
  285. func (dummyTokenSource) Token() (*oauth2.Token, error) {
  286. return new(oauth2.Token), nil
  287. }