Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #83 from brianlenz/disable-stderr-logger
Browse files Browse the repository at this point in the history
Allow Disabling stderr Output
  • Loading branch information
metachris committed Mar 2, 2018
2 parents 6ddf3c2 + a24bf32 commit 4dda341
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 30 deletions.
7 changes: 7 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ and setting a minimum loglevel.
# Set a logfile (all future log messages are also saved there)
logzero.logfile("/tmp/logfile.log")
# Set a logfile (all future log messages are also saved there), but disable the default stderr logging
logzero.logfile("/tmp/logfile.log", disableStderrLogger=True)
# You can also set a different loglevel for the file handler
logzero.logfile("/tmp/logfile.log", loglevel=logging.ERROR)
Expand All @@ -182,11 +185,15 @@ Instead of using the default logger you can also setup specific logger instances
from logzero import setup_logger
logger1 = setup_logger(name="mylogger1", logfile="/tmp/test-logger1.log", level=logging.INFO)
logger2 = setup_logger(name="mylogger2", logfile="/tmp/test-logger2.log", level=logging.INFO)
logger3 = setup_logger(name="mylogger3", logfile="/tmp/test-logger3.log", level=logging.INFO, disableStderrLogger=True)
# By default, logging
logger1.info("info for logger 1")
logger2.info("info for logger 2")
# log to a file only, excluding the default stderr logger
logger3.info("info for logger 3")
Adding custom handlers (eg. SysLogHandler)
------------------------------------------
Expand Down
52 changes: 31 additions & 21 deletions logzero/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
colorama_init()


def setup_logger(name=None, logfile=None, level=logging.DEBUG, formatter=None, maxBytes=0, backupCount=0, fileLoglevel=None):
def setup_logger(name=None, logfile=None, level=logging.DEBUG, formatter=None, maxBytes=0, backupCount=0, fileLoglevel=None, disableStderrLogger=False):
"""
Configures and returns a fully configured logger instance, no hassles.
If a logger with the specified name already exists, it returns the existing instance,
Expand All @@ -108,34 +108,39 @@ def setup_logger(name=None, logfile=None, level=logging.DEBUG, formatter=None, m
:arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs.
:arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs.
:arg int fileLoglevel: Minimum `logging-level <https://docs.python.org/2/library/logging.html#logging-levels>`_ for the file logger (is not set, it will use the loglevel from the ``level`` argument)
:arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False.
:return: A fully configured Python logging `Logger object <https://docs.python.org/2/library/logging.html#logger-objects>`_ you can use with ``.debug("msg")``, etc.
"""
_logger = logging.getLogger(name or __name__)
_logger.propagate = False
_logger.setLevel(level)

# Reconfigure existing handlers
has_stream_handler = False
stderr_stream_handler = None
for handler in list(_logger.handlers):
if isinstance(handler, logging.StreamHandler):
has_stream_handler = True

if isinstance(handler, logging.FileHandler) and hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR):
# Internal FileHandler needs to be removed and re-setup to be able
# to set a new logfile.
_logger.removeHandler(handler)
continue
if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR):
if isinstance(handler, logging.FileHandler):
# Internal FileHandler needs to be removed and re-setup to be able
# to set a new logfile.
_logger.removeHandler(handler)
continue
elif isinstance(handler, logging.StreamHandler):
stderr_stream_handler = handler

# reconfigure handler
handler.setLevel(level)
handler.setFormatter(formatter or LogFormatter())

if not has_stream_handler:
stream_handler = logging.StreamHandler()
setattr(stream_handler, LOGZERO_INTERNAL_LOGGER_ATTR, True)
stream_handler.setLevel(level)
stream_handler.setFormatter(formatter or LogFormatter())
_logger.addHandler(stream_handler)
# remove the stderr handler (stream_handler) if disabled
if disableStderrLogger:
if stderr_stream_handler is not None:
_logger.removeHandler(stderr_stream_handler)
elif stderr_stream_handler is None:
stderr_stream_handler = logging.StreamHandler()
setattr(stderr_stream_handler, LOGZERO_INTERNAL_LOGGER_ATTR, True)
stderr_stream_handler.setLevel(level)
stderr_stream_handler.setFormatter(formatter or LogFormatter())
_logger.addHandler(stderr_stream_handler)

if logfile:
rotating_filehandler = RotatingFileHandler(filename=logfile, maxBytes=maxBytes, backupCount=backupCount)
Expand Down Expand Up @@ -286,7 +291,7 @@ def _safe_unicode(s):
return repr(s)


def setup_default_logger(logfile=None, level=logging.DEBUG, formatter=None, maxBytes=0, backupCount=0):
def setup_default_logger(logfile=None, level=logging.DEBUG, formatter=None, maxBytes=0, backupCount=0, disableStderrLogger=False):
"""
Deprecated. Use `logzero.loglevel(..)`, `logzero.logfile(..)`, etc.
Expand All @@ -305,9 +310,10 @@ def setup_default_logger(logfile=None, level=logging.DEBUG, formatter=None, maxB
:arg Formatter formatter: `Python logging Formatter object <https://docs.python.org/2/library/logging.html#formatter-objects>`_ (by default uses the internal LogFormatter).
:arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs.
:arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs.
:arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False.
"""
global logger
logger = setup_logger(name=LOGZERO_DEFAULT_LOGGER, logfile=logfile, level=level, formatter=formatter)
logger = setup_logger(name=LOGZERO_DEFAULT_LOGGER, logfile=logfile, level=level, formatter=formatter, disableStderrLogger=disableStderrLogger)
return logger


Expand Down Expand Up @@ -376,7 +382,7 @@ def formatter(formatter, update_custom_handlers=False):
_formatter = formatter


def logfile(filename, formatter=None, mode='a', maxBytes=0, backupCount=0, encoding=None, loglevel=None):
def logfile(filename, formatter=None, mode='a', maxBytes=0, backupCount=0, encoding=None, loglevel=None, disableStderrLogger=False):
"""
Setup logging to file (using a `RotatingFileHandler <https://docs.python.org/2/library/logging.handlers.html#rotatingfilehandler>`_ internally).
Expand All @@ -400,11 +406,15 @@ def logfile(filename, formatter=None, mode='a', maxBytes=0, backupCount=0, encod
:arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs.
:arg string encoding: Used to open the file with that encoding.
:arg int loglevel: Set a custom loglevel for the file logger, else uses the current global loglevel.
:arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False.
"""
# Step 1: If an internal RotatingFileHandler already exists, remove it
for handler in list(logger.handlers):
if isinstance(handler, RotatingFileHandler) and hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR):
logger.removeHandler(handler)
if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR):
if isinstance(handler, RotatingFileHandler):
logger.removeHandler(handler)
elif isinstance(handler, logging.StreamHandler) and disableStderrLogger:
logger.removeHandler(handler)

# Step 2: If wanted, add the RotatingFileHandler now
if filename:
Expand Down
42 changes: 33 additions & 9 deletions tests/test_logzero.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import logging

import logzero
import pytest


def test_write_to_logfile_and_stderr(capsys):
Expand Down Expand Up @@ -141,41 +142,46 @@ def test_multiple_loggers_one_logfile():
temp.close()


def test_default_logger():
def test_default_logger(disableStdErrorLogger=False):
"""
Default logger should work and be able to be reconfigured.
"""
logzero.reset_default_logger()
temp = tempfile.NamedTemporaryFile()
try:
logzero.setup_default_logger(logfile=temp.name)
logzero.setup_default_logger(logfile=temp.name, disableStderrLogger=disableStdErrorLogger)
logzero.logger.debug("debug1") # will be logged

# Reconfigure with loglevel INFO
logzero.setup_default_logger(logfile=temp.name, level=logging.INFO)
logzero.setup_default_logger(logfile=temp.name, level=logging.INFO, disableStderrLogger=disableStdErrorLogger)
logzero.logger.debug("debug2") # will not be logged
logzero.logger.info("info1") # will be logged

# Reconfigure with a different formatter
log_format = '%(color)s[xxx]%(end_color)s %(message)s'
formatter = logzero.LogFormatter(fmt=log_format)
logzero.setup_default_logger(logfile=temp.name, level=logging.INFO, formatter=formatter)
logzero.setup_default_logger(logfile=temp.name, level=logging.INFO, formatter=formatter, disableStderrLogger=disableStdErrorLogger)

logzero.logger.info("info2") # will be logged with new formatter
logzero.logger.debug("debug3") # will not be logged

with open(temp.name) as f:
content = f.read()
assert "] debug1" in content
assert "] debug2" not in content
assert "] info1" in content
assert "xxx] info2" in content
assert "] debug3" not in content
test_default_logger_output(content)

finally:
temp.close()


@pytest.mark.skip(reason="not a standalone test")
def test_default_logger_output(content):
assert "] debug1" in content
assert "] debug2" not in content
assert "] info1" in content
assert "xxx] info2" in content
assert "] debug3" not in content


def test_setup_logger_reconfiguration():
"""
Should be able to reconfigure without loosing custom handlers
Expand Down Expand Up @@ -263,3 +269,21 @@ def example():
assert example.__name__ == "example"
assert example.__doc__ == "example doc"


def test_default_logger_logfile_only(capsys):
"""
Run the ``test_default_logger`` with ``disableStdErrorLogger`` set to ``True`` and
confirm that no data is written to stderr
"""
test_default_logger(disableStdErrorLogger=True)
out, err = capsys.readouterr()
assert err == ''


def test_default_logger_stderr_output(capsys):
"""
Run the ``test_default_logger`` and confirm that the proper data is written to stderr
"""
test_default_logger()
out, err = capsys.readouterr()
test_default_logger_output(err)

0 comments on commit 4dda341

Please sign in to comment.