Skip to content

Commit

Permalink
Fix thread safety of logging while adding handler (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
Delgan committed Dec 26, 2018
1 parent f16410d commit 62d13ce
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 31 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Unreleased
==========

- Fix adding handler while logging which was not thread-safe (`#22 <https://github.com/Delgan/loguru/issues/22>`_)


0.2.3 (2018-12-16)
==================

Expand Down
4 changes: 3 additions & 1 deletion loguru/_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,9 @@ def log_function(_self, _message, *args, **kwargs):
elif args or kwargs:
record["message"] = _message.format(*args, **kwargs)

for handler in _self._handlers.values():
handlers = _self._handlers.copy().values()

for handler in handlers:
handler.emit(record, level_color, _self._ansi, _self._raw)

doc = r"Log ``_message.format(*args, **kwargs)`` with severity ``'%s'``." % level_name
Expand Down
111 changes: 81 additions & 30 deletions tests/test_threading.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,99 @@
from threading import Thread
from threading import Thread, Barrier
import itertools
from loguru import logger
import time


def test_safe(capsys):
first_thread_initialized = False
second_thread_initialized = False
entered = False
output = ""
class NonSafeSink:
def __init__(self):
self.written = ""

def non_safe_sink(msg):
nonlocal entered
nonlocal output
assert not entered
entered = True
def write(self, message):
self.written += message
time.sleep(1)
entered = False
output += msg
self.written += message

def first_thread():
nonlocal first_thread_initialized
first_thread_initialized = True
time.sleep(1)
assert second_thread_initialized
logger.debug("message 1")

def second_thread():
nonlocal second_thread_initialized
second_thread_initialized = True
time.sleep(1)
assert first_thread_initialized
def test_safe_logging():
barrier = Barrier(2)
counter = itertools.count()

sink = NonSafeSink()
logger.add(sink, format="{message}", catch=False)

def threaded():
barrier.wait()
logger.info("{}", next(counter))

threads = [Thread(target=threaded) for _ in range(2)]

for thread in threads:
thread.start()

for thread in threads:
thread.join()

assert sink.written == "0\n0\n1\n1\n"


def test_safe_adding_while_logging(writer):
barrier = Barrier(2)
counter = itertools.count()

sink_1 = NonSafeSink()
sink_2 = NonSafeSink()
logger.add(sink_1, format="{message}", catch=False)
logger.add(sink_2, format="-> {message}", catch=False)

def thread_1():
barrier.wait()
logger.info("{}", next(counter))

def thread_2():
barrier.wait()
time.sleep(0.5)
logger.debug("message 2")
logger.add(writer, format="{message}", catch=False)
logger.info("{}", next(counter))

threads = [Thread(target=thread_1), Thread(target=thread_2)]

for thread in threads:
thread.start()

logger.add(non_safe_sink, format="{message}", catch=False)
for thread in threads:
thread.join()

assert sink_1.written == "0\n0\n1\n1\n"
assert sink_2.written == "-> 0\n-> 0\n-> 1\n-> 1\n"
assert writer.read() == "1\n"


def test_safe_removing_while_logging():
barrier = Barrier(2)
counter = itertools.count()

sink_1 = NonSafeSink()
sink_2 = NonSafeSink()
a = logger.add(sink_1, format="{message}", catch=False)
b = logger.add(sink_2, format="-> {message}", catch=False)

def thread_1():
barrier.wait()
logger.info("{}", next(counter))

def thread_2():
barrier.wait()
time.sleep(0.5)
logger.remove(b)
logger.info("{}", next(counter))

threads = [Thread(target=first_thread), Thread(target=second_thread)]
threads = [Thread(target=thread_1), Thread(target=thread_2)]

for thread in threads:
thread.start()

for thread in threads:
thread.join()

out, err = capsys.readouterr()
assert out == err == ""
assert output == "message 1\nmessage 2\n"
assert sink_1.written == "0\n0\n1\n1\n"
assert sink_2.written == "-> 0\n-> 0\n"

0 comments on commit 62d13ce

Please sign in to comment.