日志格式化:正确对齐级别名称

3

我正在尝试格式化logging的输出,使得levelname始终在终端的右侧。我目前有一个脚本看起来像:

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))

输出的结果如下所示:
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]

我希望输出看起来像这样,但我无法完全想清楚:
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个回答

2
如 @Carpetsmoker 所说,为了做到我真正想要的事情,需要创建一个新的格式化程序类并覆盖默认值。
以下类适用于此过程:
import logging
import textwrap
import itertools
'''
MyFormatter class
Adapted from: https://dev59.com/Kmw15IYBdhLWcg3wFHpZ
              https://dev59.com/007Sa4cB1Zd3GeqP45pm
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)

这需要在某个名为getTerminalSize的函数中获取终端大小。我使用了Harco Kuppens' Method,我不会在此重复。

下面是一个驱动程序示例,其中MyFormattergetTerminalSize位于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........")

输出结果如下(为了易读性已加注释):
#                 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........

感谢您的回答。请注意,您的代码存在多个PEP8问题。 - Wtower

1
我把最后几行修改成了这样:

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

你的错误在于使用了换行符(\n)和制表符(\t)。如果你一定要保留换行符(这对我来说似乎有点奇怪),那么你可以手动添加空格,像这样:
logging.info("Starting File: %s\n%s%s" % (
    os.path.basename(fn),
    ('-' * 15),
    ' ' * (width - 15 - 12)))

其他注意事项

你应该创建一个最小,完整,可测试和易读的代码。你的代码没有工作,我需要修改一些东西才能让示例运行。请参见您的消息编辑历史记录以了解我所必须编辑的内容。

自Python 3.3以来,有os.get_terminal_size。如果不可用,则执行subprocess.call(['tput cols'], shell=True)对我来说会更简单...


如果其中一个字符串比终端窗口更长怎么办?例如:logging.critical("This is a really long long long long long long long long srting") - David Folkner
@DavidFolkner 那么你就需要手动填充一些内容,就像我在示例中所做的那样。你可能还可以制作自己的 Formatter 类,并使用更高级的逻辑(即自动检测这种情况的逻辑),但这显然需要更多的工作。 - Martin Tournoij

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接