You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

632 lines
16 KiB

  1. // Copyright 2016 Google LLC
  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. // TODO(jba): test that OnError is getting called appropriately.
  15. package logging_test
  16. import (
  17. "flag"
  18. "fmt"
  19. "log"
  20. "math/rand"
  21. "os"
  22. "strings"
  23. "sync"
  24. "testing"
  25. "time"
  26. gax "github.com/googleapis/gax-go"
  27. cinternal "cloud.google.com/go/internal"
  28. "cloud.google.com/go/internal/testutil"
  29. "cloud.google.com/go/internal/uid"
  30. "cloud.google.com/go/logging"
  31. ltesting "cloud.google.com/go/logging/internal/testing"
  32. "cloud.google.com/go/logging/logadmin"
  33. "golang.org/x/net/context"
  34. "golang.org/x/oauth2"
  35. "google.golang.org/api/iterator"
  36. "google.golang.org/api/option"
  37. mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
  38. "google.golang.org/grpc"
  39. "google.golang.org/grpc/codes"
  40. "google.golang.org/grpc/status"
  41. )
  42. const testLogIDPrefix = "GO-LOGGING-CLIENT/TEST-LOG"
  43. var uids = uid.NewSpace(testLogIDPrefix, nil)
  44. var (
  45. client *logging.Client
  46. aclient *logadmin.Client
  47. testProjectID string
  48. testLogID string
  49. testFilter string
  50. errorc chan error
  51. ctx context.Context
  52. // Adjust the fields of a FullEntry received from the production service
  53. // before comparing it with the expected result. We can't correctly
  54. // compare certain fields, like times or server-generated IDs.
  55. clean func(*logging.Entry)
  56. // Create a new client with the given project ID.
  57. newClients func(ctx context.Context, projectID string) (*logging.Client, *logadmin.Client)
  58. )
  59. func testNow() time.Time {
  60. return time.Unix(1000, 0)
  61. }
  62. // If true, this test is using the production service, not a fake.
  63. var integrationTest bool
  64. func TestMain(m *testing.M) {
  65. flag.Parse() // needed for testing.Short()
  66. ctx = context.Background()
  67. testProjectID = testutil.ProjID()
  68. errorc = make(chan error, 100)
  69. if testProjectID == "" || testing.Short() {
  70. integrationTest = false
  71. if testProjectID != "" {
  72. log.Print("Integration tests skipped in short mode (using fake instead)")
  73. }
  74. testProjectID = "PROJECT_ID"
  75. clean = func(e *logging.Entry) {
  76. // Remove the insert ID for consistency with the integration test.
  77. e.InsertID = ""
  78. }
  79. addr, err := ltesting.NewServer()
  80. if err != nil {
  81. log.Fatalf("creating fake server: %v", err)
  82. }
  83. logging.SetNow(testNow)
  84. newClients = func(ctx context.Context, parent string) (*logging.Client, *logadmin.Client) {
  85. conn, err := grpc.Dial(addr, grpc.WithInsecure())
  86. if err != nil {
  87. log.Fatalf("dialing %q: %v", addr, err)
  88. }
  89. c, err := logging.NewClient(ctx, parent, option.WithGRPCConn(conn))
  90. if err != nil {
  91. log.Fatalf("creating client for fake at %q: %v", addr, err)
  92. }
  93. ac, err := logadmin.NewClient(ctx, parent, option.WithGRPCConn(conn))
  94. if err != nil {
  95. log.Fatalf("creating client for fake at %q: %v", addr, err)
  96. }
  97. return c, ac
  98. }
  99. } else {
  100. integrationTest = true
  101. clean = func(e *logging.Entry) {
  102. // We cannot compare timestamps, so set them to the test time.
  103. // Also, remove the insert ID added by the service.
  104. e.Timestamp = testNow().UTC()
  105. e.InsertID = ""
  106. }
  107. ts := testutil.TokenSource(ctx, logging.AdminScope)
  108. if ts == nil {
  109. log.Fatal("The project key must be set. See CONTRIBUTING.md for details")
  110. }
  111. log.Printf("running integration tests with project %s", testProjectID)
  112. newClients = func(ctx context.Context, parent string) (*logging.Client, *logadmin.Client) {
  113. c, err := logging.NewClient(ctx, parent, option.WithTokenSource(ts))
  114. if err != nil {
  115. log.Fatalf("creating prod client: %v", err)
  116. }
  117. ac, err := logadmin.NewClient(ctx, parent, option.WithTokenSource(ts))
  118. if err != nil {
  119. log.Fatalf("creating prod client: %v", err)
  120. }
  121. return c, ac
  122. }
  123. }
  124. client, aclient = newClients(ctx, testProjectID)
  125. client.OnError = func(e error) { errorc <- e }
  126. exit := m.Run()
  127. os.Exit(exit)
  128. }
  129. func initLogs(ctx context.Context) {
  130. testLogID = uids.New()
  131. testFilter = fmt.Sprintf(`logName = "projects/%s/logs/%s"`, testProjectID,
  132. strings.Replace(testLogID, "/", "%2F", -1))
  133. }
  134. // Testing of Logger.Log is done in logadmin_test.go, TestEntries.
  135. func TestLogSync(t *testing.T) {
  136. initLogs(ctx) // Generate new testLogID
  137. ctx := context.Background()
  138. lg := client.Logger(testLogID)
  139. err := lg.LogSync(ctx, logging.Entry{Payload: "hello"})
  140. if err != nil {
  141. t.Fatal(err)
  142. }
  143. err = lg.LogSync(ctx, logging.Entry{Payload: "goodbye"})
  144. if err != nil {
  145. t.Fatal(err)
  146. }
  147. // Allow overriding the MonitoredResource.
  148. err = lg.LogSync(ctx, logging.Entry{Payload: "mr", Resource: &mrpb.MonitoredResource{Type: "global"}})
  149. if err != nil {
  150. t.Fatal(err)
  151. }
  152. want := []*logging.Entry{
  153. entryForTesting("hello"),
  154. entryForTesting("goodbye"),
  155. entryForTesting("mr"),
  156. }
  157. var got []*logging.Entry
  158. ok := waitFor(func() bool {
  159. got, err = allTestLogEntries(ctx)
  160. if err != nil {
  161. t.Log("fetching log entries: ", err)
  162. return false
  163. }
  164. return len(got) == len(want)
  165. })
  166. if !ok {
  167. t.Fatalf("timed out; got: %d, want: %d\n", len(got), len(want))
  168. }
  169. if msg, ok := compareEntries(got, want); !ok {
  170. t.Error(msg)
  171. }
  172. }
  173. func TestLogAndEntries(t *testing.T) {
  174. initLogs(ctx) // Generate new testLogID
  175. ctx := context.Background()
  176. payloads := []string{"p1", "p2", "p3", "p4", "p5"}
  177. lg := client.Logger(testLogID)
  178. for _, p := range payloads {
  179. // Use the insert ID to guarantee iteration order.
  180. lg.Log(logging.Entry{Payload: p, InsertID: p})
  181. }
  182. lg.Flush()
  183. var want []*logging.Entry
  184. for _, p := range payloads {
  185. want = append(want, entryForTesting(p))
  186. }
  187. var got []*logging.Entry
  188. ok := waitFor(func() bool {
  189. var err error
  190. got, err = allTestLogEntries(ctx)
  191. if err != nil {
  192. t.Log("fetching log entries: ", err)
  193. return false
  194. }
  195. return len(got) == len(want)
  196. })
  197. if !ok {
  198. t.Fatalf("timed out; got: %d, want: %d\n", len(got), len(want))
  199. }
  200. if msg, ok := compareEntries(got, want); !ok {
  201. t.Error(msg)
  202. }
  203. }
  204. // compareEntries compares most fields list of Entries against expected. compareEntries does not compare:
  205. // - HTTPRequest
  206. // - Operation
  207. // - Resource
  208. func compareEntries(got, want []*logging.Entry) (string, bool) {
  209. if len(got) != len(want) {
  210. return fmt.Sprintf("got %d entries, want %d", len(got), len(want)), false
  211. }
  212. for i := range got {
  213. if !compareEntry(got[i], want[i]) {
  214. return fmt.Sprintf("#%d:\ngot %+v\nwant %+v", i, got[i], want[i]), false
  215. }
  216. }
  217. return "", true
  218. }
  219. func compareEntry(got, want *logging.Entry) bool {
  220. if got.Timestamp.Unix() != want.Timestamp.Unix() {
  221. return false
  222. }
  223. if got.Severity != want.Severity {
  224. return false
  225. }
  226. if !ltesting.PayloadEqual(got.Payload, want.Payload) {
  227. return false
  228. }
  229. if !testutil.Equal(got.Labels, want.Labels) {
  230. return false
  231. }
  232. if got.InsertID != want.InsertID {
  233. return false
  234. }
  235. if got.LogName != want.LogName {
  236. return false
  237. }
  238. return true
  239. }
  240. func entryForTesting(payload interface{}) *logging.Entry {
  241. return &logging.Entry{
  242. Timestamp: testNow().UTC(),
  243. Payload: payload,
  244. LogName: "projects/" + testProjectID + "/logs/" + testLogID,
  245. Resource: &mrpb.MonitoredResource{Type: "global", Labels: map[string]string{"project_id": testProjectID}},
  246. }
  247. }
  248. func countLogEntries(ctx context.Context, filter string) int {
  249. it := aclient.Entries(ctx, logadmin.Filter(filter))
  250. n := 0
  251. for {
  252. _, err := it.Next()
  253. if err == iterator.Done {
  254. return n
  255. }
  256. if err != nil {
  257. log.Fatalf("counting log entries: %v", err)
  258. }
  259. n++
  260. }
  261. }
  262. func allTestLogEntries(ctx context.Context) ([]*logging.Entry, error) {
  263. return allEntries(ctx, aclient, testFilter)
  264. }
  265. func allEntries(ctx context.Context, aclient *logadmin.Client, filter string) ([]*logging.Entry, error) {
  266. var es []*logging.Entry
  267. it := aclient.Entries(ctx, logadmin.Filter(filter))
  268. for {
  269. e, err := cleanNext(it)
  270. switch err {
  271. case nil:
  272. es = append(es, e)
  273. case iterator.Done:
  274. return es, nil
  275. default:
  276. return nil, err
  277. }
  278. }
  279. }
  280. func cleanNext(it *logadmin.EntryIterator) (*logging.Entry, error) {
  281. e, err := it.Next()
  282. if err != nil {
  283. return nil, err
  284. }
  285. clean(e)
  286. return e, nil
  287. }
  288. func TestStandardLogger(t *testing.T) {
  289. initLogs(ctx) // Generate new testLogID
  290. ctx := context.Background()
  291. lg := client.Logger(testLogID)
  292. slg := lg.StandardLogger(logging.Info)
  293. if slg != lg.StandardLogger(logging.Info) {
  294. t.Error("There should be only one standard logger at each severity.")
  295. }
  296. if slg == lg.StandardLogger(logging.Debug) {
  297. t.Error("There should be a different standard logger for each severity.")
  298. }
  299. slg.Print("info")
  300. lg.Flush()
  301. var got []*logging.Entry
  302. ok := waitFor(func() bool {
  303. var err error
  304. got, err = allTestLogEntries(ctx)
  305. if err != nil {
  306. t.Log("fetching log entries: ", err)
  307. return false
  308. }
  309. return len(got) == 1
  310. })
  311. if !ok {
  312. t.Fatalf("timed out; got: %d, want: %d\n", len(got), 1)
  313. }
  314. if len(got) != 1 {
  315. t.Fatalf("expected non-nil request with one entry; got:\n%+v", got)
  316. }
  317. if got, want := got[0].Payload.(string), "info\n"; got != want {
  318. t.Errorf("payload: got %q, want %q", got, want)
  319. }
  320. if got, want := logging.Severity(got[0].Severity), logging.Info; got != want {
  321. t.Errorf("severity: got %s, want %s", got, want)
  322. }
  323. }
  324. func TestSeverity(t *testing.T) {
  325. if got, want := logging.Info.String(), "Info"; got != want {
  326. t.Errorf("got %q, want %q", got, want)
  327. }
  328. if got, want := logging.Severity(-99).String(), "-99"; got != want {
  329. t.Errorf("got %q, want %q", got, want)
  330. }
  331. }
  332. func TestParseSeverity(t *testing.T) {
  333. for _, test := range []struct {
  334. in string
  335. want logging.Severity
  336. }{
  337. {"", logging.Default},
  338. {"whatever", logging.Default},
  339. {"Default", logging.Default},
  340. {"ERROR", logging.Error},
  341. {"Error", logging.Error},
  342. {"error", logging.Error},
  343. } {
  344. got := logging.ParseSeverity(test.in)
  345. if got != test.want {
  346. t.Errorf("%q: got %s, want %s\n", test.in, got, test.want)
  347. }
  348. }
  349. }
  350. func TestErrors(t *testing.T) {
  351. initLogs(ctx) // Generate new testLogID
  352. // Drain errors already seen.
  353. loop:
  354. for {
  355. select {
  356. case <-errorc:
  357. default:
  358. break loop
  359. }
  360. }
  361. // Try to log something that can't be JSON-marshalled.
  362. lg := client.Logger(testLogID)
  363. lg.Log(logging.Entry{Payload: func() {}})
  364. // Expect an error from Flush.
  365. err := lg.Flush()
  366. if err == nil {
  367. t.Fatal("expected error, got nil")
  368. }
  369. }
  370. type badTokenSource struct{}
  371. func (badTokenSource) Token() (*oauth2.Token, error) {
  372. return &oauth2.Token{}, nil
  373. }
  374. func TestPing(t *testing.T) {
  375. // Ping twice, in case the service's InsertID logic messes with the error code.
  376. ctx := context.Background()
  377. // The global client should be valid.
  378. if err := client.Ping(ctx); err != nil {
  379. t.Errorf("project %s: got %v, expected nil", testProjectID, err)
  380. }
  381. if err := client.Ping(ctx); err != nil {
  382. t.Errorf("project %s, #2: got %v, expected nil", testProjectID, err)
  383. }
  384. // nonexistent project
  385. c, a := newClients(ctx, testProjectID+"-BAD")
  386. defer c.Close()
  387. defer a.Close()
  388. if err := c.Ping(ctx); err == nil {
  389. t.Errorf("nonexistent project: want error pinging logging api, got nil")
  390. }
  391. if err := c.Ping(ctx); err == nil {
  392. t.Errorf("nonexistent project, #2: want error pinging logging api, got nil")
  393. }
  394. // Bad creds. We cannot test this with the fake, since it doesn't do auth.
  395. if integrationTest {
  396. c, err := logging.NewClient(ctx, testProjectID, option.WithTokenSource(badTokenSource{}))
  397. if err != nil {
  398. t.Fatal(err)
  399. }
  400. if err := c.Ping(ctx); err == nil {
  401. t.Errorf("bad creds: want error pinging logging api, got nil")
  402. }
  403. if err := c.Ping(ctx); err == nil {
  404. t.Errorf("bad creds, #2: want error pinging logging api, got nil")
  405. }
  406. if err := c.Close(); err != nil {
  407. t.Fatalf("error closing client: %v", err)
  408. }
  409. }
  410. }
  411. func TestLogsAndDelete(t *testing.T) {
  412. // This function tests both the Logs and DeleteLog methods. We only try to
  413. // delete those logs that we can observe and that were generated by this
  414. // test. This may not include the logs generated from the current test run,
  415. // because the logging service is only eventually consistent. It's
  416. // therefore possible that on some runs, this test will do nothing.
  417. ctx := context.Background()
  418. it := aclient.Logs(ctx)
  419. nDeleted := 0
  420. for {
  421. logID, err := it.Next()
  422. if err == iterator.Done {
  423. break
  424. }
  425. if err != nil {
  426. t.Fatal(err)
  427. }
  428. if strings.HasPrefix(logID, testLogIDPrefix) {
  429. if err := aclient.DeleteLog(ctx, logID); err != nil {
  430. // Ignore NotFound. Sometimes, amazingly, DeleteLog cannot find
  431. // a log that is returned by Logs.
  432. if status.Code(err) != codes.NotFound {
  433. t.Fatalf("deleting %q: %v", logID, err)
  434. }
  435. } else {
  436. nDeleted++
  437. }
  438. }
  439. }
  440. t.Logf("deleted %d logs", nDeleted)
  441. }
  442. func TestNonProjectParent(t *testing.T) {
  443. ctx := context.Background()
  444. initLogs(ctx)
  445. const orgID = "433637338589" // org ID for google.com
  446. parent := "organizations/" + orgID
  447. c, a := newClients(ctx, parent)
  448. defer c.Close()
  449. defer a.Close()
  450. lg := c.Logger(testLogID)
  451. err := lg.LogSync(ctx, logging.Entry{Payload: "hello"})
  452. if integrationTest {
  453. // We don't have permission to log to the organization.
  454. if got, want := status.Code(err), codes.PermissionDenied; got != want {
  455. t.Errorf("got code %s, want %s", got, want)
  456. }
  457. return
  458. }
  459. // Continue test against fake.
  460. if err != nil {
  461. t.Fatal(err)
  462. }
  463. want := []*logging.Entry{{
  464. Timestamp: testNow().UTC(),
  465. Payload: "hello",
  466. LogName: parent + "/logs/" + testLogID,
  467. Resource: &mrpb.MonitoredResource{
  468. Type: "organization",
  469. Labels: map[string]string{"organization_id": orgID},
  470. },
  471. }}
  472. var got []*logging.Entry
  473. ok := waitFor(func() bool {
  474. got, err = allEntries(ctx, a, fmt.Sprintf(`logName = "%s/logs/%s"`, parent,
  475. strings.Replace(testLogID, "/", "%2F", -1)))
  476. if err != nil {
  477. t.Log("fetching log entries: ", err)
  478. return false
  479. }
  480. return len(got) == len(want)
  481. })
  482. if !ok {
  483. t.Fatalf("timed out; got: %d, want: %d\n", len(got), len(want))
  484. }
  485. if msg, ok := compareEntries(got, want); !ok {
  486. t.Error(msg)
  487. }
  488. }
  489. // waitFor calls f repeatedly with exponential backoff, blocking until it returns true.
  490. // It returns false after a while (if it times out).
  491. func waitFor(f func() bool) bool {
  492. // TODO(shadams): Find a better way to deflake these tests.
  493. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
  494. defer cancel()
  495. err := cinternal.Retry(ctx,
  496. gax.Backoff{Initial: time.Second, Multiplier: 2},
  497. func() (bool, error) { return f(), nil })
  498. return err == nil
  499. }
  500. // Interleave a lot of Log and Flush calls, to induce race conditions.
  501. // Run this test with:
  502. // go test -run LogFlushRace -race -count 100
  503. func TestLogFlushRace(t *testing.T) {
  504. initLogs(ctx) // Generate new testLogID
  505. lg := client.Logger(testLogID,
  506. logging.ConcurrentWriteLimit(5), // up to 5 concurrent log writes
  507. logging.EntryCountThreshold(100)) // small bundle size to increase interleaving
  508. var wgf, wgl sync.WaitGroup
  509. donec := make(chan struct{})
  510. for i := 0; i < 10; i++ {
  511. wgl.Add(1)
  512. go func() {
  513. defer wgl.Done()
  514. for j := 0; j < 1e4; j++ {
  515. lg.Log(logging.Entry{Payload: "the payload"})
  516. }
  517. }()
  518. }
  519. for i := 0; i < 5; i++ {
  520. wgf.Add(1)
  521. go func() {
  522. defer wgf.Done()
  523. for {
  524. select {
  525. case <-donec:
  526. return
  527. case <-time.After(time.Duration(rand.Intn(5)) * time.Millisecond):
  528. lg.Flush()
  529. }
  530. }
  531. }()
  532. }
  533. wgl.Wait()
  534. close(donec)
  535. wgf.Wait()
  536. }
  537. // Test the throughput of concurrent writers.
  538. // TODO(jba): when 1.8 is out, use sub-benchmarks.
  539. func BenchmarkConcurrentWrites1(b *testing.B) {
  540. benchmarkConcurrentWrites(b, 1)
  541. }
  542. func BenchmarkConcurrentWrites2(b *testing.B) {
  543. benchmarkConcurrentWrites(b, 2)
  544. }
  545. func BenchmarkConcurrentWrites4(b *testing.B) {
  546. benchmarkConcurrentWrites(b, 4)
  547. }
  548. func BenchmarkConcurrentWrites8(b *testing.B) {
  549. benchmarkConcurrentWrites(b, 8)
  550. }
  551. func BenchmarkConcurrentWrites16(b *testing.B) {
  552. benchmarkConcurrentWrites(b, 16)
  553. }
  554. func BenchmarkConcurrentWrites32(b *testing.B) {
  555. benchmarkConcurrentWrites(b, 32)
  556. }
  557. func benchmarkConcurrentWrites(b *testing.B, c int) {
  558. if !integrationTest {
  559. b.Skip("only makes sense when running against production service")
  560. }
  561. b.StopTimer()
  562. lg := client.Logger(testLogID, logging.ConcurrentWriteLimit(c), logging.EntryCountThreshold(1000))
  563. const (
  564. nEntries = 1e5
  565. payload = "the quick brown fox jumps over the lazy dog"
  566. )
  567. b.SetBytes(int64(nEntries * len(payload)))
  568. b.StartTimer()
  569. for i := 0; i < b.N; i++ {
  570. for j := 0; j < nEntries; j++ {
  571. lg.Log(logging.Entry{Payload: payload})
  572. }
  573. lg.Flush()
  574. }
  575. }