3

I am trying to format the output of the logging to have the levelname on the right side of the terminal always. I currently have a script that looks like:

import logging, os, time fn = 'FN' start = time.time() def getTerminalSize(): import os env = os.environ def ioctl_GWINSZ(fd): try: import fcntl, termios, struct, os cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) except: return return cr cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: cr = (env.get('LINES', 25), env.get('COLUMNS', 80)) return int(cr[1]), int(cr[0]) (width, _) = getTerminalSize() level_width = 8 message_width = width - level_width - 4 FORMAT = '%(message)-{len1:{width1}d}s [%(levelname){len2:{width2}d}s]'.format( len1 = message_width,_ len2 = level_width,_ width1 = len(str(message_width)),_ width2 = len(str(level_width))) logging.basicConfig(format=FORMAT, level="DEBUG") logging.debug("Debug Message") logging.info("Info Message") logging.warning("Warning Message") logging.error("Error Message") logging.critical("Critical Message") logging.info("Starting File: " + os.path.basename(fn) + "\n-----------------------------------------") logging.info("\tTo read data: %s"%(time.time() - start)) 

The output looks like:

Debug Message [ DEBUG] Info Message [ INFO] Warning Message [ WARNING] Error Message [ ERROR] Critical Message [CRITICAL] Starting File: Channel209.Raw32 ----------------------------------------- [ INFO] To read data: 0.281999826431 [ INFO] 

I would like the output to look something like this instead and can't quite figure it out:

Debug Message [ DEBUG] Info Message [ INFO] Warning Message [ WARNING] Error Message [ ERROR] Critical Message [CRITICAL] Starting File: Channel209.Raw32 ----------------------------------------- [ INFO] To read data: 0.281999826431 [ INFO] 

2 Answers 2

2

As @Carpetsmoker said, to do what I truly desired required creating a new formatter class overwriting the default.

The following class worked well for this process:

import logging import textwrap import itertools ''' MyFormatter class Adapted from: https://stackoverflow.com/questions/6847862/how-to-change-the-format-of-logged-messages-temporarily-in-python https://stackoverflow.com/questions/3096402/python-string-formatter-for-paragraphs Authors: Vinay Sajip, unutbu ''' class MyFormatter(logging.Formatter): #This function overwrites logging.Formatter.format #We conver the msg into the overall format we want to see def format(self,record): widths=[getTerminalSize()[0] - 12 ,10] form='{row[0]:<{width[0]}} {row[1]:<{width[1]}}' #Instead of formatting...rewrite message as desired here record.msg = self.Create_Columns(form,widths,[record.msg],["[%8s]"%record.levelname]) #Return basic formatter return super(MyFormatter,self).format(record) def Create_Columns(self,format_str,widths,*columns): ''' format_str describes the format of the report. {row[i]} is replaced by data from the ith element of columns. widths is expected to be a list of integers. {width[i]} is replaced by the ith element of the list widths. All the power of Python's string format spec is available for you to use in format_str. You can use it to define fill characters, alignment, width, type, etc. formatter takes an arbitrary number of arguments. Every argument after format_str and widths should be a list of strings. Each list contains the data for one column of the report. formatter returns the report as one big string. ''' result=[] for row in zip(*columns): #Create a indents for each row... sub = [] #Loop through for r in row: #Expand tabs to spaces to make our lives easier r = r.expandtabs() #Find the leading spaces and create indend character if r.find(" ") == 0: i = 0 for letters in r: if not letters == " ": break i += 1 sub.append(" "*i) else: sub.append("") #Actually wrap and creat the string to return...stolen from internet lines=[textwrap.wrap(elt, width=num, subsequent_indent=ind) for elt,num,ind in zip(row,widths,sub)] for line in itertools.izip_longest(*lines,fillvalue=''): result.append(format_str.format(width=widths,row=line)) return '\n'.join(result) 

It relies on getting the terminal size in some function called getTerminalSize. I used Harco Kuppens' Method that I will not repost here.

An example driver program is as follows, where MyFormatter and getTerminalSize are located in Colorer:

import logging import Colorer logger = logging.getLogger() logger_handler = logging.StreamHandler() logger.addHandler(logger_handler) logger_handler.setFormatter(Colorer.MyFormatter("%(message)s")) logger.setLevel("DEBUG") logging.debug("\t\tTHIS IS A REALY long DEBUG Message that works and wraps around great........") logging.info(" THIS IS A REALY long INFO Message that works and wraps around great........") logging.warning("THIS IS A REALY long WARNING Message that works and wraps around great........") logging.error("\tTHIS IS A REALY long ERROR Message that works and wraps around great........") logging.critical("THIS IS A REALY long CRITICAL Message that works and wraps around great........") 

Where the output is (commented for readability):

# THIS IS A REALY long DEBUG Message that works and [ DEBUG] # wraps around great........ # THIS IS A REALY long INFO Message that works and wraps around [ INFO] # great........ # THIS IS A REALY long WARNING Message that works and wraps around [ WARNING] # great........ # THIS IS A REALY long ERROR Message that works and wraps [ ERROR] # around great........ # THIS IS A REALY long CRITICAL Message that works and wraps around [CRITICAL] # great........ 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for your answer. Please note that there are multiple PEP8 issues with your code.
1

I modified the last lines to look like:

logging.info("Starting File: %s" % os.path.basename(fn)) logging.info("%s" % ('-' * 15)) logging.info(" To read data: %s" % (time.time() - start)) 

Your error was using a newline (\n) and tab (\t) character.

Or, if you must keep the newline (which seems rather odd to me), you could manually add the spaces, like so:

logging.info("Starting File: %s\n%s%s" % ( os.path.basename(fn), ('-' * 15), ' ' * (width - 15 - 12))) 

Other notes

You should create a Minimal, Complete, Tested and Readable. Your code wasn't working, I needed to modify a few things just to get the example running. See your messages edit history for what I had to edit.

Since Python 3.3, there's os.get_terminal_size. If that isn't available, then doing subprocess.call(['tput cols'], shell=True) seems a whole lot simpler to me...

2 Comments

What if one of the strings was just longer than the terminal window? likeL logging.critical("This is a really long long long long long long long long srting")
@DavidFolkner Then you'll need to manually pad stuff, like I did in my example. You could probably also make your own Formatter class, and use more advanced logic (ie. one which autodetects this sort of thing), but that's obviously more work.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.