Here's what I mean. To do it properly you have to:
- get a logger
- set the verbosity level of the logger
- create a file or steam handler
- create a formatter (the default needs replacing)
- add the formatter to the handler
- add the handler to the logger
- do this all again if you want to mirror to stderr AND to a file (which is why I started using logging in the first place)
- put in code to shut down the logging (makes sure the streams get flushed) and for safety use the atexit module, meaning
- import atexit
- register the shutdown
- add an exception hook so that we can log uncaught exceptions too
To be fair, there is a "simple" way to use logging which is to just use the logging module functions "BasicConfig()" and "debug|info|warning|error|etc()" functions without getting a logger for your module. But it doesn't give the behaviour I want and even they prefer you don't use it in this manner.
What I believe is missing is a set of helper-functions and/or sytactic sugar to handle common tasks.
- let more things like Level and Handlers be put in the argument to getLogger
- automatically wrap common things like a File-like object and filename string into a handler instead of having the need to explicitly make one.
- an at-exit shutdown should be somewhat implicit (maybe an option to turn it off) as well as the option to trap other exceptions
- one line (minus "import") to get a logger for my module with any optional formatting and whatnot.
- one line to configure the root logger with all options, that can deal with an array of logging destinations, that will auto-interpret formatting strings and destinations instead of needing to create sub-handlers and formatters, etc.
def add_to_logging(log,whereto=None,level=10,format="%(levelname)s: %(message)s",dateformat='%Y%m%d_%H%M%S'): ''' shortuct to attach a destination to an existing logging object logfile can be file or gzip or stream or None(meaning stderr) ''' if whereto is None: whereto = sys.stderr if isinstance(whereto,(str,unicode)): fp = opener(whereto,'w') else: fp = whereto fh = logging.StreamHandler(fp) fh.setLevel(level) if format: formatter = logging.Formatter(format,dateformat) fh.setFormatter(formatter) log.addHandler(fh) def setupLogging(logname=None,rootname='',timestamp=False,consoleLevel=20): ''' shortcut to set up a dual stderr/logname LOGGING stream default level for file is DEBUG, for console is INFO (set consoleLevel to 0 to turn off console) SEE PYTHON LOGGING DOCUMENTATION FOR LOGGING BEHAVIOR returns a logging object''' import atexit logger = logging.getLogger(rootname) logger.setLevel(logging.DEBUG) dateformat='%Y%m%d_%H%M%S' # change the formatting if timestamp fmtstring = "%(levelname)s: %(message)s" if rootname is not None and rootname != '': fmtstring = "%(name)s:" + fmtstring if timestamp: fmtstring = "[%(asctime)s:%(name)s:%(lineno)s:%(levelname)s] %(message)s" # add a file if specified if logname: assert isinstance(logname,(str,unicode)) #logging.basicConfig(filename=logname,format=fmtstring,dateformat=dateformat) add_to_logging(logger,logname,format=fmtstring,dateformat=dateformat) # add a console if consoleLevel!=0: add_to_logging(logger,sys.stderr,format=fmtstring,level=consoleLevel,dateformat=dateformat) # cleanup and exception handling atexit.register(logging.shutdown) # the following will capture exceptions to the logs as well sys.excepthook = lambda *x: logger.error('Uncaught Exception',exc_info=x) return(logger)
In the above "opener()" is a separate function I have that wraps opening a filename, file object, pipe, or what have you depending on the input and optionally with encoding. Sometimes I miss how easy that is dealt with in Perl.
No comments:
Post a Comment