|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import contextlib |
|
import glob |
|
import logging |
|
import os |
|
import re |
|
import subprocess |
|
import sys |
|
import tempfile |
|
import unittest |
|
import warnings |
|
|
|
from tornado.escape import utf8 |
|
from tornado.log import LogFormatter, define_logging_options, enable_pretty_logging |
|
from tornado.options import OptionParser |
|
from tornado.util import basestring_type |
|
|
|
|
|
@contextlib.contextmanager |
|
def ignore_bytes_warning(): |
|
with warnings.catch_warnings(): |
|
warnings.simplefilter("ignore", category=BytesWarning) |
|
yield |
|
|
|
|
|
class LogFormatterTest(unittest.TestCase): |
|
|
|
|
|
LINE_RE = re.compile( |
|
b"(?s)\x01\\[E [0-9]{6} [0-9]{2}:[0-9]{2}:[0-9]{2} log_test:[0-9]+\\]\x02 (.*)" |
|
) |
|
|
|
def setUp(self): |
|
self.formatter = LogFormatter(color=False) |
|
|
|
|
|
|
|
|
|
self.formatter._colors = {logging.ERROR: "\u0001"} |
|
self.formatter._normal = "\u0002" |
|
|
|
self.logger = logging.Logger("LogFormatterTest") |
|
self.logger.propagate = False |
|
self.tempdir = tempfile.mkdtemp() |
|
self.filename = os.path.join(self.tempdir, "log.out") |
|
self.handler = self.make_handler(self.filename) |
|
self.handler.setFormatter(self.formatter) |
|
self.logger.addHandler(self.handler) |
|
|
|
def tearDown(self): |
|
self.handler.close() |
|
os.unlink(self.filename) |
|
os.rmdir(self.tempdir) |
|
|
|
def make_handler(self, filename): |
|
return logging.FileHandler(filename, encoding="utf-8") |
|
|
|
def get_output(self): |
|
with open(self.filename, "rb") as f: |
|
line = f.read().strip() |
|
m = LogFormatterTest.LINE_RE.match(line) |
|
if m: |
|
return m.group(1) |
|
else: |
|
raise Exception("output didn't match regex: %r" % line) |
|
|
|
def test_basic_logging(self): |
|
self.logger.error("foo") |
|
self.assertEqual(self.get_output(), b"foo") |
|
|
|
def test_bytes_logging(self): |
|
with ignore_bytes_warning(): |
|
|
|
self.logger.error(b"\xe9") |
|
self.assertEqual(self.get_output(), utf8(repr(b"\xe9"))) |
|
|
|
def test_utf8_logging(self): |
|
with ignore_bytes_warning(): |
|
self.logger.error("\u00e9".encode("utf8")) |
|
if issubclass(bytes, basestring_type): |
|
|
|
|
|
self.assertEqual(self.get_output(), utf8("\u00e9")) |
|
else: |
|
|
|
|
|
|
|
self.assertEqual(self.get_output(), utf8(repr(utf8("\u00e9")))) |
|
|
|
def test_bytes_exception_logging(self): |
|
try: |
|
raise Exception(b"\xe9") |
|
except Exception: |
|
self.logger.exception("caught exception") |
|
|
|
|
|
output = self.get_output() |
|
self.assertRegex(output, rb"Exception.*\\xe9") |
|
|
|
self.assertNotIn(rb"\n", output) |
|
|
|
def test_unicode_logging(self): |
|
self.logger.error("\u00e9") |
|
self.assertEqual(self.get_output(), utf8("\u00e9")) |
|
|
|
|
|
class EnablePrettyLoggingTest(unittest.TestCase): |
|
def setUp(self): |
|
super().setUp() |
|
self.options = OptionParser() |
|
define_logging_options(self.options) |
|
self.logger = logging.Logger("tornado.test.log_test.EnablePrettyLoggingTest") |
|
self.logger.propagate = False |
|
|
|
def test_log_file(self): |
|
tmpdir = tempfile.mkdtemp() |
|
try: |
|
self.options.log_file_prefix = tmpdir + "/test_log" |
|
enable_pretty_logging(options=self.options, logger=self.logger) |
|
self.assertEqual(1, len(self.logger.handlers)) |
|
self.logger.error("hello") |
|
self.logger.handlers[0].flush() |
|
filenames = glob.glob(tmpdir + "/test_log*") |
|
self.assertEqual(1, len(filenames)) |
|
with open(filenames[0], encoding="utf-8") as f: |
|
self.assertRegex(f.read(), r"^\[E [^]]*\] hello$") |
|
finally: |
|
for handler in self.logger.handlers: |
|
handler.flush() |
|
handler.close() |
|
for filename in glob.glob(tmpdir + "/test_log*"): |
|
os.unlink(filename) |
|
os.rmdir(tmpdir) |
|
|
|
def test_log_file_with_timed_rotating(self): |
|
tmpdir = tempfile.mkdtemp() |
|
try: |
|
self.options.log_file_prefix = tmpdir + "/test_log" |
|
self.options.log_rotate_mode = "time" |
|
enable_pretty_logging(options=self.options, logger=self.logger) |
|
self.logger.error("hello") |
|
self.logger.handlers[0].flush() |
|
filenames = glob.glob(tmpdir + "/test_log*") |
|
self.assertEqual(1, len(filenames)) |
|
with open(filenames[0], encoding="utf-8") as f: |
|
self.assertRegex(f.read(), r"^\[E [^]]*\] hello$") |
|
finally: |
|
for handler in self.logger.handlers: |
|
handler.flush() |
|
handler.close() |
|
for filename in glob.glob(tmpdir + "/test_log*"): |
|
os.unlink(filename) |
|
os.rmdir(tmpdir) |
|
|
|
def test_wrong_rotate_mode_value(self): |
|
try: |
|
self.options.log_file_prefix = "some_path" |
|
self.options.log_rotate_mode = "wrong_mode" |
|
self.assertRaises( |
|
ValueError, |
|
enable_pretty_logging, |
|
options=self.options, |
|
logger=self.logger, |
|
) |
|
finally: |
|
for handler in self.logger.handlers: |
|
handler.flush() |
|
handler.close() |
|
|
|
|
|
class LoggingOptionTest(unittest.TestCase): |
|
"""Test the ability to enable and disable Tornado's logging hooks.""" |
|
|
|
def logs_present(self, statement, args=None): |
|
|
|
|
|
|
|
|
|
|
|
IMPORT = "from tornado.options import options, parse_command_line" |
|
LOG_INFO = 'import logging; logging.info("hello")' |
|
program = ";".join([IMPORT, statement, LOG_INFO]) |
|
proc = subprocess.Popen( |
|
[sys.executable, "-c", program] + (args or []), |
|
stdout=subprocess.PIPE, |
|
stderr=subprocess.STDOUT, |
|
) |
|
stdout, stderr = proc.communicate() |
|
self.assertEqual(proc.returncode, 0, "process failed: %r" % stdout) |
|
return b"hello" in stdout |
|
|
|
def test_default(self): |
|
self.assertFalse(self.logs_present("pass")) |
|
|
|
def test_tornado_default(self): |
|
self.assertTrue(self.logs_present("parse_command_line()")) |
|
|
|
def test_disable_command_line(self): |
|
self.assertFalse(self.logs_present("parse_command_line()", ["--logging=none"])) |
|
|
|
def test_disable_command_line_case_insensitive(self): |
|
self.assertFalse(self.logs_present("parse_command_line()", ["--logging=None"])) |
|
|
|
def test_disable_code_string(self): |
|
self.assertFalse( |
|
self.logs_present('options.logging = "none"; parse_command_line()') |
|
) |
|
|
|
def test_disable_code_none(self): |
|
self.assertFalse( |
|
self.logs_present("options.logging = None; parse_command_line()") |
|
) |
|
|
|
def test_disable_override(self): |
|
|
|
self.assertTrue( |
|
self.logs_present( |
|
"options.logging = None; parse_command_line()", ["--logging=info"] |
|
) |
|
) |
|
|