github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/logs_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "math/rand" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/juju/loggo" 13 "github.com/juju/names" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/mgo.v2" 17 "gopkg.in/mgo.v2/bson" 18 19 "github.com/juju/juju/state" 20 coretesting "github.com/juju/juju/testing" 21 ) 22 23 type LogsSuite struct { 24 ConnSuite 25 logsColl *mgo.Collection 26 } 27 28 var _ = gc.Suite(&LogsSuite{}) 29 30 func (s *LogsSuite) SetUpTest(c *gc.C) { 31 s.ConnSuite.SetUpTest(c) 32 33 session := s.State.MongoSession() 34 s.logsColl = session.DB("logs").C("logs") 35 } 36 37 func (s *LogsSuite) TestLastSentLoggerSetGet(c *gc.C) { 38 logger0 := state.NewLastSentLogger(s.State, "test-sink0") 39 logger1 := state.NewLastSentLogger(s.State, "test-sink1") 40 t := time.Date(2016, 04, 15, 16, 0, 0, 42, time.UTC) 41 err := logger0.Set(t) 42 c.Assert(err, jc.ErrorIsNil) 43 t1, err := logger0.Get() 44 c.Assert(err, jc.ErrorIsNil) 45 c.Assert(t1, gc.DeepEquals, t) 46 t2 := t.Add(time.Hour) 47 err = logger0.Set(t2) 48 c.Assert(err, jc.ErrorIsNil) 49 t3, err := logger0.Get() 50 c.Assert(err, jc.ErrorIsNil) 51 c.Assert(t3, gc.DeepEquals, t2) 52 _, err = logger1.Get() 53 c.Assert(err, gc.ErrorMatches, state.ErrNeverForwarded.Error()) 54 55 t5 := time.Date(2016, 4, 15, 16, 0, 0, 43, time.Local) 56 err = logger1.Set(t5) 57 c.Assert(err, jc.ErrorIsNil) 58 t6, err := logger1.Get() 59 c.Assert(err, jc.ErrorIsNil) 60 c.Assert(t6, gc.DeepEquals, t5.UTC()) 61 } 62 63 func (s *LogsSuite) TestLastSentLoggerNoSet(c *gc.C) { 64 logger := state.NewLastSentLogger(s.State, "test") 65 _, err := logger.Get() 66 c.Assert(err, gc.ErrorMatches, state.ErrNeverForwarded.Error()) 67 } 68 69 func (s *LogsSuite) TestIndexesCreated(c *gc.C) { 70 // Indexes should be created on the logs collection when state is opened. 71 indexes, err := s.logsColl.Indexes() 72 c.Assert(err, jc.ErrorIsNil) 73 var keys []string 74 for _, index := range indexes { 75 keys = append(keys, strings.Join(index.Key, "-")) 76 } 77 c.Assert(keys, jc.SameContents, []string{ 78 "_id", // default index 79 "e-t", // model-uuid and timestamp 80 "e-n", // model-uuid and entity 81 }) 82 } 83 84 func (s *LogsSuite) TestDbLogger(c *gc.C) { 85 logger := state.NewDbLogger(s.State, names.NewMachineTag("22")) 86 defer logger.Close() 87 t0 := time.Now().Truncate(time.Millisecond) // MongoDB only stores timestamps with ms precision. 88 logger.Log(t0, "some.where", "foo.go:99", loggo.INFO, "all is well") 89 t1 := t0.Add(time.Second) 90 logger.Log(t1, "else.where", "bar.go:42", loggo.ERROR, "oh noes") 91 92 var docs []bson.M 93 err := s.logsColl.Find(nil).Sort("t").All(&docs) 94 c.Assert(err, jc.ErrorIsNil) 95 c.Assert(docs, gc.HasLen, 2) 96 97 c.Assert(docs[0]["t"], gc.Equals, t0) 98 c.Assert(docs[0]["e"], gc.Equals, s.State.ModelUUID()) 99 c.Assert(docs[0]["n"], gc.Equals, "machine-22") 100 c.Assert(docs[0]["m"], gc.Equals, "some.where") 101 c.Assert(docs[0]["l"], gc.Equals, "foo.go:99") 102 c.Assert(docs[0]["v"], gc.Equals, int(loggo.INFO)) 103 c.Assert(docs[0]["x"], gc.Equals, "all is well") 104 105 c.Assert(docs[1]["t"], gc.Equals, t1) 106 c.Assert(docs[1]["e"], gc.Equals, s.State.ModelUUID()) 107 c.Assert(docs[1]["n"], gc.Equals, "machine-22") 108 c.Assert(docs[1]["m"], gc.Equals, "else.where") 109 c.Assert(docs[1]["l"], gc.Equals, "bar.go:42") 110 c.Assert(docs[1]["v"], gc.Equals, int(loggo.ERROR)) 111 c.Assert(docs[1]["x"], gc.Equals, "oh noes") 112 } 113 114 func (s *LogsSuite) TestPruneLogsByTime(c *gc.C) { 115 dbLogger := state.NewDbLogger(s.State, names.NewMachineTag("22")) 116 defer dbLogger.Close() 117 log := func(t time.Time, msg string) { 118 err := dbLogger.Log(t, "module", "loc", loggo.INFO, msg) 119 c.Assert(err, jc.ErrorIsNil) 120 } 121 122 now := time.Now() 123 maxLogTime := now.Add(-time.Minute) 124 log(now, "keep") 125 log(maxLogTime.Add(time.Second), "keep") 126 log(maxLogTime, "keep") 127 log(maxLogTime.Add(-time.Second), "prune") 128 log(maxLogTime.Add(-(2 * time.Second)), "prune") 129 130 noPruneMB := 100 131 err := state.PruneLogs(s.State, maxLogTime, noPruneMB) 132 c.Assert(err, jc.ErrorIsNil) 133 134 // After pruning there should just be 3 "keep" messages left. 135 var docs []bson.M 136 err = s.logsColl.Find(nil).All(&docs) 137 c.Assert(err, jc.ErrorIsNil) 138 c.Assert(docs, gc.HasLen, 3) 139 for _, doc := range docs { 140 c.Assert(doc["x"], gc.Equals, "keep") 141 } 142 } 143 144 func (s *LogsSuite) TestPruneLogsBySize(c *gc.C) { 145 // Set up 3 models and generate different amounts of logs 146 // for them. 147 now := time.Now().Truncate(time.Millisecond) 148 149 s0 := s.State 150 startingLogsS0 := 10 151 s.generateLogs(c, s0, now, startingLogsS0) 152 153 s1 := s.Factory.MakeModel(c, nil) 154 defer s1.Close() 155 startingLogsS1 := 10000 156 s.generateLogs(c, s1, now, startingLogsS1) 157 158 s2 := s.Factory.MakeModel(c, nil) 159 defer s2.Close() 160 startingLogsS2 := 12000 161 s.generateLogs(c, s2, now, startingLogsS2) 162 163 // Prune logs collection back to 1 MiB. 164 tsNoPrune := time.Now().Add(-3 * 24 * time.Hour) 165 err := state.PruneLogs(s.State, tsNoPrune, 1) 166 c.Assert(err, jc.ErrorIsNil) 167 168 // Logs for first env should not be touched. 169 c.Assert(s.countLogs(c, s0), gc.Equals, startingLogsS0) 170 171 // Logs for second env should be pruned. 172 c.Assert(s.countLogs(c, s1), jc.LessThan, startingLogsS1) 173 174 // Logs for third env should be pruned to a similar level as 175 // second env. 176 c.Assert(s.countLogs(c, s2), jc.LessThan, startingLogsS1) 177 178 // Ensure that the latest log records are still there. 179 assertLatestTs := func(st *state.State) { 180 var doc bson.M 181 err := s.logsColl.Find(bson.M{"e": st.ModelUUID()}).Sort("-t").One(&doc) 182 c.Assert(err, jc.ErrorIsNil) 183 c.Assert(doc["t"].(time.Time), gc.Equals, now) 184 } 185 assertLatestTs(s0) 186 assertLatestTs(s1) 187 assertLatestTs(s2) 188 } 189 190 func (s *LogsSuite) generateLogs(c *gc.C, st *state.State, endTime time.Time, count int) { 191 dbLogger := state.NewDbLogger(st, names.NewMachineTag("0")) 192 defer dbLogger.Close() 193 for i := 0; i < count; i++ { 194 ts := endTime.Add(-time.Duration(i) * time.Second) 195 err := dbLogger.Log(ts, "module", "loc", loggo.INFO, "message") 196 c.Assert(err, jc.ErrorIsNil) 197 } 198 } 199 200 func (s *LogsSuite) countLogs(c *gc.C, st *state.State) int { 201 count, err := s.logsColl.Find(bson.M{"e": st.ModelUUID()}).Count() 202 c.Assert(err, jc.ErrorIsNil) 203 return count 204 } 205 206 type LogTailerSuite struct { 207 ConnSuite 208 logsColl *mgo.Collection 209 oplogColl *mgo.Collection 210 otherState *state.State 211 } 212 213 var _ = gc.Suite(&LogTailerSuite{}) 214 215 func (s *LogTailerSuite) SetUpTest(c *gc.C) { 216 s.ConnSuite.SetUpTest(c) 217 218 session := s.State.MongoSession() 219 s.logsColl = session.DB("logs").C("logs") 220 221 // Create a fake oplog collection. 222 s.oplogColl = session.DB("logs").C("oplog.fake") 223 err := s.oplogColl.Create(&mgo.CollectionInfo{ 224 Capped: true, 225 MaxBytes: 1024 * 1024, 226 }) 227 c.Assert(err, jc.ErrorIsNil) 228 s.AddCleanup(func(*gc.C) { s.oplogColl.DropCollection() }) 229 230 s.otherState = s.NewStateForModelNamed(c, "test-model") 231 c.Assert(s.otherState, gc.NotNil) 232 s.AddCleanup(func(c *gc.C) { 233 err := s.otherState.Close() 234 c.Assert(err, jc.ErrorIsNil) 235 }) 236 } 237 238 func (s *LogTailerSuite) TestTimeFiltering(c *gc.C) { 239 // Add 10 logs that shouldn't be returned. 240 threshT := time.Now() 241 s.writeLogsT(c, 242 threshT.Add(-5*time.Second), threshT.Add(-time.Millisecond), 5, 243 logTemplate{Message: "dont want"}, 244 ) 245 246 // Add 5 logs that should be returned. 247 want := logTemplate{Message: "want"} 248 s.writeLogsT(c, threshT, threshT.Add(5*time.Second), 5, want) 249 tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{ 250 StartTime: threshT, 251 Oplog: s.oplogColl, 252 }) 253 c.Assert(err, jc.ErrorIsNil) 254 defer tailer.Stop() 255 s.assertTailer(c, tailer, 5, want) 256 257 // Write more logs. These will be read from the the oplog. 258 want2 := logTemplate{Message: "want 2"} 259 s.writeLogsT(c, threshT.Add(6*time.Second), threshT.Add(10*time.Second), 5, want2) 260 s.assertTailer(c, tailer, 5, want2) 261 262 } 263 264 func (s *LogTailerSuite) TestOplogTransition(c *gc.C) { 265 // Ensure that logs aren't repeated as the log tailer moves from 266 // reading from the logs collection to tailing the oplog. 267 // 268 // All logs are written out with the same timestamp to create a 269 // challenging scenario for the tailer. 270 271 for i := 0; i < 5; i++ { 272 s.writeLogs(c, 1, logTemplate{Message: strconv.Itoa(i)}) 273 } 274 275 tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{ 276 Oplog: s.oplogColl, 277 }) 278 c.Assert(err, jc.ErrorIsNil) 279 defer tailer.Stop() 280 for i := 0; i < 5; i++ { 281 s.assertTailer(c, tailer, 1, logTemplate{Message: strconv.Itoa(i)}) 282 } 283 284 // Write more logs. These will be read from the the oplog. 285 for i := 5; i < 10; i++ { 286 lt := logTemplate{Message: strconv.Itoa(i)} 287 s.writeLogs(c, 2, lt) 288 s.assertTailer(c, tailer, 2, lt) 289 } 290 } 291 292 func (s *LogTailerSuite) TestModelFiltering(c *gc.C) { 293 good := logTemplate{Message: "good"} 294 writeLogs := func() { 295 s.writeLogs(c, 1, logTemplate{ 296 ModelUUID: "someuuid0", 297 Message: "bad", 298 }) 299 s.writeLogs(c, 1, logTemplate{ 300 ModelUUID: "someuuid1", 301 Message: "bad", 302 }) 303 s.writeLogs(c, 1, good) 304 } 305 306 assert := func(tailer state.LogTailer) { 307 // Only the entries the s.State's UUID should be reported. 308 s.assertTailer(c, tailer, 1, good) 309 } 310 311 s.checkLogTailerFiltering(c, s.otherState, &state.LogTailerParams{}, writeLogs, assert) 312 } 313 314 func (s *LogTailerSuite) TestTailingLogsForAllModels(c *gc.C) { 315 writeLogs := func() { 316 s.writeLogs(c, 1, logTemplate{ 317 ModelUUID: "someuuid0", 318 Message: "bad", 319 }) 320 s.writeLogs(c, 1, logTemplate{ 321 ModelUUID: "someuuid1", 322 Message: "bad", 323 }) 324 s.writeLogs(c, 1, logTemplate{Message: "good"}) 325 } 326 327 assert := func(tailer state.LogTailer) { 328 messagesFrom := map[string]bool{ 329 s.otherState.ModelUUID(): false, 330 "someuuid0": false, 331 "someuuid1": false, 332 } 333 defer func() { 334 c.Assert(messagesFrom, gc.HasLen, 3) 335 for _, v := range messagesFrom { 336 c.Assert(v, jc.IsTrue) 337 } 338 }() 339 count := 0 340 for { 341 select { 342 case log := <-tailer.Logs(): 343 messagesFrom[log.ModelUUID] = true 344 count++ 345 c.Logf("count %d", count) 346 if count >= 3 { 347 return 348 } 349 case <-time.After(coretesting.ShortWait): 350 c.Fatalf("timeout waiting for logs %d", count) 351 } 352 } 353 } 354 s.checkLogTailerFiltering(c, s.State, &state.LogTailerParams{AllModels: true}, writeLogs, assert) 355 } 356 357 func (s *LogTailerSuite) TestTailingLogsOnlyForControllerModel(c *gc.C) { 358 writeLogs := func() { 359 s.writeLogs(c, 1, logTemplate{ 360 ModelUUID: s.otherState.ModelUUID(), 361 Message: "bad"}, 362 ) 363 s.writeLogs(c, 1, logTemplate{ 364 ModelUUID: s.State.ModelUUID(), 365 Message: "good1", 366 }) 367 s.writeLogs(c, 1, logTemplate{ 368 ModelUUID: s.State.ModelUUID(), 369 Message: "good2", 370 }) 371 } 372 373 assert := func(tailer state.LogTailer) { 374 messages := map[string]bool{} 375 defer func() { 376 c.Assert(messages, gc.HasLen, 2) 377 for m, _ := range messages { 378 if m != "good1" && m != "good2" { 379 c.Fatalf("received message: %v", m) 380 } 381 } 382 }() 383 count := 0 384 for { 385 select { 386 case log := <-tailer.Logs(): 387 c.Assert(log.ModelUUID, gc.Equals, s.State.ModelUUID()) 388 messages[log.Message] = true 389 count++ 390 c.Logf("count %d", count) 391 if count >= 2 { 392 return 393 } 394 case <-time.After(coretesting.ShortWait): 395 c.Fatalf("timeout waiting for logs %d", count) 396 } 397 } 398 } 399 s.checkLogTailerFiltering(c, s.State, &state.LogTailerParams{}, writeLogs, assert) 400 } 401 402 func (s *LogTailerSuite) TestTailingAllLogsFromNonController(c *gc.C) { 403 _, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{AllModels: true}) 404 c.Assert(err, gc.ErrorMatches, "not allowed to tail logs from all models: not a controller") 405 } 406 407 func (s *LogTailerSuite) TestLevelFiltering(c *gc.C) { 408 info := logTemplate{Level: loggo.INFO} 409 error := logTemplate{Level: loggo.ERROR} 410 writeLogs := func() { 411 s.writeLogs(c, 1, logTemplate{Level: loggo.DEBUG}) 412 s.writeLogs(c, 1, info) 413 s.writeLogs(c, 1, error) 414 } 415 params := &state.LogTailerParams{ 416 MinLevel: loggo.INFO, 417 } 418 assert := func(tailer state.LogTailer) { 419 s.assertTailer(c, tailer, 1, info) 420 s.assertTailer(c, tailer, 1, error) 421 } 422 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 423 } 424 425 func (s *LogTailerSuite) TestInitialLines(c *gc.C) { 426 expected := logTemplate{Message: "want"} 427 s.writeLogs(c, 3, logTemplate{Message: "dont want"}) 428 s.writeLogs(c, 5, expected) 429 430 tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{ 431 InitialLines: 5, 432 }) 433 c.Assert(err, jc.ErrorIsNil) 434 defer tailer.Stop() 435 436 // Should see just the last 5 lines as requested. 437 s.assertTailer(c, tailer, 5, expected) 438 } 439 440 func (s *LogTailerSuite) TestInitialLinesWithNotEnoughLines(c *gc.C) { 441 expected := logTemplate{Message: "want"} 442 s.writeLogs(c, 2, expected) 443 444 tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{ 445 InitialLines: 5, 446 }) 447 c.Assert(err, jc.ErrorIsNil) 448 defer tailer.Stop() 449 450 // Should see just the 2 lines that existed, even though 5 were 451 // asked for. 452 s.assertTailer(c, tailer, 2, expected) 453 } 454 455 func (s *LogTailerSuite) TestNoTail(c *gc.C) { 456 expected := logTemplate{Message: "want"} 457 s.writeLogs(c, 2, expected) 458 459 // Write a log entry that's only in the oplog. 460 doc := s.logTemplateToDoc(logTemplate{Message: "dont want"}, time.Now()) 461 err := s.writeLogToOplog(doc) 462 c.Assert(err, jc.ErrorIsNil) 463 464 tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{ 465 NoTail: true, 466 }) 467 c.Assert(err, jc.ErrorIsNil) 468 // Not strictly necessary, just in case NoTail doesn't work in the test. 469 defer tailer.Stop() 470 471 // Logs only in the oplog shouldn't be reported and the tailer 472 // should stop itself once the log collection has been read. 473 s.assertTailer(c, tailer, 2, expected) 474 select { 475 case _, ok := <-tailer.Logs(): 476 if ok { 477 c.Fatal("shouldn't be any further logs") 478 } 479 case <-time.After(coretesting.LongWait): 480 c.Fatal("timed out waiting for logs channel to close") 481 } 482 483 select { 484 case <-tailer.Dying(): 485 // Success. 486 case <-time.After(coretesting.LongWait): 487 c.Fatal("tailer didn't stop itself") 488 } 489 } 490 491 func (s *LogTailerSuite) TestIncludeEntity(c *gc.C) { 492 machine0 := logTemplate{Entity: names.NewMachineTag("0")} 493 foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")} 494 foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")} 495 writeLogs := func() { 496 s.writeLogs(c, 3, machine0) 497 s.writeLogs(c, 2, foo0) 498 s.writeLogs(c, 1, foo1) 499 s.writeLogs(c, 3, machine0) 500 } 501 params := &state.LogTailerParams{ 502 IncludeEntity: []string{ 503 "unit-foo-0", 504 "unit-foo-1", 505 }, 506 } 507 assert := func(tailer state.LogTailer) { 508 s.assertTailer(c, tailer, 2, foo0) 509 s.assertTailer(c, tailer, 1, foo1) 510 } 511 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 512 } 513 514 func (s *LogTailerSuite) TestIncludeEntityWildcard(c *gc.C) { 515 machine0 := logTemplate{Entity: names.NewMachineTag("0")} 516 foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")} 517 foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")} 518 writeLogs := func() { 519 s.writeLogs(c, 3, machine0) 520 s.writeLogs(c, 2, foo0) 521 s.writeLogs(c, 1, foo1) 522 s.writeLogs(c, 3, machine0) 523 } 524 params := &state.LogTailerParams{ 525 IncludeEntity: []string{ 526 "unit-foo*", 527 }, 528 } 529 assert := func(tailer state.LogTailer) { 530 s.assertTailer(c, tailer, 2, foo0) 531 s.assertTailer(c, tailer, 1, foo1) 532 } 533 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 534 } 535 536 func (s *LogTailerSuite) TestExcludeEntity(c *gc.C) { 537 machine0 := logTemplate{Entity: names.NewMachineTag("0")} 538 foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")} 539 foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")} 540 writeLogs := func() { 541 s.writeLogs(c, 3, machine0) 542 s.writeLogs(c, 2, foo0) 543 s.writeLogs(c, 1, foo1) 544 s.writeLogs(c, 3, machine0) 545 } 546 params := &state.LogTailerParams{ 547 ExcludeEntity: []string{ 548 "machine-0", 549 "unit-foo-0", 550 }, 551 } 552 assert := func(tailer state.LogTailer) { 553 s.assertTailer(c, tailer, 1, foo1) 554 } 555 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 556 } 557 558 func (s *LogTailerSuite) TestExcludeEntityWildcard(c *gc.C) { 559 machine0 := logTemplate{Entity: names.NewMachineTag("0")} 560 foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")} 561 foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")} 562 writeLogs := func() { 563 s.writeLogs(c, 3, machine0) 564 s.writeLogs(c, 2, foo0) 565 s.writeLogs(c, 1, foo1) 566 s.writeLogs(c, 3, machine0) 567 } 568 params := &state.LogTailerParams{ 569 ExcludeEntity: []string{ 570 "machine*", 571 "unit-*-0", 572 }, 573 } 574 assert := func(tailer state.LogTailer) { 575 s.assertTailer(c, tailer, 1, foo1) 576 } 577 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 578 } 579 func (s *LogTailerSuite) TestIncludeModule(c *gc.C) { 580 mod0 := logTemplate{Module: "foo.bar"} 581 mod1 := logTemplate{Module: "juju.thing"} 582 subMod1 := logTemplate{Module: "juju.thing.hai"} 583 mod2 := logTemplate{Module: "elsewhere"} 584 writeLogs := func() { 585 s.writeLogs(c, 1, mod0) 586 s.writeLogs(c, 1, mod1) 587 s.writeLogs(c, 1, mod0) 588 s.writeLogs(c, 1, subMod1) 589 s.writeLogs(c, 1, mod0) 590 s.writeLogs(c, 1, mod2) 591 } 592 params := &state.LogTailerParams{ 593 IncludeModule: []string{"juju.thing", "elsewhere"}, 594 } 595 assert := func(tailer state.LogTailer) { 596 s.assertTailer(c, tailer, 1, mod1) 597 s.assertTailer(c, tailer, 1, subMod1) 598 s.assertTailer(c, tailer, 1, mod2) 599 } 600 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 601 } 602 603 func (s *LogTailerSuite) TestExcludeModule(c *gc.C) { 604 mod0 := logTemplate{Module: "foo.bar"} 605 mod1 := logTemplate{Module: "juju.thing"} 606 subMod1 := logTemplate{Module: "juju.thing.hai"} 607 mod2 := logTemplate{Module: "elsewhere"} 608 writeLogs := func() { 609 s.writeLogs(c, 1, mod0) 610 s.writeLogs(c, 1, mod1) 611 s.writeLogs(c, 1, mod0) 612 s.writeLogs(c, 1, subMod1) 613 s.writeLogs(c, 1, mod0) 614 s.writeLogs(c, 1, mod2) 615 } 616 params := &state.LogTailerParams{ 617 ExcludeModule: []string{"juju.thing", "elsewhere"}, 618 } 619 assert := func(tailer state.LogTailer) { 620 s.assertTailer(c, tailer, 2, mod0) 621 } 622 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 623 } 624 625 func (s *LogTailerSuite) TestIncludeExcludeModule(c *gc.C) { 626 foo := logTemplate{Module: "foo"} 627 bar := logTemplate{Module: "bar"} 628 barSub := logTemplate{Module: "bar.thing"} 629 baz := logTemplate{Module: "baz"} 630 qux := logTemplate{Module: "qux"} 631 writeLogs := func() { 632 s.writeLogs(c, 1, foo) 633 s.writeLogs(c, 1, bar) 634 s.writeLogs(c, 1, barSub) 635 s.writeLogs(c, 1, baz) 636 s.writeLogs(c, 1, qux) 637 } 638 params := &state.LogTailerParams{ 639 IncludeModule: []string{"foo", "bar", "qux"}, 640 ExcludeModule: []string{"foo", "bar"}, 641 } 642 assert := func(tailer state.LogTailer) { 643 // Except just "qux" because "foo" and "bar" were included and 644 // then excluded. 645 s.assertTailer(c, tailer, 1, qux) 646 } 647 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 648 } 649 650 func (s *LogTailerSuite) checkLogTailerFiltering( 651 c *gc.C, 652 st *state.State, 653 params *state.LogTailerParams, 654 writeLogs func(), 655 assertTailer func(state.LogTailer), 656 ) { 657 // Check the tailer does the right thing when reading from the 658 // logs collection. 659 writeLogs() 660 params.Oplog = s.oplogColl 661 tailer, err := state.NewLogTailer(st, params) 662 c.Assert(err, jc.ErrorIsNil) 663 defer tailer.Stop() 664 assertTailer(tailer) 665 666 // Now write out logs and check the tailer again. These will be 667 // read from the oplog. 668 writeLogs() 669 assertTailer(tailer) 670 } 671 672 type logTemplate struct { 673 ModelUUID string 674 Entity names.Tag 675 Module string 676 Location string 677 Level loggo.Level 678 Message string 679 } 680 681 // writeLogs creates count log messages at the current time using 682 // the supplied template. As well as writing to the logs collection, 683 // entries are also made into the fake oplog collection. 684 func (s *LogTailerSuite) writeLogs(c *gc.C, count int, lt logTemplate) { 685 t := time.Now() 686 s.writeLogsT(c, t, t, count, lt) 687 } 688 689 // writeLogsT creates count log messages between startTime and 690 // endTime using the supplied template. As well as writing to the logs 691 // collection, entries are also made into the fake oplog collection. 692 func (s *LogTailerSuite) writeLogsT(c *gc.C, startTime, endTime time.Time, count int, lt logTemplate) { 693 interval := endTime.Sub(startTime) / time.Duration(count) 694 t := startTime 695 for i := 0; i < count; i++ { 696 doc := s.logTemplateToDoc(lt, t) 697 err := s.writeLogToOplog(doc) 698 c.Assert(err, jc.ErrorIsNil) 699 err = s.logsColl.Insert(doc) 700 c.Assert(err, jc.ErrorIsNil) 701 t = t.Add(interval) 702 } 703 } 704 705 // writeLogToOplog writes out a log record to the a (probably fake) 706 // oplog collection. 707 func (s *LogTailerSuite) writeLogToOplog(doc interface{}) error { 708 return s.oplogColl.Insert(bson.D{ 709 {"ts", bson.MongoTimestamp(time.Now().Unix() << 32)}, // an approximation which will do 710 {"h", rand.Int63()}, // again, a suitable fake 711 {"op", "i"}, // this will always be an insert 712 {"ns", "logs.logs"}, 713 {"o", doc}, 714 }) 715 } 716 717 func (s *LogTailerSuite) normaliseLogTemplate(lt *logTemplate) { 718 if lt.ModelUUID == "" { 719 lt.ModelUUID = s.otherState.ModelUUID() 720 } 721 if lt.Entity == nil { 722 lt.Entity = names.NewMachineTag("0") 723 } 724 if lt.Module == "" { 725 lt.Module = "module" 726 } 727 if lt.Location == "" { 728 lt.Location = "loc" 729 } 730 if lt.Level == loggo.UNSPECIFIED { 731 lt.Level = loggo.INFO 732 } 733 if lt.Message == "" { 734 lt.Message = "message" 735 } 736 } 737 738 func (s *LogTailerSuite) logTemplateToDoc(lt logTemplate, t time.Time) interface{} { 739 s.normaliseLogTemplate(<) 740 return state.MakeLogDoc( 741 lt.ModelUUID, 742 lt.Entity, 743 t, 744 lt.Module, 745 lt.Location, 746 lt.Level, 747 lt.Message, 748 ) 749 } 750 751 func (s *LogTailerSuite) assertTailer(c *gc.C, tailer state.LogTailer, expectedCount int, lt logTemplate) { 752 s.normaliseLogTemplate(<) 753 754 timeout := time.After(coretesting.LongWait) 755 count := 0 756 for { 757 select { 758 case log, ok := <-tailer.Logs(): 759 if !ok { 760 c.Fatalf("tailer died unexpectedly: %v", tailer.Err()) 761 } 762 c.Assert(log.Entity, gc.Equals, lt.Entity.String()) 763 c.Assert(log.Module, gc.Equals, lt.Module) 764 c.Assert(log.Location, gc.Equals, lt.Location) 765 c.Assert(log.Level, gc.Equals, lt.Level) 766 c.Assert(log.Message, gc.Equals, lt.Message) 767 c.Assert(log.ModelUUID, gc.Equals, lt.ModelUUID) 768 count++ 769 if count == expectedCount { 770 return 771 } 772 case <-timeout: 773 c.Fatalf("timed out waiting for logs (received %d)", count) 774 } 775 } 776 }