-
Notifications
You must be signed in to change notification settings - Fork 709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to handle a (possibly) misbehaving logger? #390
Comments
Hi @kace. ;) Is it possible that you inadvertently used the Answering your questions:
|
Thanks for the quick response @Delgan! I don't think I do too much with configuring the logger aside from setting up an intercept handler (following the documentation), adding a field to the extra's dictionary, and setting a custom log format (using the custom extra's dict). Below you can see some snippets from the convenience module where I wrap Loguru. class InterceptHandler(logging.Handler):
'''https://github.com/Delgan/loguru#entirely-compatible-with-standard-logging'''
def emit(self, record):
# Get corresponding Loguru level if it exists
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
# HACK must do a .bind() here instead of passing `id` as a kwarg to the call to .log()
# https://github.com/Delgan/loguru/issues/318
logger.opt(depth=depth, exception=record.exc_info).bind(id=record.name).log(
level,
record.getMessage(),
)
def enable_intercept_logging():
logging.basicConfig(handlers=[InterceptHandler()], level=0)
logger.debug(f"Redirecting from stdlib logging")
def disable_intercept_logging():
l = logging.getLogger()
for handler in l.handlers[:]:
if isinstance(handler, InterceptHandler):
l.removeHandler(handler)
logger.debug(f"Redirecting from stdlib logging disabled")
def set_intercept_level(level):
l = logging.getLogger()
l.setLevel(level)
logger.debug(f"Redirect log level set to {level}")
FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{thread.name: <12}</cyan> | <m>{extra[id]}</m> | <level>{message}</level>"
logger = loguru.logger
logger.configure(
handlers=[
{"sink": sys.stderr, "format": FORMAT},
],
extra={"id": "-"},
)
logger = logger.bind(id="logging") Where I add sinks is pretty straightforward also - I'll be updating these to roll on either time or space following one of your documented recipes. logger.add(
"agent_{time}.log",
retention=10,
rotation="100 MB",
compression="tar.gz",
level="INFO",
)
logger.add(
"agent_debug_{time}.log",
retention=2,
rotation="100 MB",
compression="tar.gz",
level="DEBUG",
backtrace=True,
diagnose=True,
)
I might try capturing stdout/stderr while |
Hi @kace. I made a small test. It seems the Apart from that, I don't see any issues with your configuration. |
Hi @Delgan. I suspected that myself at first, but it seems my deadlock is completely unrelated to log file size or log rotation. In my case the whole application appears to be frozen where all of the system resources are held indefinitely. I think no work (and no logging) is being done. I'm having a difficult time confirming that because the process is daemonized and isn't logging. Before asking you to spend more time on this, I'll run some more tests and do more to try to reproduce and resolve this on my side. To be honest I'm thinking this might not have anything to do with Loguru after all. I'll let you know what I come up with. Thanks for confirming my configuration. |
Hi @Delgan. Quick update. I've had some luck recreating this issue since I've last replied. It seems to be a race condition related to subprocess calls. What I've observed is that if I have a thread attempting to log at the same moment another thread is initiating a subprocess call then I get an immediate deadlock. I suspect this has to do with the process forking and copying of process memory/file descriptors, but haven't gotten that far yet. Here are some example logs:
To give you some context on the above, my If I'm right this maybe an issue with the subprocess module rather than Loguru. I'm using Python 3.8 for your reference. It's also possible that using the Before digging deeper I'll work on creating a simple code snippet to reproduce the error. I'll share this with you once it's done. Thanks! |
Thanks for the update! Surely, being able to reproduce the error is the hardest part. Then, we should be able to understand the problem much more easily. Your description reminds me of a somehow similar bug reported while mixing |
Good news @Delgan, I managed create a minimal example for reproducing this deadlock! I've successfully reproduced in both Python 3.6.13 and 3.8.5 using Loguru version 0.5.3 in both cases. Some notes about the example:
import signal
import subprocess
import threading
import time
from loguru import logger
STOP_EVENT = threading.Event()
def sigchld_handler(signum, frame):
logger.warning("SIGCHLD recieved - ignoring")
signal.signal(signal.SIGCHLD, sigchld_handler)
def stop(signum, frame):
logger.warning(f"Signal {signum} recieved - stop requested")
STOP_EVENT.set()
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, stop)
def run_process():
while not STOP_EVENT.is_set():
subprocess.run(["ls", "-la"], stdout=subprocess.DEVNULL)
if __name__ == "__main__":
threads = []
for _ in range(2):
threads.append(threading.Thread(target=run_process, name=f"ProcessThread-{_}"))
for t in threads:
t.start()
logger.info(f"{t.name} started")
# Keep the main thread alive
while not STOP_EVENT.is_set():
time.sleep(0.1)
logger.info("tick")
# Stop cleanly
for t in threads:
t.join()
logger.info("Exiting.") Output from hanging_threads From what I can tell the deadlock is caused by a lock in the
Also note that during my tests with my full application I would often see two back-to-back signals of the same signal type right before the deadlock. As a temporary fix for this deadlock I've removed all logging calls from my signal handlers. This seems to have worked as I haven't had any issues for the last 12 hours - for reference I used to see problems within just a couple of hours. Let me know what you think! |
Wow! Congratulations on successfully tracking down the problem! This is similar to this StackOverflow question. Basically, the However, logging functions are not re-entrant. The sinks are protected by a Unfortunately, I don't think there exists a possible solution. I should probably add in the documentation that one can't use the |
I assumed this was the case, but failed to find anything from the Python stdlib documentation listing this requirement (not sure if re-entrant signal handlers are the norm across all programming languages). Your explanation makes sense. Don't worry about coming up with a fix. I've already removed the logging calls from my Thanks again Delgan and great work on Loguru! |
Noting here for posterity, I have seen a very similar issue which relates not to threading, but to garbage collection.
In my case, during the process of emitting a log, and object with a As a (non-performant) workaround, I have added logic to the class LoguruInterceptHandler(logging.Handler):
def emit(self, record):
for frame_info in inspect.stack(0):
if "loguru" in frame_info.filename and "emit" in frame_info.function:
# Don't intercept `logging` calls whilst handling a `loguru` message, or this will create deadlock.
return
... # Forward to loguru
I suspect a more performant solution could be achieved by using a context var in the loguru handler, which ignores logs emitted during the process of emitting another log. |
Actually, I've managed to reproduce and I think what I've found constitutes a separate issue. I will raise and link it here. EDIT: #712 |
Hi Delgan,
In one of my multithreaded service daemons I'm seeing that the logger will occasionally stop altogether. By that I mean the application continues to run as if there are no errors but nothing is written to the logs and the files themselves aren't rotated. The file descriptors for the log files themselves are still open too. I suspect that I'm either blocking on a single logging call or that the there could have been an exception in the logger itself. Unfortunately I don't have a record of strerr to check this.
In the interest of debugging my problem I have a couple of questions:
catch
toFalse
when adding a sink) to the documentation? Would this basically mean that every logging call needs to be put into a try/except block?enqueue
toTrue
when adding a sink might solve my problem? Do logging calls eventually timeout?I use Loguru extensively in my work. Thanks for your effort!
The text was updated successfully, but these errors were encountered: