mmengine/tests/test_logging/test_logger.py

153 lines
6.3 KiB
Python

# Copyright (c) OpenMMLab. All rights reserved.
import logging
import os
import re
import sys
from unittest.mock import patch
import pytest
from mmengine import MMLogger, print_log
class TestLogger:
stream_handler_regex_time = r'\d{2}/\d{2} \d{2}:\d{2}:\d{2}'
file_handler_regex_time = r'\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}'
@patch('torch.distributed.get_rank', lambda: 0)
@patch('torch.distributed.is_initialized', lambda: True)
@patch('torch.distributed.is_available', lambda: True)
def test_init_rank0(self, tmp_path):
logger = MMLogger.get_instance('rank0.pkg1', log_level='INFO')
assert logger.name == 'mmengine'
assert logger.instance_name == 'rank0.pkg1'
assert logger.instance_name == 'rank0.pkg1'
# Logger get from `MMLogger.get_instance` does not inherit from
# `logging.root`
assert logger.parent is None
assert len(logger.handlers) == 1
assert isinstance(logger.handlers[0], logging.StreamHandler)
assert logger.level == logging.NOTSET
assert logger.handlers[0].level == logging.INFO
# If `rank=0`, the `log_level` of stream_handler and file_handler
# depends on the given arguments.
tmp_file = tmp_path / 'tmp_file.log'
logger = MMLogger.get_instance(
'rank0.pkg2', log_level='INFO', log_file=str(tmp_file))
assert isinstance(logger, logging.Logger)
assert len(logger.handlers) == 2
assert isinstance(logger.handlers[0], logging.StreamHandler)
assert isinstance(logger.handlers[1], logging.FileHandler)
logger_pkg3 = MMLogger.get_instance('rank0.pkg2')
assert id(logger_pkg3) == id(logger)
logger = MMLogger.get_instance(
'rank0.pkg3', logger_name='logger_test', log_level='INFO')
assert logger.name == 'logger_test'
assert logger.instance_name == 'rank0.pkg3'
logging.shutdown()
@patch('torch.distributed.get_rank', lambda: 1)
@patch('torch.distributed.is_initialized', lambda: True)
@patch('torch.distributed.is_available', lambda: True)
def test_init_rank1(self, tmp_path):
# If `rank!=1`, the `loglevel` of file_handler is `logging.ERROR`.
tmp_file = tmp_path / 'tmp_file.log'
log_path = tmp_path / 'tmp_file' / 'tmp_file_rank1.log'
logger = MMLogger.get_instance(
'rank1.pkg2', log_level='INFO', log_file=str(tmp_file))
assert len(logger.handlers) == 1
logger = MMLogger.get_instance(
'rank1.pkg3',
log_level='INFO',
log_file=str(tmp_file),
distributed=True)
assert logger.handlers[0].level == logging.ERROR
assert logger.handlers[1].level == logging.INFO
assert len(logger.handlers) == 2
assert os.path.exists(log_path)
logging.shutdown()
@pytest.mark.parametrize('log_level',
[logging.WARNING, logging.INFO, logging.DEBUG])
def test_handler(self, capsys, tmp_path, log_level):
# test stream handler can output correct format logs
instance_name = f'test_stream_{str(log_level)}'
logger = MMLogger.get_instance(instance_name, log_level=log_level)
logger.log(level=log_level, msg='welcome')
out, _ = capsys.readouterr()
# Skip match colored INFO
loglevl_name = logging._levelToName[log_level]
match = re.fullmatch(
self.stream_handler_regex_time + f' - mmengine - '
f'(.*){loglevl_name}(.*) - welcome\n', out)
assert match is not None
# test file_handler output plain text without color.
tmp_file = tmp_path / 'tmp_file.log'
instance_name = f'test_file_{log_level}'
logger = MMLogger.get_instance(
instance_name, log_level=log_level, log_file=tmp_file)
logger.log(level=log_level, msg='welcome')
with open(tmp_path / 'tmp_file' / 'tmp_file.log', 'r') as f:
log_text = f.read()
match = re.fullmatch(
self.file_handler_regex_time +
f' - mmengine - {loglevl_name} - '
f'welcome\n', log_text)
assert match is not None
logging.shutdown()
def test_error_format(self, capsys):
# test error level log can output file path, function name and
# line number
logger = MMLogger.get_instance('test_error', log_level='INFO')
logger.error('welcome')
lineno = sys._getframe().f_lineno - 1
file_path = __file__
function_name = sys._getframe().f_code.co_name
pattern = self.stream_handler_regex_time + \
r' - mmengine - (.*)ERROR(.*) - ' \
f'{file_path} - {function_name} - ' \
f'{lineno} - welcome\n'
out, _ = capsys.readouterr()
match = re.fullmatch(pattern, out)
assert match is not None
def test_print_log(self, capsys, tmp_path):
# caplog cannot record MMLogger's logs.
# Test simple print.
print_log('welcome', logger=None)
out, _ = capsys.readouterr()
assert out == 'welcome\n'
# Test silent logger and skip print.
print_log('welcome', logger='silent')
out, _ = capsys.readouterr()
assert out == ''
logger = MMLogger.get_instance('test_print_log')
# Test using specified logger
print_log('welcome', logger=logger)
out, _ = capsys.readouterr()
match = re.fullmatch(
self.stream_handler_regex_time + ' - mmengine - (.*)INFO(.*) - '
'welcome\n', out)
assert match is not None
# Test access logger by name.
print_log('welcome', logger='test_print_log')
out, _ = capsys.readouterr()
match = re.fullmatch(
self.stream_handler_regex_time + ' - mmengine - (.*)INFO(.*) - '
'welcome\n', out)
assert match is not None
# Test access the latest created logger.
print_log('welcome', logger='current')
out, _ = capsys.readouterr()
match = re.fullmatch(
self.stream_handler_regex_time + ' - mmengine - (.*)INFO(.*) - '
'welcome\n', out)
assert match is not None
# Test invalid logger type.
with pytest.raises(TypeError):
print_log('welcome', logger=dict)
with pytest.raises(ValueError):
print_log('welcome', logger='unknown')