test_loggers.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. # -*- coding: utf-8 -*-
  2. import errno
  3. import sys
  4. import unittest
  5. import tempfile
  6. import shutil
  7. import os
  8. import syslog
  9. from supervisor.compat import mock
  10. from supervisor.compat import PY3
  11. from supervisor.compat import as_string
  12. from supervisor.tests.base import DummyStream
  13. from supervisor import read_file
  14. class LevelTests(unittest.TestCase):
  15. def test_LOG_LEVELS_BY_NUM_doesnt_include_builtins(self):
  16. from supervisor import loggers
  17. for level_name in loggers.LOG_LEVELS_BY_NUM.values():
  18. self.assertFalse(level_name.startswith('_'))
  19. class HandlerTests:
  20. def setUp(self):
  21. self.basedir = tempfile.mkdtemp()
  22. self.filename = os.path.join(self.basedir, 'thelog')
  23. def tearDown(self):
  24. try:
  25. shutil.rmtree(self.basedir)
  26. except OSError:
  27. pass
  28. def _makeOne(self, *arg, **kw):
  29. klass = self._getTargetClass()
  30. return klass(*arg, **kw)
  31. def _makeLogRecord(self, msg):
  32. from supervisor import loggers
  33. record = loggers.LogRecord(
  34. level=loggers.LevelsByName.INFO,
  35. msg=msg,
  36. exc_info=None
  37. )
  38. return record
  39. class BareHandlerTests(HandlerTests, unittest.TestCase):
  40. def _getTargetClass(self):
  41. from supervisor.loggers import Handler
  42. return Handler
  43. def test_flush_stream_flush_raises_IOError_EPIPE(self):
  44. stream = DummyStream(error=IOError(errno.EPIPE))
  45. inst = self._makeOne(stream=stream)
  46. self.assertEqual(inst.flush(), None) # does not raise
  47. def test_flush_stream_flush_raises_IOError_not_EPIPE(self):
  48. stream = DummyStream(error=IOError(errno.EALREADY))
  49. inst = self._makeOne(stream=stream)
  50. self.assertRaises(IOError, inst.flush) # non-EPIPE IOError raises
  51. def test_close_already_closed(self):
  52. stream = DummyStream()
  53. inst = self._makeOne(stream=stream)
  54. inst.closed = True
  55. self.assertEqual(inst.close(), None)
  56. def test_close_stream_fileno_above_3(self):
  57. stream = DummyStream(fileno=50)
  58. inst = self._makeOne(stream=stream)
  59. self.assertEqual(inst.close(), None)
  60. self.assertTrue(inst.closed)
  61. self.assertTrue(inst.stream.closed)
  62. def test_close_stream_fileno_below_3(self):
  63. stream = DummyStream(fileno=0)
  64. inst = self._makeOne(stream=stream)
  65. self.assertEqual(inst.close(), None)
  66. self.assertFalse(inst.closed)
  67. self.assertFalse(inst.stream.closed)
  68. def test_emit_gardenpath(self):
  69. stream = DummyStream()
  70. inst = self._makeOne(stream=stream)
  71. record = self._makeLogRecord('foo')
  72. inst.emit(record)
  73. self.assertEqual(stream.flushed, True)
  74. self.assertEqual(stream.written, 'foo')
  75. def test_emit_unicode_error(self):
  76. stream = DummyStream(error=UnicodeError)
  77. inst = self._makeOne(stream=stream)
  78. record = self._makeLogRecord('foo')
  79. inst.emit(record)
  80. self.assertEqual(stream.flushed, True)
  81. self.assertEqual(stream.written, 'foo')
  82. def test_emit_other_error(self):
  83. stream = DummyStream(error=TypeError)
  84. inst = self._makeOne(stream=stream)
  85. handled = []
  86. inst.handleError = lambda: handled.append(True)
  87. record = self._makeLogRecord('foo')
  88. inst.emit(record)
  89. self.assertEqual(stream.flushed, False)
  90. self.assertEqual(stream.written, '')
  91. class FileHandlerTests(HandlerTests, unittest.TestCase):
  92. def _getTargetClass(self):
  93. from supervisor.loggers import FileHandler
  94. return FileHandler
  95. def test_ctor(self):
  96. handler = self._makeOne(self.filename)
  97. self.assertTrue(os.path.exists(self.filename), self.filename)
  98. self.assertEqual(handler.mode, 'a')
  99. self.assertEqual(handler.baseFilename, self.filename)
  100. self.assertEqual(handler.stream.name, self.filename)
  101. handler.close()
  102. def test_close(self):
  103. handler = self._makeOne(self.filename)
  104. handler.stream.close()
  105. handler.stream = DummyStream()
  106. handler.close()
  107. self.assertEqual(handler.stream.closed, True)
  108. def test_close_raises(self):
  109. handler = self._makeOne(self.filename)
  110. handler.stream.close()
  111. handler.stream = DummyStream(OSError)
  112. self.assertRaises(OSError, handler.close)
  113. self.assertEqual(handler.stream.closed, False)
  114. def test_reopen(self):
  115. handler = self._makeOne(self.filename)
  116. handler.stream.close()
  117. stream = DummyStream()
  118. handler.stream = stream
  119. handler.reopen()
  120. self.assertEqual(stream.closed, True)
  121. self.assertEqual(handler.stream.name, self.filename)
  122. handler.close()
  123. def test_reopen_raises(self):
  124. handler = self._makeOne(self.filename)
  125. handler.stream.close()
  126. stream = DummyStream()
  127. handler.stream = stream
  128. handler.baseFilename = os.path.join(self.basedir, 'notthere', 'a.log')
  129. self.assertRaises(IOError, handler.reopen)
  130. self.assertEqual(stream.closed, True)
  131. def test_remove_exists(self):
  132. handler = self._makeOne(self.filename)
  133. self.assertTrue(os.path.exists(self.filename), self.filename)
  134. handler.remove()
  135. self.assertFalse(os.path.exists(self.filename), self.filename)
  136. def test_remove_doesntexist(self):
  137. handler = self._makeOne(self.filename)
  138. os.remove(self.filename)
  139. self.assertFalse(os.path.exists(self.filename), self.filename)
  140. handler.remove() # should not raise
  141. self.assertFalse(os.path.exists(self.filename), self.filename)
  142. def test_remove_raises(self):
  143. handler = self._makeOne(self.filename)
  144. os.remove(self.filename)
  145. os.mkdir(self.filename)
  146. self.assertTrue(os.path.exists(self.filename), self.filename)
  147. self.assertRaises(OSError, handler.remove)
  148. def test_emit_ascii_noerror(self):
  149. handler = self._makeOne(self.filename)
  150. record = self._makeLogRecord('hello!')
  151. handler.emit(record)
  152. handler.close()
  153. self.assertEqual(read_file(self.filename,'r'), 'hello!')
  154. # def test_emit_unicode_noerror(self):
  155. # handler = self._makeOne(self.filename)
  156. # record = self._makeLogRecord(u'fi\xed')
  157. # handler.emit(record)
  158. # content = open(self.filename, 'r').read()
  159. # self.assertEqual(content, 'fi\xc3\xad')
  160. def test_emit_error(self):
  161. handler = self._makeOne(self.filename)
  162. handler.stream.close()
  163. handler.stream = DummyStream(error=OSError)
  164. record = self._makeLogRecord('hello!')
  165. try:
  166. old_stderr = sys.stderr
  167. dummy_stderr = DummyStream()
  168. sys.stderr = dummy_stderr
  169. handler.emit(record)
  170. finally:
  171. sys.stderr = old_stderr
  172. self.assertTrue(dummy_stderr.written.endswith('OSError\n'),
  173. dummy_stderr.written)
  174. class RotatingFileHandlerTests(FileHandlerTests):
  175. def _getTargetClass(self):
  176. from supervisor.loggers import RotatingFileHandler
  177. return RotatingFileHandler
  178. def test_ctor(self):
  179. handler = self._makeOne(self.filename)
  180. self.assertEqual(handler.mode, 'a')
  181. self.assertEqual(handler.maxBytes, 512*1024*1024)
  182. self.assertEqual(handler.backupCount, 10)
  183. def test_emit_does_rollover(self):
  184. handler = self._makeOne(self.filename, maxBytes=10, backupCount=2)
  185. record = self._makeLogRecord('a' * 4)
  186. handler.emit(record) # 4 bytes
  187. self.assertFalse(os.path.exists(self.filename + '.1'))
  188. self.assertFalse(os.path.exists(self.filename + '.2'))
  189. handler.emit(record) # 8 bytes
  190. self.assertFalse(os.path.exists(self.filename + '.1'))
  191. self.assertFalse(os.path.exists(self.filename + '.2'))
  192. handler.emit(record) # 12 bytes, do rollover
  193. self.assertTrue(os.path.exists(self.filename + '.1'))
  194. self.assertFalse(os.path.exists(self.filename + '.2'))
  195. handler.emit(record) # 16 bytes
  196. self.assertTrue(os.path.exists(self.filename + '.1'))
  197. self.assertFalse(os.path.exists(self.filename + '.2'))
  198. handler.emit(record) # 20 bytes
  199. self.assertTrue(os.path.exists(self.filename + '.1'))
  200. self.assertFalse(os.path.exists(self.filename + '.2'))
  201. handler.emit(record) # 24 bytes, do rollover
  202. self.assertTrue(os.path.exists(self.filename + '.1'))
  203. self.assertTrue(os.path.exists(self.filename + '.2'))
  204. handler.emit(record) # 28 bytes
  205. self.assertTrue(os.path.exists(self.filename + '.1'))
  206. self.assertTrue(os.path.exists(self.filename + '.2'))
  207. self.assertEqual(read_file(self.filename), 'a' * 4)
  208. one = read_file(self.filename+'.1')
  209. self.assertEqual(one, 'a'*12)
  210. two = read_file(self.filename+'.2')
  211. self.assertEqual(two, 'a'*12)
  212. def test_current_logfile_removed(self):
  213. handler = self._makeOne(self.filename, maxBytes=6, backupCount=1)
  214. record = self._makeLogRecord('a' * 4)
  215. handler.emit(record) # 4 bytes
  216. self.assertTrue(os.path.exists(self.filename))
  217. self.assertFalse(os.path.exists(self.filename + '.1'))
  218. # Someone removes the active log file! :-(
  219. os.unlink(self.filename)
  220. self.assertFalse(os.path.exists(self.filename))
  221. handler.emit(record) # 8 bytes, do rollover
  222. self.assertTrue(os.path.exists(self.filename))
  223. self.assertFalse(os.path.exists(self.filename + '.1'))
  224. def test_removeAndRename_destination_does_not_exist(self):
  225. inst = self._makeOne(self.filename)
  226. renames = []
  227. removes = []
  228. inst._remove = lambda v: removes.append(v)
  229. inst._exists = lambda v: False
  230. inst._rename = lambda s, t: renames.append((s, t))
  231. inst.removeAndRename('foo', 'bar')
  232. self.assertEqual(renames, [('foo', 'bar')])
  233. self.assertEqual(removes, [])
  234. def test_removeAndRename_destination_exists(self):
  235. inst = self._makeOne(self.filename)
  236. renames = []
  237. removes = []
  238. inst._remove = lambda v: removes.append(v)
  239. inst._exists = lambda v: True
  240. inst._rename = lambda s, t: renames.append((s, t))
  241. inst.removeAndRename('foo', 'bar')
  242. self.assertEqual(renames, [('foo', 'bar')])
  243. self.assertEqual(removes, ['bar'])
  244. def test_removeAndRename_remove_raises_ENOENT(self):
  245. def remove(fn):
  246. raise OSError(errno.ENOENT)
  247. inst = self._makeOne(self.filename)
  248. renames = []
  249. inst._remove = remove
  250. inst._exists = lambda v: True
  251. inst._rename = lambda s, t: renames.append((s, t))
  252. inst.removeAndRename('foo', 'bar')
  253. self.assertEqual(renames, [('foo', 'bar')])
  254. def test_removeAndRename_remove_raises_other_than_ENOENT(self):
  255. def remove(fn):
  256. raise OSError(errno.EAGAIN)
  257. inst = self._makeOne(self.filename)
  258. inst._remove = remove
  259. inst._exists = lambda v: True
  260. self.assertRaises(OSError, inst.removeAndRename, 'foo', 'bar')
  261. def test_removeAndRename_rename_raises_ENOENT(self):
  262. def rename(s, d):
  263. raise OSError(errno.ENOENT)
  264. inst = self._makeOne(self.filename)
  265. inst._rename = rename
  266. inst._exists = lambda v: False
  267. self.assertEqual(inst.removeAndRename('foo', 'bar'), None)
  268. def test_removeAndRename_rename_raises_other_than_ENOENT(self):
  269. def rename(s, d):
  270. raise OSError(errno.EAGAIN)
  271. inst = self._makeOne(self.filename)
  272. inst._rename = rename
  273. inst._exists = lambda v: False
  274. self.assertRaises(OSError, inst.removeAndRename, 'foo', 'bar')
  275. def test_doRollover_maxbytes_lte_zero(self):
  276. inst = self._makeOne(self.filename)
  277. inst.maxBytes = 0
  278. self.assertEqual(inst.doRollover(), None)
  279. class BoundIOTests(unittest.TestCase):
  280. def _getTargetClass(self):
  281. from supervisor.loggers import BoundIO
  282. return BoundIO
  283. def _makeOne(self, maxbytes, buf=''):
  284. klass = self._getTargetClass()
  285. return klass(maxbytes, buf)
  286. def test_write_overflow(self):
  287. io = self._makeOne(1, 'a')
  288. io.write('b')
  289. self.assertEqual(io.buf, 'b')
  290. def test_getvalue(self):
  291. io = self._makeOne(1, 'a')
  292. self.assertEqual(io.getvalue(), 'a')
  293. def test_clear(self):
  294. io = self._makeOne(1, 'a')
  295. io.clear()
  296. self.assertEqual(io.buf, '')
  297. def test_close(self):
  298. io = self._makeOne(1, 'a')
  299. io.close()
  300. self.assertEqual(io.buf, '')
  301. class LoggerTests(unittest.TestCase):
  302. def _getTargetClass(self):
  303. from supervisor.loggers import Logger
  304. return Logger
  305. def _makeOne(self, level=None, handlers=None):
  306. klass = self._getTargetClass()
  307. return klass(level, handlers)
  308. def test_blather(self):
  309. from supervisor.loggers import LevelsByName
  310. handler = DummyHandler(LevelsByName.BLAT)
  311. logger = self._makeOne(LevelsByName.BLAT, (handler,))
  312. logger.blather('hello')
  313. self.assertEqual(len(handler.records), 1)
  314. logger.level = LevelsByName.TRAC
  315. logger.blather('hello')
  316. self.assertEqual(len(handler.records), 1)
  317. def test_trace(self):
  318. from supervisor.loggers import LevelsByName
  319. handler = DummyHandler(LevelsByName.TRAC)
  320. logger = self._makeOne(LevelsByName.TRAC, (handler,))
  321. logger.trace('hello')
  322. self.assertEqual(len(handler.records), 1)
  323. logger.level = LevelsByName.DEBG
  324. logger.trace('hello')
  325. self.assertEqual(len(handler.records), 1)
  326. def test_debug(self):
  327. from supervisor.loggers import LevelsByName
  328. handler = DummyHandler(LevelsByName.DEBG)
  329. logger = self._makeOne(LevelsByName.DEBG, (handler,))
  330. logger.debug('hello')
  331. self.assertEqual(len(handler.records), 1)
  332. logger.level = LevelsByName.INFO
  333. logger.debug('hello')
  334. self.assertEqual(len(handler.records), 1)
  335. def test_info(self):
  336. from supervisor.loggers import LevelsByName
  337. handler = DummyHandler(LevelsByName.INFO)
  338. logger = self._makeOne(LevelsByName.INFO, (handler,))
  339. logger.info('hello')
  340. self.assertEqual(len(handler.records), 1)
  341. logger.level = LevelsByName.WARN
  342. logger.info('hello')
  343. self.assertEqual(len(handler.records), 1)
  344. def test_warn(self):
  345. from supervisor.loggers import LevelsByName
  346. handler = DummyHandler(LevelsByName.WARN)
  347. logger = self._makeOne(LevelsByName.WARN, (handler,))
  348. logger.warn('hello')
  349. self.assertEqual(len(handler.records), 1)
  350. logger.level = LevelsByName.ERRO
  351. logger.warn('hello')
  352. self.assertEqual(len(handler.records), 1)
  353. def test_error(self):
  354. from supervisor.loggers import LevelsByName
  355. handler = DummyHandler(LevelsByName.ERRO)
  356. logger = self._makeOne(LevelsByName.ERRO, (handler,))
  357. logger.error('hello')
  358. self.assertEqual(len(handler.records), 1)
  359. logger.level = LevelsByName.CRIT
  360. logger.error('hello')
  361. self.assertEqual(len(handler.records), 1)
  362. def test_critical(self):
  363. from supervisor.loggers import LevelsByName
  364. handler = DummyHandler(LevelsByName.CRIT)
  365. logger = self._makeOne(LevelsByName.CRIT, (handler,))
  366. logger.critical('hello')
  367. self.assertEqual(len(handler.records), 1)
  368. def test_close(self):
  369. from supervisor.loggers import LevelsByName
  370. handler = DummyHandler(LevelsByName.CRIT)
  371. logger = self._makeOne(LevelsByName.CRIT, (handler,))
  372. logger.close()
  373. self.assertEqual(handler.closed, True)
  374. def test_getvalue(self):
  375. from supervisor.loggers import LevelsByName
  376. handler = DummyHandler(LevelsByName.CRIT)
  377. logger = self._makeOne(LevelsByName.CRIT, (handler,))
  378. self.assertRaises(NotImplementedError, logger.getvalue)
  379. class MockSysLog(mock.Mock):
  380. def __call__(self, *args, **kwargs):
  381. message = args[-1]
  382. if sys.version_info < (3, 0) and isinstance(message, unicode):
  383. # Python 2.x raises a UnicodeEncodeError when attempting to
  384. # transmit unicode characters that don't encode in the
  385. # default encoding.
  386. message.encode()
  387. super(MockSysLog, self).__call__(*args, **kwargs)
  388. class SyslogHandlerTests(HandlerTests, unittest.TestCase):
  389. def setUp(self):
  390. pass
  391. def tearDown(self):
  392. pass
  393. def _getTargetClass(self):
  394. return __import__('supervisor.loggers').loggers.SyslogHandler
  395. def _makeOne(self):
  396. return self._getTargetClass()()
  397. def test_emit_record_asdict_raises(self):
  398. class Record(object):
  399. def asdict(self):
  400. raise TypeError
  401. record = Record()
  402. handler = self._makeOne()
  403. handled = []
  404. handler.handleError = lambda: handled.append(True)
  405. handler.emit(record)
  406. self.assertEqual(handled, [True])
  407. @mock.patch('syslog.syslog', MockSysLog())
  408. def test_emit_ascii_noerror(self):
  409. handler = self._makeOne()
  410. record = self._makeLogRecord('hello!')
  411. handler.emit(record)
  412. syslog.syslog.assert_called_with('hello!')
  413. @mock.patch('syslog.syslog', MockSysLog())
  414. def test_close(self):
  415. handler = self._makeOne()
  416. handler.close() # no-op for syslog
  417. @mock.patch('syslog.syslog', MockSysLog())
  418. def test_reopen(self):
  419. handler = self._makeOne()
  420. handler.reopen() # no-op for syslog
  421. if PY3:
  422. @mock.patch('syslog.syslog', MockSysLog())
  423. def test_emit_unicode_noerror(self):
  424. handler = self._makeOne()
  425. record = self._makeLogRecord('fií')
  426. handler.emit(record)
  427. syslog.syslog.assert_called_with('fií')
  428. def test_emit_unicode_witherror(self):
  429. handler = self._makeOne()
  430. called = []
  431. def fake_syslog(msg):
  432. if not called:
  433. called.append(msg)
  434. raise UnicodeError
  435. handler._syslog = fake_syslog
  436. record = self._makeLogRecord('fií')
  437. handler.emit(record)
  438. self.assertEqual(called, ['fií'])
  439. else:
  440. @mock.patch('syslog.syslog', MockSysLog())
  441. def test_emit_unicode_noerror(self):
  442. handler = self._makeOne()
  443. inp = as_string('fií')
  444. record = self._makeLogRecord(inp)
  445. handler.emit(record)
  446. syslog.syslog.assert_called_with('fi\xc3\xad')
  447. def test_emit_unicode_witherror(self):
  448. handler = self._makeOne()
  449. called = []
  450. def fake_syslog(msg):
  451. if not called:
  452. called.append(msg)
  453. raise UnicodeError
  454. handler._syslog = fake_syslog
  455. record = self._makeLogRecord(as_string('fií'))
  456. handler.emit(record)
  457. self.assertEqual(called, [as_string('fi\xc3\xad')])
  458. class DummyHandler:
  459. close = False
  460. def __init__(self, level):
  461. self.level = level
  462. self.records = []
  463. def emit(self, record):
  464. self.records.append(record)
  465. def close(self):
  466. self.closed = True
  467. def test_suite():
  468. return unittest.findTestCases(sys.modules[__name__])
  469. if __name__ == '__main__':
  470. unittest.main(defaultTest='test_suite')