Optimal solution for coloring logging output

Jean-Michel Pichavant jeanmichel at sequans.com
Mon Aug 3 14:47:56 CEST 2015


---- Original Message -----
> From: "c buhtz" <c.buhtz at posteo.jp>
> To: python-list at python.org
> Sent: Monday, 3 August, 2015 11:13:37 AM
> Subject: Optimal solution for coloring logging output
> 
> I don't want to ask how to do this because there are so many
> "solutions" about it.
> <http://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output>
> 
> There are so much different and part of unpythontic solutions I can
> not
> decide myself. What do you (as real pythontics) think about that.
> Which of the solutions fit to concepts of Python and its logging
> package?
> 
> Coloring means here not only the message itself. The (levelname)
> should
> be included in the coloring.
> For myself coloring the (levelname) would be enough to avoid to much
> color in the output.
> 
> 1.
> The solution itself shouldn't care about plattform differences
> because
> there are still some packages which are able to offer
> plattform-independent console-coloring. Which would you prefere? ;)
> 
> 2.
> Some solutions derive from StreamHandler or much more bad hacking the
> emit() function. I think both of them are not responsible for how the
> output should look or be presented.
> 
> 3.
> How to present the output is IMO the responsibility of a Formater,
> isn't
> it? So I should derive from the Formater.
> 
> What do you as Pythonics think of that? ;)

This is more or less how it could be done:

1/ use the module "curses" to get terminal colors (the doc suggests to use the "Console" moduel on windows)
2/ write a logging Formatter that will replace DEBUG/INFO/ERROR message by their colored version.


import curses
import logging
import string
import re

curses.setupterm()
class ColorFormat:
	#{ Foregroung colors
	BLACK = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_BLACK)
	RED = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_RED)
	GREEN = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_GREEN)
	YELLOW = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_YELLOW)
	BLUE = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_BLUE)
	MAGENTA = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_MAGENTA)
	CYAN = curses.tparm(curses.tigetstr('setaf'), curses.COLOR_CYAN)
	WHITE = curses.tparm(curses.tigetstr('setaf'), 9) # default white is 7, the 9 is a better white
	#{ Backgrounds colors
	BG_BLACK = curses.tparm(curses.tigetstr('setab'), curses.COLOR_BLACK)
	BG_RED = curses.tparm(curses.tigetstr('setab'), curses.COLOR_RED)
	BG_GREEN = curses.tparm(curses.tigetstr('setab'), curses.COLOR_GREEN)
	BG_YELLOW = curses.tparm(curses.tigetstr('setab'), curses.COLOR_YELLOW)
	BG_BLUE = curses.tparm(curses.tigetstr('setab'), curses.COLOR_BLUE)
	BG_MAGENTA = curses.tparm(curses.tigetstr('setab'), curses.COLOR_MAGENTA)
	BG_CYAN = curses.tparm(curses.tigetstr('setab'), curses.COLOR_CYAN)
	BG_WHITE = curses.tparm(curses.tigetstr('setab'), curses.COLOR_WHITE) 
	#{ Format codes
	BOLD = curses.tparm(curses.tigetstr('bold'), curses.A_BOLD)
	UNDERLINE = curses.tparm(curses.tigetstr('smul'), curses.A_UNDERLINE)
	BLINK = curses.tparm(curses.tigetstr('blink'), curses.A_BLINK)
	NO_FORMAT = curses.tparm(curses.tigetstr('sgr0'), curses.A_NORMAL)
	NO_COLOR = curses.tigetstr('sgr0')
	#}

def setFormat(attributeList):
	_set = '' # avoid collision with the builtin set type
	for attribute in attributeList:
		_set += getattr(ColorFormat, attribute, '')
	return _set

class ColorFormatter(logging.Formatter):
	def format(self, record):
		parameters = record.__dict__.copy()
		parameters['message'] = record.getMessage()
				
		# ------------------------------------------------------------------
		# Log Level Format : %(levelname)
		# ------------------------------------------------------------------
		fmt = self._fmt
		pattern = r'(%\(levelname\)(?:-?\d+)?s)'
		if record.levelno <= logging.DEBUG:
			fmt = re.sub(pattern, setFormat(['BLUE']) + r'\1' + 
						 setFormat(['NO_COLOR']), fmt)
		elif record.levelno <= logging.INFO:
			fmt = re.sub(pattern, setFormat(['CYAN']) + r'\1' + 
						 setFormat(['NO_COLOR']), fmt)
		elif record.levelno <= logging.WARNING:
			fmt = re.sub(pattern, setFormat(['MAGENTA']) + r'\1' + 
						 setFormat(['NO_COLOR']), fmt)
		elif record.levelno <= logging.ERROR:
			fmt = re.sub(pattern, setFormat(['RED','BOLD']) + r'\1' + 
						 setFormat(['NO_COLOR']), fmt)
		else:
			fmt = re.sub(pattern, setFormat(['WHITE', 'BOLD', 'BG_RED']) + 
						 r'\1' + setFormat(['NO_COLOR']), fmt)
		
		# ------------------------------------------------------------------
		# following is the classic format method from the default formatter
		# ------------------------------------------------------------------
		if string.find(fmt,"%(asctime)") >= 0:
			record.asctime = self.formatTime(record, self.datefmt)
			parameters['asctime'] = record.asctime
		s = fmt % parameters
		if record.exc_info:
			# Cache the traceback text to avoid converting it multiple times
			# (it's constant anyway)
			if not record.exc_text:
				record.exc_text = self.formatException(record.exc_info)
		if record.exc_text:
			if s[-1] != "\n":
				s = s + "\n"
			s = s + record.exc_text
		return s
	
if __name__ == '__main__':
	logger = logging.getLogger('Foo')
	logger.setLevel(logging.DEBUG)
	logger.addHandler(logging.StreamHandler())
	logger.handlers[-1].setFormatter(ColorFormatter('%(levelname)s - %(message)s'))
	logger.error('This is an error')
	logger.debug('This is an debug')


-- IMPORTANT NOTICE: 

The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.


More information about the Python-list mailing list