Skip to content

Commit

Permalink
Add "stop()" function to remove handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Delgan committed Oct 18, 2017
1 parent cda01d5 commit 8ec147f
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 64 deletions.
69 changes: 45 additions & 24 deletions loguru/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def get_get_frame_function():

get_frame = get_get_frame_function()

class StrRecord(str):
pass

class Handler:

def __init__(self, *, writter, level, format, filter, colored, better_exceptions):
Expand Down Expand Up @@ -91,7 +94,7 @@ def generate_formats(format, colored):
# loguru_record = RecordUtils.to_loguru_record(record)
# self.emit(loguru_record)

def emit(self, record, exception):
def emit(self, record):
level = record['level']
if self.level > level.no:
return
Expand All @@ -100,16 +103,19 @@ def emit(self, record, exception):
if not self.filter(record):
return

message = self.formats_per_level[level.name].format_map(record) + '\n'
exception = record['exception']

self.writter(message)
formatted = self.formats_per_level[level.name].format_map(record) + '\n'
if exception:
if self.better_exceptions:
for part in self.exception_formatter.format_exception(*exception):
self.writter(part)
formatted += ''.join(self.exception_formatter.format_exception(*exception))
else:
self.writter(''.join(format_exception(*exception)))
formatted += ''.join(format_exception(*exception))

message = StrRecord(formatted)
message.record = record

self.writter(message)

class LevelRecattr(str):
__slots__ = ('no', 'name')
Expand All @@ -134,10 +140,16 @@ def __init__(self, path, *, mode='a', encoding='utf8', delay=False, buffering=1)
self.write = f.write


def stop(self):
if self.file is not None:
self.file.close()
self.file = None

class Logger:

def __init__(self):
self.handlers = []
self.handlers_count = 0
self.handlers = {}

def log_to(self, sink, *, level=DEBUG, format=VERBOSE_FORMAT, filter=None, colored=None, better_exceptions=True, **kwargs):
if isclass(sink):
Expand All @@ -147,25 +159,21 @@ def log_to(self, sink, *, level=DEBUG, format=VERBOSE_FORMAT, filter=None, color
if kwargs:
writter = lambda m: sink(m, **kwargs)
else:
# Specialized for performances of main use case
writter = sink
if colored is None:
colored = False
elif isinstance(sink, (str, PathLike)):
path = sink
sink = FileSink(path, **kwargs)
if colored is None:
colored = False
return self.log_to(sink, level=level, format=format, filter=filter, colored=colored, better_exceptions=better_exceptions)
elif hasattr(sink, 'write'):
elif hasattr(sink, 'write') and callable(sink.write):
sink_write = sink.write
if kwargs:
write = lambda m: sink_write(m, **kwargs)
else:
# Specialized for performances of main use case
write = sink_write

if hasattr(sink, 'flush'):
if hasattr(sink, 'flush') and callable(sink.flush):
sink_flush = sink.flush
writter = lambda m: write(m) and sink_flush()
else:
Expand All @@ -175,8 +183,6 @@ def log_to(self, sink, *, level=DEBUG, format=VERBOSE_FORMAT, filter=None, color
try:
colored = sink.isatty()
except Exception:
# The sys.stderr defined by the Python Interface to Vim doesn't
# have an isatty() method.
colored = False
else:
type_name = type(sink).__name__
Expand All @@ -196,9 +202,26 @@ def log_to(self, sink, *, level=DEBUG, format=VERBOSE_FORMAT, filter=None, color
better_exceptions=better_exceptions,
)

self.handlers.append(handler)
self.handlers[self.handlers_count] = handler
self.handlers_count += 1

return self.handlers_count - 1

def stop(self, handler_id=None):
if handler_id is None:
for handler in self.handlers.values():
if hasattr(handler, 'stop') and callable(handler.stop):
handler.stop()
count = len(self.handlers)
self.handlers.clear()
return count
elif handler_id in self.handlers:
handler = self.handlers.pop(handler_id)
if hasattr(handler, 'stop') and callable(handler.stop):
handler.stop()
return 1

return handler
return 0

@staticmethod
def make_log_function(level, log_exception=False):
Expand Down Expand Up @@ -237,6 +260,8 @@ def log_function(self, message, *args, **kwargs):
process_recattr = ProcessRecattr(process.ident)
process_recattr.id, process_recattr.name = process.ident, process.name

exception = exc_info() if log_exception else None

record = {
'name': name,
'message': message,
Expand All @@ -249,15 +274,11 @@ def log_function(self, message, *args, **kwargs):
'module': module_name,
'thread': thread_recattr,
'process': process_recattr,
'frame': frame,
'exception': exception,
}

exception = None
if log_exception:
exception = exc_info()

for handler in self.handlers:
handler.emit(record, exception=exception)
for handler in self.handlers.values():
handler.emit(record)

doc = "Log 'message.format(*args, **kwargs)' with severity '{}'.".format(level_name)
if log_exception:
Expand Down
15 changes: 15 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,18 @@
def with_and_without_sys_getframe(request, monkeypatch):
if request.param == 'without_sys_getframe':
monkeypatch.setattr(loguru, 'get_frame', loguru.get_frame_fallback)

@pytest.fixture
def logger():
return loguru.Logger()

@pytest.fixture
def writer():

def w(message):
w.written.append(message)

w.written = []
w.read = lambda: ''.join(w.written)

return w
16 changes: 0 additions & 16 deletions tests/test_log_to_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,6 @@
import pytest


@pytest.fixture
def logger():
return loguru.Logger()

@pytest.fixture
def writer():

def w(message):
w.written.append(message)

w.written = []
w.read = lambda: ''.join(w.written)

return w


@pytest.mark.parametrize('level, function, should_output', [
(0, lambda x: x.trace, True),
(loguru.TRACE, lambda x: x.debug, True),
Expand Down
86 changes: 62 additions & 24 deletions tests/test_log_to_sinks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,117 @@

import pytest

MESSAGES = ['ASCII test']
PARAMS = [(message, message + '\n') for message in MESSAGES]

messages = pytest.mark.parametrize('message, expected', PARAMS)
repetitions = pytest.mark.parametrize('rep', [0, 1, 2])
message = "test message"
expected = message + "\n"

repetitions = pytest.mark.parametrize('rep', [0, 1, 2])

def log(sink, message, rep=1):
def log(sink, rep=1):
logger = loguru.Logger()
logger.debug("This shouldn't be printed.")
logger.log_to(sink, format='{message}')
i = logger.log_to(sink, format='{message}')
for _ in range(rep):
logger.debug(message)
logger.stop(i)
logger.debug("This shouldn't be printed neither.")

@messages
@repetitions
def test_stdout_sink(message, expected, rep, capsys):
log(sys.stdout, message, rep)
def test_stdout_sink(rep, capsys):
log(sys.stdout, rep)
out, err = capsys.readouterr()
assert out == expected * rep
assert err == ''

@messages
@repetitions
def test_stderr_sink(message, expected, rep, capsys):
log(sys.stderr, message, rep)
def test_stderr_sink(rep, capsys):
log(sys.stderr, rep)
out, err = capsys.readouterr()
assert out == ''
assert err == expected * rep

@messages
@repetitions
@pytest.mark.parametrize("sink_from_path", [
str,
pathlib.Path,
lambda path: open(path, 'a'),
lambda path: pathlib.Path(path).open('a'),
])
def test_file_sink(message, expected, rep, sink_from_path, tmpdir):
def test_file_sink(rep, sink_from_path, tmpdir):
file = tmpdir.join('test.log')
path = file.realpath()
sink = sink_from_path(path)
log(sink, message, rep)
log(sink, rep)
assert file.read() == expected * rep

@messages
@repetitions
def test_function_sink(message, expected, rep):
def test_function_sink(rep):
a = []
func = lambda log_message: a.append(log_message)
log(func, message, rep)
log(func, rep)
assert a == [expected] * rep

@messages
@repetitions
def test_class_sink(message, expected, rep):
def test_class_sink(rep):
out = []
class A:
def write(self, m): out.append(m)
log(A, message, rep)
log(A, rep)
assert out == [expected] * rep

@messages
@repetitions
def test_file_object_sink(message, expected, rep):
def test_file_object_sink(rep):
class A:
def __init__(self): self.out = ""
def write(self, m): self.out += m
a = A()
log(a, message, rep)
log(a, rep)
assert a.out == expected * rep

@pytest.mark.parametrize('sink', [123, sys, object(), loguru.Logger(), loguru.Logger])
def test_invalid_sink(sink):
with pytest.raises(ValueError):
log(sink, "")

def test_stop_all(tmpdir, writer, capsys):
logger = loguru.Logger()
file = tmpdir.join("test.log")

logger.debug("This shouldn't be printed.")

logger.log_to(file.realpath(), format='{message}')
logger.log_to(sys.stdout, format='{message}')
logger.log_to(sys.stderr, format='{message}')
logger.log_to(writer, format='{message}')

logger.debug(message)

logger.stop()

logger.debug("This shouldn't be printed neither.")

out, err = capsys.readouterr()

assert file.read() == expected
assert out == expected
assert err == expected
assert writer.read() == expected

def test_stop_count(logger, writer):
n = logger.stop()
assert n == 0

n = logger.stop(42)
assert n == 0

i = logger.log_to(writer)
n = logger.stop(i)
assert n == 1

logger.log_to(writer)
logger.log_to(writer)
n = logger.stop()
assert n == 2

n = logger.stop(0)
assert n == 0

0 comments on commit 8ec147f

Please sign in to comment.