在我们日常的程序开发过程中免不了调试,输出调试日志到控制台或者输出到文件。
Python的logging模块提供了通用的日志系统,熟练使用logging模块可以方便开发者开发第三方模块或者是自己的Python应用。同样这个模块提供不同的日志级别,并可以采用不同的方式记录日志,比如文件,HTTP、GET/POST,SMTP,Socket等,甚至可以自己实现具体的日志记录方式。
Python中的logging模块与Java/Android中的log4j的机制是一样的,只是具体的实现细节不同。logging模块提供logger,handler,filter,formatter。与log4j类似,logger,handler和日志消息的调用可以有具体的日志级别(Level),只有在日志消息的级别大于logger和handler的级别。
logger:提供日志接口,供应用代码使用。logger最长用的操作有两类:配置和发送日志消息。可以通过logging.getLogger(name)获取logger对象,如果不指定name则返回root对象,多次使用相同的name调用getLogger方法返回同一个logger对象。
handler:将日志记录(log record)发送到合适的目的地(destination),比如文件,socket等。一个logger对象可以通过addHandler方法添加0到多个handler,每个handler又可以定义不同日志级别,以实现日志分级过滤显示。
filter:提供一种优雅的方式决定一个日志记录是否发送到handler。

formatter:指定日志记录输出的具体格式。formatter的构造方法需要两个参数:消息的格式字符串和日期字符串,这两个参数都是可选的。

#-*- coding:utf-8 -*-

import logging
import logging.handlers
   
class Logger():
    def __init__(self, tag="", filename="python.log", loglevel=1, toConsole=True, toFile=True):
        '''
           既要把日志输出到控制台, 还要写入日志文件
        '''
        # 创建一个logger
        # 返回一个logger实例,如果没有指定name,返回root logger。
        # 只要name相同,返回的logger实例都是同一个而且只有一个,即name和logger实例是一一对应的。
        # 这意味着,无需把logger实例在各个模块中传递。只要知道name,就能得到同一个logger实例。
        # tag就是Logger的名字,相当于Android中的TAG
        self.logger = logging.getLogger(tag)
        
        # 防止重复记录日志的问题
        if not self.logger.handlers:
            
            # 定义handler的输出格式:
            #   [时间][代码文件名,函数名:行号][logger名(相当于Android中的TAG)]: 输出日志信息
            format_dict = {
               1 : logging.Formatter('[%(asctime)s][%(filename)s,%(funcName)s:%(lineno)s][%(name)s]: %(message)s'),
               2 : logging.Formatter('[%(asctime)s][%(filename)s,%(funcName)s:%(lineno)s][%(name)s]: %(message)s'),
               3 : logging.Formatter('[%(asctime)s][%(filename)s,%(funcName)s:%(lineno)s][%(name)s]: %(message)s'),
               4 : logging.Formatter('[%(asctime)s][%(filename)s,%(funcName)s:%(lineno)s][%(name)s]: %(message)s'),
               5 : logging.Formatter('[%(asctime)s][%(filename)s,%(funcName)s:%(lineno)s][%(name)s]: %(message)s')
            }
            formatter = format_dict[int(loglevel)]
            
            # 指定最低的日志级别,低于指定级别的将被忽略。
            # 级别高低顺序:NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL
            # 如果把looger的级别设置为INFO, 那么小于INFO级别的日志都不输出, 大于等于INFO级别的日志都输出 
            self.logger.setLevel(logging.DEBUG)

            # 创建一个handler,用于写入日志文件
            # 需要创建/写文件的权限
            try:
                if toFile:
                    fh = logging.FileHandler(filename)
                    fh.setLevel(logging.DEBUG)
                    fh.setFormatter(formatter)
                    self.logger.addHandler(fh)
            except Exception as e:
                print e
            
            # 再创建一个handler,用于输出到控制台
            if toConsole:
                ch = logging.StreamHandler()
                ch.setLevel(logging.DEBUG)
                ch.setFormatter(formatter)
                self.logger.addHandler(ch)
   
    def getlogger(self):
        return self.logger

        
def func():
    Logger(tag="test").getlogger().debug("HHHHHHHHHHHHHHHHHHHHHHHHHHH")

    
if __name__ == "__main__":        
    Logger(tag="test").getlogger().debug("YYYYYYYYYYYYYYYYYYYYYYY")
    func()



输出到控制台:

user@linux:~/share/test$ sudo python logger.py 
[2017-05-15 10:06:15,533][logger.py,<module>:65][test]: YYYYYYYYYYYYYYYYYYYYYYY
[2017-05-15 10:06:15,534][logger.py,func:61][test]: HHHHHHHHHHHHHHHHHHHHHHHHHHH



输出到文件:

[2017-05-15 10:06:15,533][logger.py,<module>:65][test]: YYYYYYYYYYYYYYYYYYYYYYY
[2017-05-15 10:06:15,534][logger.py,func:61][test]: HHHHHHHHHHHHHHHHHHHHHHHHHHH



 


当然,也可以不用loggin也同样能编写日志输出系统:

#-*- coding:utf-8 -*-

import sys
import time
 
def logOut(msg, tag="", filename="python.log", toConsole=True, toFile=True):
    str_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) # 获得当前时间
    fname = sys._getframe().f_back.f_code.co_filename                          # 调用者文件名
    funcname = sys._getframe().f_back.f_code.co_name                           # 调用者函数名
    lineno   = sys._getframe().f_back.f_lineno                                 # 调用者所在行号
    info = str.format("[%s][%s,%s:%d][%s] %s" % (str_time, fname, funcname, lineno, tag, msg))

    if toConsole:
        print info
        
    if toFile:
        try:
            with open(filename, "a") as f:        
                f.write(info); 
                f.write("\n"); 
        except Exception as e:
            print e


def func():
    logOut("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", tag="test")
    
if __name__ == "__main__":
    logOut("YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", tag="test")
    func()



输出效果是一样的。