logging_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. "google.golang.org/cloud/internal/testutil"
  29. )
  30. func TestLogPayload(t *testing.T) {
  31. lt := newLogTest(t)
  32. defer lt.ts.Close()
  33. tests := []struct {
  34. name string
  35. entry Entry
  36. want string
  37. }{
  38. {
  39. name: "string",
  40. entry: Entry{
  41. Time: time.Unix(0, 0),
  42. Payload: "some log string",
  43. },
  44. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"textPayload":"some log string"}]}`,
  45. },
  46. {
  47. name: "[]byte",
  48. entry: Entry{
  49. Time: time.Unix(0, 0),
  50. Payload: []byte("some log bytes"),
  51. },
  52. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"textPayload":"some log bytes"}]}`,
  53. },
  54. {
  55. name: "struct",
  56. entry: Entry{
  57. Time: time.Unix(0, 0),
  58. Payload: struct {
  59. Foo string `json:"foo"`
  60. Bar int `json:"bar,omitempty"`
  61. }{
  62. Foo: "foovalue",
  63. },
  64. },
  65. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"structPayload":{"foo":"foovalue"}}]}`,
  66. },
  67. {
  68. name: "map[string]interface{}",
  69. entry: Entry{
  70. Time: time.Unix(0, 0),
  71. Payload: map[string]interface{}{
  72. "string": "foo",
  73. "int": 42,
  74. },
  75. },
  76. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"structPayload":{"int":42,"string":"foo"}}]}`,
  77. },
  78. {
  79. name: "map[string]interface{}",
  80. entry: Entry{
  81. Time: time.Unix(0, 0),
  82. Payload: customJSONObject{},
  83. },
  84. want: `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","timestamp":"1970-01-01T00:00:00Z"},"structPayload":{"custom":"json"}}]}`,
  85. },
  86. }
  87. for _, tt := range tests {
  88. lt.startGetRequest()
  89. if err := lt.c.LogSync(tt.entry); err != nil {
  90. t.Errorf("%s: LogSync = %v", tt.name, err)
  91. continue
  92. }
  93. got := lt.getRequest()
  94. if got != tt.want {
  95. t.Errorf("%s: mismatch\n got: %s\nwant: %s\n", tt.name, got, tt.want)
  96. }
  97. }
  98. }
  99. func TestBufferInterval(t *testing.T) {
  100. lt := newLogTest(t)
  101. defer lt.ts.Close()
  102. lt.c.CommonLabels = map[string]string{
  103. "common1": "one",
  104. "common2": "two",
  105. }
  106. lt.c.BufferInterval = 1 * time.Millisecond // immediately, basically.
  107. lt.c.FlushAfter = 100 // but we'll only send 1
  108. lt.startGetRequest()
  109. lt.c.Logger(Debug).Printf("log line 1")
  110. got := lt.getRequest()
  111. 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"}]}`
  112. if got != want {
  113. t.Errorf(" got: %s\nwant: %s\n", got, want)
  114. }
  115. }
  116. func TestFlushAfter(t *testing.T) {
  117. lt := newLogTest(t)
  118. defer lt.ts.Close()
  119. lt.c.CommonLabels = map[string]string{
  120. "common1": "one",
  121. "common2": "two",
  122. }
  123. lt.c.BufferInterval = getRequestTimeout * 2
  124. lt.c.FlushAfter = 2
  125. lt.c.Logger(Debug).Printf("log line 1")
  126. lt.startGetRequest()
  127. lt.c.Logger(Debug).Printf("log line 2")
  128. got := lt.getRequest()
  129. 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"}]}`
  130. if got != want {
  131. t.Errorf(" got: %s\nwant: %s\n", got, want)
  132. }
  133. }
  134. func TestFlush(t *testing.T) {
  135. lt := newLogTest(t)
  136. defer lt.ts.Close()
  137. lt.c.BufferInterval = getRequestTimeout * 2
  138. lt.c.FlushAfter = 100 // but we'll only send 1, requiring a Flush
  139. lt.c.Logger(Debug).Printf("log line 1")
  140. lt.startGetRequest()
  141. if err := lt.c.Flush(); err != nil {
  142. t.Fatal(err)
  143. }
  144. got := lt.getRequest()
  145. want := `{"entries":[{"metadata":{"serviceName":"custom.googleapis.com","severity":"DEBUG","timestamp":"1970-01-01T00:00:01Z"},"textPayload":"log line 1\n"}]}`
  146. if got != want {
  147. t.Errorf(" got: %s\nwant: %s\n", got, want)
  148. }
  149. }
  150. func TestOverflow(t *testing.T) {
  151. lt := newLogTest(t)
  152. defer lt.ts.Close()
  153. lt.c.FlushAfter = 1
  154. lt.c.BufferLimit = 5
  155. lt.c.BufferInterval = 1 * time.Millisecond // immediately, basically.
  156. someErr := errors.New("some specific error value")
  157. lt.c.Overflow = func(c *Client, e Entry) error {
  158. return someErr
  159. }
  160. unblock := make(chan bool, 1)
  161. inHandler := make(chan bool, 1)
  162. lt.handlerc <- http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  163. inHandler <- true
  164. <-unblock
  165. ioutil.ReadAll(r.Body)
  166. io.WriteString(w, "{}") // WriteLogEntriesResponse
  167. })
  168. lt.c.Logger(Debug).Printf("log line 1")
  169. <-inHandler
  170. lt.c.Logger(Debug).Printf("log line 2")
  171. lt.c.Logger(Debug).Printf("log line 3")
  172. lt.c.Logger(Debug).Printf("log line 4")
  173. lt.c.Logger(Debug).Printf("log line 5")
  174. queued, inFlight := lt.c.stats()
  175. if want := 4; queued != want {
  176. t.Errorf("queued = %d; want %d", queued, want)
  177. }
  178. if want := 1; inFlight != want {
  179. t.Errorf("inFlight = %d; want %d", inFlight, want)
  180. }
  181. if err := lt.c.Log(Entry{Payload: "to overflow"}); err != someErr {
  182. t.Errorf("Log(overflow Log entry) = %v; want someErr", err)
  183. }
  184. lt.startGetRequest()
  185. unblock <- true
  186. got := lt.getRequest()
  187. 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"}]}`
  188. if got != want {
  189. t.Errorf(" got: %s\nwant: %s\n", got, want)
  190. }
  191. if err := lt.c.Flush(); err != nil {
  192. t.Fatal(err)
  193. }
  194. queued, inFlight = lt.c.stats()
  195. if want := 0; queued != want {
  196. t.Errorf("queued = %d; want %d", queued, want)
  197. }
  198. if want := 0; inFlight != want {
  199. t.Errorf("inFlight = %d; want %d", inFlight, want)
  200. }
  201. }
  202. func TestIntegration(t *testing.T) {
  203. if testing.Short() {
  204. t.Skip("Integration tests skipped in short mode")
  205. }
  206. ctx := context.Background()
  207. ts := testutil.TokenSource(ctx, Scope)
  208. if ts == nil {
  209. t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
  210. }
  211. projID := testutil.ProjID()
  212. c, err := NewClient(ctx, projID, "logging-integration-test", cloud.WithTokenSource(ts))
  213. if err != nil {
  214. t.Fatalf("error creating client: %v", err)
  215. }
  216. if err := c.Ping(); err != nil {
  217. t.Fatalf("error pinging logging api: %v", err)
  218. }
  219. if err := c.LogSync(Entry{Payload: customJSONObject{}}); err != nil {
  220. t.Fatalf("error writing log: %v", err)
  221. }
  222. if err := c.Log(Entry{Payload: customJSONObject{}}); err != nil {
  223. t.Fatalf("error writing log: %v", err)
  224. }
  225. if _, err := c.Writer(Default).Write([]byte("test log with io.Writer")); err != nil {
  226. t.Fatalf("error writing log using io.Writer: %v", err)
  227. }
  228. c.Logger(Default).Println("test log with log.Logger")
  229. if err := c.Flush(); err != nil {
  230. t.Fatalf("error flushing logs: %v", err)
  231. }
  232. }
  233. func (c *Client) stats() (queued, inFlight int) {
  234. c.mu.Lock()
  235. defer c.mu.Unlock()
  236. return len(c.queued), c.inFlight
  237. }
  238. type customJSONObject struct{}
  239. func (customJSONObject) MarshalJSON() ([]byte, error) {
  240. return []byte(`{"custom":"json"}`), nil
  241. }
  242. type logTest struct {
  243. t *testing.T
  244. ts *httptest.Server
  245. c *Client
  246. handlerc chan<- http.Handler
  247. bodyc chan string
  248. }
  249. func newLogTest(t *testing.T) *logTest {
  250. handlerc := make(chan http.Handler, 1)
  251. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  252. select {
  253. case h := <-handlerc:
  254. h.ServeHTTP(w, r)
  255. default:
  256. slurp, _ := ioutil.ReadAll(r.Body)
  257. t.Errorf("Unexpected HTTP request received: %s", slurp)
  258. w.WriteHeader(500)
  259. io.WriteString(w, "unexpected HTTP request")
  260. }
  261. }))
  262. c, err := NewClient(context.Background(), "PROJ-ID", "LOG-NAME",
  263. cloud.WithEndpoint(ts.URL),
  264. cloud.WithTokenSource(dummyTokenSource{}), // prevent DefaultTokenSource
  265. )
  266. if err != nil {
  267. t.Fatal(err)
  268. }
  269. var clock struct {
  270. sync.Mutex
  271. now time.Time
  272. }
  273. c.timeNow = func() time.Time {
  274. clock.Lock()
  275. defer clock.Unlock()
  276. if clock.now.IsZero() {
  277. clock.now = time.Unix(0, 0)
  278. }
  279. clock.now = clock.now.Add(1 * time.Second)
  280. return clock.now
  281. }
  282. return &logTest{
  283. t: t,
  284. ts: ts,
  285. c: c,
  286. handlerc: handlerc,
  287. }
  288. }
  289. func (lt *logTest) startGetRequest() {
  290. bodyc := make(chan string, 1)
  291. lt.bodyc = bodyc
  292. lt.handlerc <- http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  293. slurp, err := ioutil.ReadAll(r.Body)
  294. if err != nil {
  295. bodyc <- "ERROR: " + err.Error()
  296. } else {
  297. bodyc <- string(slurp)
  298. }
  299. io.WriteString(w, "{}") // a complete WriteLogEntriesResponse JSON struct
  300. })
  301. }
  302. const getRequestTimeout = 5 * time.Second
  303. func (lt *logTest) getRequest() string {
  304. if lt.bodyc == nil {
  305. lt.t.Fatalf("getRequest called without previous startGetRequest")
  306. }
  307. select {
  308. case v := <-lt.bodyc:
  309. return strings.TrimSpace(v)
  310. case <-time.After(getRequestTimeout):
  311. lt.t.Fatalf("timeout waiting for request")
  312. panic("unreachable")
  313. }
  314. }
  315. // dummyTokenSource returns fake oauth2 tokens for local testing.
  316. type dummyTokenSource struct{}
  317. func (dummyTokenSource) Token() (*oauth2.Token, error) {
  318. return new(oauth2.Token), nil
  319. }