57

After reading the documentation on logging, I know I can use code like this to perform simple logging:

import logging

def main():
    logging.basicConfig(filename="messages.log",
                        level=logging.WARNING,
                        format='%(filename)s: '    
                                '%(levelname)s: '
                                '%(funcName)s(): '
                                '%(lineno)d:\t'
                                '%(message)s')

    logging.debug("Only for debug purposes\n")
    logging.shutdown()

main()

However, I realised I don't know how to change the format of log messages on a per-logger basis, since basicConfig is a module-level function. This code works for creating different loggers with different levels, names, etc. but is there a way to change the format of those log messages on a per-logger basis as well, in a way similar to basicConfig?

import inspect
import logging

def function_logger(level=logging.DEBUG):
    function_name = inspect.stack()[1][3]
    logger = logging.getLogger(function_name)
    logger.setLevel(level)
    logger.addHandler(logging.FileHandler("{0}.log".format(function_name)))
    return logger

def f1():
    f1_logger = function_logger()
    f1_logger.debug("f1 Debug message")
    f1_logger.warning("f1 Warning message")
    f1_logger.critical("f1 Critical message")

def f2():
    f2_logger = function_logger(logging.WARNING)
    f2_logger.debug("f2 Debug message")
    f2_logger.warning("f2 Warning message")
    f2_logger.critical("f2 Critical message")

def main():
    f1()
    f2()
    logging.shutdown()

main()
Ricardo Altamirano
  • 13,752
  • 21
  • 69
  • 104
  • You just helped me to figure out how to add filename, function name, and line number to my logs. Thanks – Xander YzWich Nov 19 '19 at 20:26
  • 1
    @XanderYzWich See https://docs.python.org/3/library/logging.html#logrecord-attributes for the full list of available attributes – Harm Sep 14 '20 at 10:43

2 Answers2

93

Try this

import logging

logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create file handler that logs debug and higher level messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# add the handlers to logger
logger.addHandler(ch)
logger.addHandler(fh)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

See http://docs.python.org/howto/logging-cookbook.html#multiple-handlers-and-formatters for more information

0 _
  • 9,294
  • 10
  • 71
  • 105
Pavel Reznikov
  • 2,699
  • 17
  • 17
  • +1, and +1 again (sadly no) for adding the code. I edited it into my [previous answer](http://stackoverflow.com/a/11581118/869912) to a logging question as well, and it works perfectly. – Ricardo Altamirano Jul 20 '12 at 15:50
  • 71
    I am always surprised it's not as simple as `logger = logging.getLogger('mylogger') logger.basicConfig(level=..., format=...)`... – theartofrain Feb 18 '15 at 21:58
2

You have to create or use an existing subclass of logging.Handler and call the setformatter() method of an instance thereof with an instance of a custom subclass of logger.Formatter. If you set the formatter for a handler that was already attached to the logger you want to modify the output of, you are fine, otherwise you have to retrieve a logger object with logging.getLogger() and call its addHandler() method with the instance of your handler class that you set the formatter on as the argument.

Silas Ray
  • 24,966
  • 5
  • 46
  • 61