github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/debuglog_internal_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // This is an internal package test. 5 6 package apiserver 7 8 import ( 9 "bytes" 10 "net/url" 11 "os" 12 "path/filepath" 13 "time" 14 15 "github.com/juju/loggo" 16 jc "github.com/juju/testing/checkers" 17 gc "launchpad.net/gocheck" 18 19 "github.com/juju/juju/testing" 20 ) 21 22 type debugInternalSuite struct { 23 testing.BaseSuite 24 } 25 26 var _ = gc.Suite(&debugInternalSuite{}) 27 28 func (s *debugInternalSuite) TestParseLogLine(c *gc.C) { 29 line := "machine-0: 2014-03-24 22:34:25 INFO juju.cmd.jujud machine.go:127 machine agent machine-0 start (1.17.7.1-trusty-amd64 [gc])" 30 logLine := parseLogLine(line) 31 c.Assert(logLine.line, gc.Equals, line) 32 c.Assert(logLine.agent, gc.Equals, "machine-0") 33 c.Assert(logLine.level, gc.Equals, loggo.INFO) 34 c.Assert(logLine.module, gc.Equals, "juju.cmd.jujud") 35 } 36 37 func (s *debugInternalSuite) TestParseLogLineMachineMultiline(c *gc.C) { 38 line := "machine-1: continuation line" 39 logLine := parseLogLine(line) 40 c.Assert(logLine.line, gc.Equals, line) 41 c.Assert(logLine.agent, gc.Equals, "machine-1") 42 c.Assert(logLine.level, gc.Equals, loggo.UNSPECIFIED) 43 c.Assert(logLine.module, gc.Equals, "") 44 } 45 46 func (s *debugInternalSuite) TestParseLogLineInvalid(c *gc.C) { 47 line := "not a full line" 48 logLine := parseLogLine(line) 49 c.Assert(logLine.line, gc.Equals, line) 50 c.Assert(logLine.agent, gc.Equals, "") 51 c.Assert(logLine.level, gc.Equals, loggo.UNSPECIFIED) 52 c.Assert(logLine.module, gc.Equals, "") 53 } 54 55 func checkLevel(logValue, streamValue loggo.Level) bool { 56 stream := &logStream{} 57 if streamValue != loggo.UNSPECIFIED { 58 stream.filterLevel = streamValue 59 } 60 line := &logLine{level: logValue} 61 return stream.checkLevel(line) 62 } 63 64 func (s *debugInternalSuite) TestCheckLevel(c *gc.C) { 65 c.Check(checkLevel(loggo.UNSPECIFIED, loggo.UNSPECIFIED), jc.IsTrue) 66 c.Check(checkLevel(loggo.TRACE, loggo.UNSPECIFIED), jc.IsTrue) 67 c.Check(checkLevel(loggo.DEBUG, loggo.UNSPECIFIED), jc.IsTrue) 68 c.Check(checkLevel(loggo.INFO, loggo.UNSPECIFIED), jc.IsTrue) 69 c.Check(checkLevel(loggo.WARNING, loggo.UNSPECIFIED), jc.IsTrue) 70 c.Check(checkLevel(loggo.ERROR, loggo.UNSPECIFIED), jc.IsTrue) 71 c.Check(checkLevel(loggo.CRITICAL, loggo.UNSPECIFIED), jc.IsTrue) 72 73 c.Check(checkLevel(loggo.UNSPECIFIED, loggo.TRACE), jc.IsFalse) 74 c.Check(checkLevel(loggo.TRACE, loggo.TRACE), jc.IsTrue) 75 c.Check(checkLevel(loggo.DEBUG, loggo.TRACE), jc.IsTrue) 76 c.Check(checkLevel(loggo.INFO, loggo.TRACE), jc.IsTrue) 77 c.Check(checkLevel(loggo.WARNING, loggo.TRACE), jc.IsTrue) 78 c.Check(checkLevel(loggo.ERROR, loggo.TRACE), jc.IsTrue) 79 c.Check(checkLevel(loggo.CRITICAL, loggo.TRACE), jc.IsTrue) 80 81 c.Check(checkLevel(loggo.UNSPECIFIED, loggo.INFO), jc.IsFalse) 82 c.Check(checkLevel(loggo.TRACE, loggo.INFO), jc.IsFalse) 83 c.Check(checkLevel(loggo.DEBUG, loggo.INFO), jc.IsFalse) 84 c.Check(checkLevel(loggo.INFO, loggo.INFO), jc.IsTrue) 85 c.Check(checkLevel(loggo.WARNING, loggo.INFO), jc.IsTrue) 86 c.Check(checkLevel(loggo.ERROR, loggo.INFO), jc.IsTrue) 87 c.Check(checkLevel(loggo.CRITICAL, loggo.INFO), jc.IsTrue) 88 } 89 90 func checkIncludeEntity(logValue string, agent ...string) bool { 91 stream := &logStream{includeEntity: agent} 92 line := &logLine{agent: logValue} 93 return stream.checkIncludeEntity(line) 94 } 95 96 func (s *debugInternalSuite) TestCheckIncludeEntity(c *gc.C) { 97 c.Check(checkIncludeEntity("machine-0"), jc.IsTrue) 98 c.Check(checkIncludeEntity("machine-0", "machine-0"), jc.IsTrue) 99 c.Check(checkIncludeEntity("machine-1", "machine-0"), jc.IsFalse) 100 c.Check(checkIncludeEntity("machine-1", "machine-0", "machine-1"), jc.IsTrue) 101 c.Check(checkIncludeEntity("machine-0-lxc-0", "machine-0"), jc.IsFalse) 102 c.Check(checkIncludeEntity("machine-0-lxc-0", "machine-0*"), jc.IsTrue) 103 c.Check(checkIncludeEntity("machine-0-lxc-0", "machine-0-lxc-*"), jc.IsTrue) 104 } 105 106 func checkIncludeModule(logValue string, module ...string) bool { 107 stream := &logStream{includeModule: module} 108 line := &logLine{module: logValue} 109 return stream.checkIncludeModule(line) 110 } 111 112 func (s *debugInternalSuite) TestCheckIncludeModule(c *gc.C) { 113 c.Check(checkIncludeModule("juju"), jc.IsTrue) 114 c.Check(checkIncludeModule("juju", "juju"), jc.IsTrue) 115 c.Check(checkIncludeModule("juju", "juju.environ"), jc.IsFalse) 116 c.Check(checkIncludeModule("juju.provisioner", "juju"), jc.IsTrue) 117 c.Check(checkIncludeModule("juju.provisioner", "juju*"), jc.IsFalse) 118 c.Check(checkIncludeModule("juju.provisioner", "juju.environ"), jc.IsFalse) 119 c.Check(checkIncludeModule("unit.mysql/1", "juju", "unit"), jc.IsTrue) 120 } 121 122 func checkExcludeEntity(logValue string, agent ...string) bool { 123 stream := &logStream{excludeEntity: agent} 124 line := &logLine{agent: logValue} 125 return stream.exclude(line) 126 } 127 128 func (s *debugInternalSuite) TestCheckExcludeEntity(c *gc.C) { 129 c.Check(checkExcludeEntity("machine-0"), jc.IsFalse) 130 c.Check(checkExcludeEntity("machine-0", "machine-0"), jc.IsTrue) 131 c.Check(checkExcludeEntity("machine-1", "machine-0"), jc.IsFalse) 132 c.Check(checkExcludeEntity("machine-1", "machine-0", "machine-1"), jc.IsTrue) 133 c.Check(checkExcludeEntity("machine-0-lxc-0", "machine-0"), jc.IsFalse) 134 c.Check(checkExcludeEntity("machine-0-lxc-0", "machine-0*"), jc.IsTrue) 135 c.Check(checkExcludeEntity("machine-0-lxc-0", "machine-0-lxc-*"), jc.IsTrue) 136 } 137 138 func checkExcludeModule(logValue string, module ...string) bool { 139 stream := &logStream{excludeModule: module} 140 line := &logLine{module: logValue} 141 return stream.exclude(line) 142 } 143 144 func (s *debugInternalSuite) TestCheckExcludeModule(c *gc.C) { 145 c.Check(checkExcludeModule("juju"), jc.IsFalse) 146 c.Check(checkExcludeModule("juju", "juju"), jc.IsTrue) 147 c.Check(checkExcludeModule("juju", "juju.environ"), jc.IsFalse) 148 c.Check(checkExcludeModule("juju.provisioner", "juju"), jc.IsTrue) 149 c.Check(checkExcludeModule("juju.provisioner", "juju*"), jc.IsFalse) 150 c.Check(checkExcludeModule("juju.provisioner", "juju.environ"), jc.IsFalse) 151 c.Check(checkExcludeModule("unit.mysql/1", "juju", "unit"), jc.IsTrue) 152 } 153 154 func (s *debugInternalSuite) TestFilterLine(c *gc.C) { 155 stream := &logStream{ 156 filterLevel: loggo.INFO, 157 includeEntity: []string{"machine-0", "unit-mysql*"}, 158 includeModule: []string{"juju"}, 159 excludeEntity: []string{"unit-mysql-2"}, 160 excludeModule: []string{"juju.foo"}, 161 } 162 c.Check(stream.filterLine([]byte( 163 "machine-0: date time WARNING juju")), jc.IsTrue) 164 c.Check(stream.filterLine([]byte( 165 "machine-1: date time WARNING juju")), jc.IsFalse) 166 c.Check(stream.filterLine([]byte( 167 "unit-mysql-0: date time WARNING juju")), jc.IsTrue) 168 c.Check(stream.filterLine([]byte( 169 "unit-mysql-1: date time WARNING juju")), jc.IsTrue) 170 c.Check(stream.filterLine([]byte( 171 "unit-mysql-2: date time WARNING juju")), jc.IsFalse) 172 c.Check(stream.filterLine([]byte( 173 "unit-wordpress-0: date time WARNING juju")), jc.IsFalse) 174 c.Check(stream.filterLine([]byte( 175 "machine-0: date time DEBUG juju")), jc.IsFalse) 176 c.Check(stream.filterLine([]byte( 177 "machine-0: date time WARNING juju.foo.bar")), jc.IsFalse) 178 } 179 180 func (s *debugInternalSuite) TestCountedFilterLineWithLimit(c *gc.C) { 181 stream := &logStream{ 182 filterLevel: loggo.INFO, 183 maxLines: 5, 184 } 185 line := []byte("machine-0: date time WARNING juju") 186 c.Check(stream.countedFilterLine(line), jc.IsTrue) 187 c.Check(stream.countedFilterLine(line), jc.IsTrue) 188 c.Check(stream.countedFilterLine(line), jc.IsTrue) 189 c.Check(stream.countedFilterLine(line), jc.IsTrue) 190 c.Check(stream.countedFilterLine(line), jc.IsTrue) 191 c.Check(stream.countedFilterLine(line), jc.IsFalse) 192 c.Check(stream.countedFilterLine(line), jc.IsFalse) 193 } 194 195 type chanWriter struct { 196 ch chan []byte 197 } 198 199 func (w *chanWriter) Write(buf []byte) (n int, err error) { 200 bufcopy := append([]byte{}, buf...) 201 w.ch <- bufcopy 202 return len(buf), nil 203 } 204 205 func (s *debugInternalSuite) testStreamInternal(c *gc.C, fromTheStart bool, backlog, maxLines uint, expected, errMatch string) { 206 207 dir := c.MkDir() 208 logPath := filepath.Join(dir, "logfile.txt") 209 logFile, err := os.Create(logPath) 210 c.Assert(err, gc.IsNil) 211 defer logFile.Close() 212 logFileReader, err := os.Open(logPath) 213 c.Assert(err, gc.IsNil) 214 defer logFileReader.Close() 215 216 logFile.WriteString(`line 1 217 line 2 218 line 3 219 `) 220 stream := &logStream{ 221 fromTheStart: fromTheStart, 222 backlog: backlog, 223 maxLines: maxLines, 224 } 225 err = stream.positionLogFile(logFileReader) 226 c.Assert(err, gc.IsNil) 227 var output bytes.Buffer 228 writer := &chanWriter{make(chan []byte)} 229 stream.start(logFileReader, writer) 230 defer stream.logTailer.Wait() 231 232 go func() { 233 defer stream.tomb.Done() 234 stream.tomb.Kill(stream.loop()) 235 }() 236 237 logFile.WriteString("line 4\n") 238 logFile.WriteString("line 5\n") 239 240 timeout := time.After(testing.LongWait) 241 for output.String() != expected { 242 select { 243 case buf := <-writer.ch: 244 output.Write(buf) 245 case <-timeout: 246 c.Fatalf("expected data didn't arrive:\n\tobtained: %#v\n\texpected: %#v", output.String(), expected) 247 } 248 } 249 250 stream.logTailer.Stop() 251 252 err = stream.tomb.Wait() 253 if errMatch == "" { 254 c.Assert(err, gc.IsNil) 255 } else { 256 c.Assert(err, gc.ErrorMatches, errMatch) 257 } 258 } 259 260 func (s *debugInternalSuite) TestLogStreamLoopFromTheStart(c *gc.C) { 261 expected := `line 1 262 line 2 263 line 3 264 line 4 265 line 5 266 ` 267 s.testStreamInternal(c, true, 0, 0, expected, "") 268 } 269 270 func (s *debugInternalSuite) TestLogStreamLoopFromTheStartMaxLines(c *gc.C) { 271 expected := `line 1 272 line 2 273 line 3 274 ` 275 s.testStreamInternal(c, true, 0, 3, expected, "max lines reached") 276 } 277 278 func (s *debugInternalSuite) TestLogStreamLoopJustTail(c *gc.C) { 279 expected := `line 4 280 line 5 281 ` 282 s.testStreamInternal(c, false, 0, 0, expected, "") 283 } 284 285 func (s *debugInternalSuite) TestLogStreamLoopBackOneLimitTwo(c *gc.C) { 286 expected := `line 3 287 line 4 288 ` 289 s.testStreamInternal(c, false, 1, 2, expected, "max lines reached") 290 } 291 292 func (s *debugInternalSuite) TestLogStreamLoopTailMaxLinesNotYetReached(c *gc.C) { 293 expected := `line 4 294 line 5 295 ` 296 s.testStreamInternal(c, false, 0, 3, expected, "") 297 } 298 299 func assertStreamParams(c *gc.C, obtained, expected *logStream) { 300 c.Check(obtained.includeEntity, jc.DeepEquals, expected.includeEntity) 301 c.Check(obtained.includeModule, jc.DeepEquals, expected.includeModule) 302 c.Check(obtained.excludeEntity, jc.DeepEquals, expected.excludeEntity) 303 c.Check(obtained.excludeModule, jc.DeepEquals, expected.excludeModule) 304 c.Check(obtained.maxLines, gc.Equals, expected.maxLines) 305 c.Check(obtained.fromTheStart, gc.Equals, expected.fromTheStart) 306 c.Check(obtained.filterLevel, gc.Equals, expected.filterLevel) 307 c.Check(obtained.backlog, gc.Equals, expected.backlog) 308 } 309 310 func (s *debugInternalSuite) TestNewLogStream(c *gc.C) { 311 obtained, err := newLogStream(nil) 312 c.Assert(err, gc.IsNil) 313 assertStreamParams(c, obtained, &logStream{}) 314 315 values := url.Values{ 316 "includeEntity": []string{"machine-1*", "machine-2"}, 317 "includeModule": []string{"juju", "unit"}, 318 "excludeEntity": []string{"machine-1-lxc*"}, 319 "excludeModule": []string{"juju.provisioner"}, 320 "maxLines": []string{"300"}, 321 "backlog": []string{"100"}, 322 "level": []string{"INFO"}, 323 // OK, just a little nonsense 324 "replay": []string{"true"}, 325 } 326 expected := &logStream{ 327 includeEntity: []string{"machine-1*", "machine-2"}, 328 includeModule: []string{"juju", "unit"}, 329 excludeEntity: []string{"machine-1-lxc*"}, 330 excludeModule: []string{"juju.provisioner"}, 331 maxLines: 300, 332 backlog: 100, 333 filterLevel: loggo.INFO, 334 fromTheStart: true, 335 } 336 obtained, err = newLogStream(values) 337 c.Assert(err, gc.IsNil) 338 assertStreamParams(c, obtained, expected) 339 340 _, err = newLogStream(url.Values{"maxLines": []string{"foo"}}) 341 c.Assert(err, gc.ErrorMatches, `maxLines value "foo" is not a valid unsigned number`) 342 343 _, err = newLogStream(url.Values{"backlog": []string{"foo"}}) 344 c.Assert(err, gc.ErrorMatches, `backlog value "foo" is not a valid unsigned number`) 345 346 _, err = newLogStream(url.Values{"replay": []string{"foo"}}) 347 c.Assert(err, gc.ErrorMatches, `replay value "foo" is not a valid boolean`) 348 349 _, err = newLogStream(url.Values{"level": []string{"foo"}}) 350 c.Assert(err, gc.ErrorMatches, `level value "foo" is not one of "TRACE", "DEBUG", "INFO", "WARNING", "ERROR"`) 351 }