37

Is there an easy way with python's logging module to send messages with a DEBUG or INFO level and the one with a higher level to different streams?

Is it a good idea anyway?

Dinoboff
  • 2,592
  • 2
  • 25
  • 26
  • 1
    See this for a solution: http://stackoverflow.com/questions/1383254/logging-streamhandler-and-standard-streams – guettli Mar 01 '12 at 13:42

8 Answers8

39
import logging
import sys

class LessThanFilter(logging.Filter):
    def __init__(self, exclusive_maximum, name=""):
        super(LessThanFilter, self).__init__(name)
        self.max_level = exclusive_maximum

    def filter(self, record):
        #non-zero return means we log this message
        return 1 if record.levelno < self.max_level else 0

#Get the root logger
logger = logging.getLogger()
#Have to set the root logger level, it defaults to logging.WARNING
logger.setLevel(logging.NOTSET)

logging_handler_out = logging.StreamHandler(sys.stdout)
logging_handler_out.setLevel(logging.DEBUG)
logging_handler_out.addFilter(LessThanFilter(logging.WARNING))
logger.addHandler(logging_handler_out)

logging_handler_err = logging.StreamHandler(sys.stderr)
logging_handler_err.setLevel(logging.WARNING)
logger.addHandler(logging_handler_err)

#demonstrate the logging levels
logger.debug('DEBUG')
logger.info('INFO')
logger.warning('WARNING')
logger.error('ERROR')
logger.critical('CRITICAL')

Implementation aside, I do think it is a good idea to use the logging facilities in python to output to the terminal, in particular because you can add another handler to additionally log to a file. If you set stdout to be INFO instead of DEBUG, you can even include additional DEBUG information that the user wouldn't standardly see in the log file.

Zoey Greer
  • 489
  • 4
  • 4
  • Where do you get the `LessThanFilter` class from. Can't find it in the Python3 libs. – buhtz Jul 31 '15 at 08:16
  • 4
    I wrote it (and provided the implementation), it's not in the standard libraries. – Zoey Greer Aug 25 '15 at 15:02
  • Thanks so much. This is the first place I've found anyone mention the default logging level of Warning. I lost a day to this. Some semblance to santiy is restored. THANK YOU! – penderi Dec 07 '17 at 15:55
  • 1
    It's perhaps worth noting that you can simplify the filter method - i.e. directly write `return record.levelno < self.max_level` instead of `return 1 if record.levelno < self.max_level else 0`. Also, with Python 3.2 or newer you can simply supply that filter as lambda. – maxschlepzig Sep 26 '20 at 16:23
6

Yes. You must define multiple handlers for your logging.

http://docs.python.org/library/logging.html#logging-to-multiple-destinations

http://docs.python.org/library/logging.handlers.html#module-logging.handlers

S.Lott
  • 373,146
  • 78
  • 498
  • 766
  • I would like only debug and info message in stdout and no warning or error messages. – Dinoboff Feb 21 '10 at 21:50
  • documentation is updated, correct link is at http://docs.python.org/library/logging.handlers.html#module-logging.handlers – gcb Feb 17 '12 at 06:13
5

I had the same problem and wrote a custom logging handler called SplitStreamHandler:

import sys
import logging

class SplitStreamHandler(logging.Handler):
    def __init__(self):
        logging.Handler.__init__(self)

    def emit(self, record):
        # mostly copy-paste from logging.StreamHandler
        try:
            msg = self.format(record)
            if record.levelno < logging.WARNING:
                stream = sys.stdout
            else:
                stream = sys.stderr
            fs = "%s\n"

            try:
                if (isinstance(msg, unicode) and
                    getattr(stream, 'encoding', None)):
                    ufs = fs.decode(stream.encoding)
                    try:
                        stream.write(ufs % msg)
                    except UnicodeEncodeError:
                        stream.write((ufs % msg).encode(stream.encoding))
                else:
                    stream.write(fs % msg)
            except UnicodeError:
                stream.write(fs % msg.encode("UTF-8"))

            stream.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)
intgr
  • 18,847
  • 5
  • 57
  • 67
  • I know it is slightly off-topic, but can someone explain the encode/decode try/catch logic here in terms of character encoding? – Chris Aug 20 '13 at 15:48
4

Just for your convenience adding everything together with the formatter in one package:

# shared formatter, but you can use separate ones:
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(threadName)s - %(message)s'
formatter = logging.Formatter(FORMAT)

# single app logger:
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)

# 2 handlers for the same logger:
h1 = logging.StreamHandler(sys.stdout)
h1.setLevel(logging.DEBUG)
# filter out everything that is above INFO level (WARN, ERROR, ...)
h1.addFilter(lambda record: record.levelno <= logging.INFO)
h1.setFormatter(formatter)
log.addHandler(h1)

h2 = logging.StreamHandler(sys.stderr)
# take only warnings and error logs
h2.setLevel(logging.WARNING)
h2.setFormatter(formatter)
log.addHandler(h2)

# profit:
log.info(...)
log.debug(...)

My use case was to redirect stdout to a datafile while seeing errors on the screen during processing.

yǝsʞǝla
  • 16,097
  • 2
  • 40
  • 64
2

right from the updated docs, it cover this case pretty well now.

http://docs.python.org/howto/logging.html#logging-advanced-tutorial

import sys # Add this.
import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler( sys.__stdout__ ) # Add this
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

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

i've mentioned on comments the two changes required from the example to make the output go to stdout. you may also use filters to redirect depending on the level.

more information to understand the changes is at http://docs.python.org/library/logging.handlers.html#module-logging.handlers

gcb
  • 13,157
  • 7
  • 65
  • 89
  • 5
    This only redirects to stdout, but does not filter depending on the level to stderr. – guettli Mar 01 '12 at 13:43
  • @guettli "you may also use filters to redirect depending on the level" – gcb Mar 09 '12 at 04:03
  • 3
    @gcb please could you show (!) how to add a suitable filter which does the job? Your suggestion means that "logger" still outputs to stderr, but "ch" outputs the same thing to stdout. Given that ch is dependent on logger, how could you set a filter on logger without it then applying also to ch? Confused. – mike rodent Mar 08 '14 at 11:31
0

Here's the compact solution I use (tested with Python 3.10):

import logging
import sys

root_logger = logging.getLogger()

# configure default StreamHandler to stderr
logging.basicConfig(
    format="%(asctime)s | %(levelname)-8s | %(filename)s:%(funcName)s(%(lineno)d) | %(message)s",
    level=logging.INFO,  # overall log level; DEBUG or INFO make most sense
)
stderr_handler = root_logger.handlers[0]  # get default handler
stderr_handler.setLevel(logging.WARNING)  # only print WARNING+

# add another StreamHandler to stdout which only emits below WARNING
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.addFilter(lambda rec: rec.levelno < logging.WARNING)
stdout_handler.setFormatter(stderr_handler.formatter)  # reuse the stderr formatter
root_logger.addHandler(stdout_handler)
Tom Pohl
  • 2,215
  • 3
  • 25
  • 32
-3

Not necessarily a good idea (it could be confusing to see info and debug messages mixed in with normal output!), but feasible, since you can have multiple handler objects and a custom filter for each of them, in order to pick and choose which log records each handler gets to handle.

Alex Martelli
  • 811,175
  • 162
  • 1,198
  • 1,373
  • 1
    please could you show (!) how to add a suitable filter which does the job? gcb's suggestion below means that "logger" still outputs to stderr, but "ch" outputs the same thing to stdout. Given that ch is dependent on logger, how could you set a filter on logger without it then applying also to ch? Confused. – mike rodent Mar 08 '14 at 11:33
  • There are platforms where the separation is required in order to improve operation tasks. Cloud Foundry is one such example. There, having all the DEBUG or INFO separated from the WARNING and ERROR logs is making troubleshooting via logs easier. – Gabriel Petrovay Jun 19 '20 at 15:52
-7
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import sys

class LessThenFilter(logging.Filter):
    def __init__(self, level):
        self._level = level
        logging.Filter.__init__(self)

    def filter(self, rec):
        return rec.levelno < self._level

log = logging.getLogger()
log.setLevel(logging.NOTSET)

sh_out = logging.StreamHandler(stream=sys.stdout)
sh_out.setLevel(logging.DEBUG)
sh_out.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
sh_out.addFilter(LessThenFilter(logging.WARNING))
log.addHandler(sh_out)

sh_err = logging.StreamHandler(stream=sys.stderr)
sh_err.setLevel(logging.WARNING)
sh_err.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
log.addHandler(sh_err)

logging.critical('x')
logging.error('x')
logging.warning('x')
logging.info('x')
logging.debug('x')
buhtz
  • 8,057
  • 11
  • 59
  • 115