I'm trying to make a simple module I can paste into my various projects to provide basic logging capabilities. I'm hoping to become less dependent on print
because:
- Using
logging
will allow me to quickly enable and disable what's being logged logging
has built-in file and formatting utilities- The built-in levels of
logging
are helpful
Here's the log.py
module:
import argparse
import logging
import sys
def parse_log_args(parser: argparse.ArgumentParser):
default_level = 'INFO'
parser.add_argument("-l", "--log", dest="loglevel",
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
default=default_level,
help=f"Set the logging level (default: {default_level})",
type=str)
parser.add_argument(
'-lf', '--logfile',
help="File for logging",
dest="logfile",
type=str
)
return parser
def conf_logging(logger: logging.Logger, loglevel: str, path=None):
if loglevel.upper() not in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'):
raise ValueError("Invalid logging level")
if path is None:
console_formatter = logging.Formatter('[%(levelname)s] %(message)s')
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(console_formatter)
else:
file_formatter = logging.Formatter(
('%(asctime)s [%(levelname)s] %(name)s.%(funcName)s'
'@ L%(lineno)d\n %(message)s'))
handler = logging.FileHandler(path, encoding='utf-8')
handler.setFormatter(file_formatter)
l_name = logging.getLevelName(loglevel)
handler.setLevel(l_name)
logger.setLevel(l_name)
logger.addHandler(handler)
return logger
Here's an example of its usage:
import logging
import argparse
from log import parse_log_args, conf_logging
logger = logging.getLogger(__name__)
parser = argparse.ArgumentParser()
parser = parse_log_args(parser)
args = parser.parse_args()
logger = conf_logging(logger, args.loglevel, args.logfile)
logger.debug("What")
logger.info("okay")
Which can be called as follows:
> python basic.py --log INFO
[INFO] okay
> python basic.py --log DEBUG
[DEBUG] What
[INFO] okay
I'm not super familiar with the logging
module, so I was a little surprised I had to have to set the logging lever for both the logger
and the handler
. Is there a better, more explicit way of writing this?
-
1\$\begingroup\$ Please don't modify your code. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information. \$\endgroup\$Sᴀᴍ Onᴇᴌᴀ– Sᴀᴍ Onᴇᴌᴀ ♦2019年05月01日 17:58:21 +00:00Commented May 1, 2019 at 17:58
-
\$\begingroup\$ @SᴀᴍOnᴇᴌᴀ understood. I will make sure not to change my code. \$\endgroup\$Seanny123– Seanny1232019年05月01日 18:00:08 +00:00Commented May 1, 2019 at 18:00
1 Answer 1
It's more natural to say "--loglevel", ...
than "--log", dest="loglevel", ...
. A log switch suggests that it takes an argument of a logfile output filename.
Not sure why you need dest
for the logfile argument.
You're specifying type of str
which is already the default, but maybe you did it to help with type annotations?
Raising "Invalid logging level" is maybe superfluous, better to let the underlying logging library deal with it. Looks like you're reaching across layers.
Naming your module log
is perhaps a bit adventurous, as sometimes folks concisely write log.info()
instead of logger.info()
.
In the example, I recommend deleting:
logger = logging.getLogger(__name__)
in favor of conf_logging(logging.getLogger(__name__), args.loglevel, args.logfile)
.
Perhaps you'd like to make it more convenient for callers, by defaulting loglevel, and letting a caller just pass in __name__
if that's all that is interesting.
Overall, it looks like a useful utility.