https://www.u3v3.com/ar/1330
http://www.jianshu.com/p/1772306cb3db
https://groups.google.com/forum/#!topic/python-cn/9ViSyvIX1S4
http://python.jobbole.com/87300/
logging.getLogger("abc.xyz")
会创建两个logger对象,一个是abc父对象,一个是xyz子对象,同时abc没有父对象所以它的父对象是root。logging 多进程写同一个文件
logging 包的官方文档中明确说过
Although logging is thread-safe, and logging to a single file from multiple threads in a single process is supported, logging to a single file from multiple processes is not supported,
because there is no standard way to serialize access to a single file across multiple processes in Python
没有标准的方式让多进程写同一个日志文件
- 在linux下, 我们通过
o_append
方式向文件中写入数据时, 在没有锁的情况下, 可以实现数据写入的原子性
- uwsgi的log_master=1选项
但是,如果使用了Rotating的日志格式,在多进程进程切割时,还是会出错的,所以终极办法还是改写默认的handler
import os import time import multiprocessing from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler class RotatingFileHandler(RotatingFileHandler): lock=multiprocessing.Lock() version=multiprocessing.Value("i",0) def __init__(self, *args, **kwargs): super(RotatingFileHandler, self).__init__(*args, **kwargs) self._version = 0 def doRollover(self): """ Do a rollover, as described in __init__(). """ if self.stream: self.stream.close() self.stream = None with self.__class__.lock: if self._version == self.__class__.version.value: if self.backupCount > 0: for i in range(self.backupCount - 1, 0, -1): sfn = "%s.%d" % (self.baseFilename, i) dfn = "%s.%d" % (self.baseFilename, i + 1) if os.path.exists(sfn): #print "%s -> %s" % (sfn, dfn) if os.path.exists(dfn): os.remove(dfn) os.rename(sfn, dfn) dfn = self.baseFilename + ".1" if os.path.exists(dfn): os.remove(dfn) # Issue 18940: A file may not have been created if delay is True. if os.path.exists(self.baseFilename): os.rename(self.baseFilename, dfn) self.__class__.version.value += 1 self._version += 1 if not self.delay: self.stream = self._open() class TimedRotatingFileHandler(TimedRotatingFileHandler): lock=multiprocessing.Lock() version=multiprocessing.Value("i",0) def __init__(self,*args,**kwargs): super(TimedRotatingFileHandler,self).__init__(*args,**kwargs) self._version = 0 def rename_file(self): # get the time that this sequence started at and make it a TimeTuple t = self.rolloverAt - self.interval if self.utc: timeTuple = time.gmtime(t) else: timeTuple = time.localtime(t) dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple) if os.path.exists(dfn): os.remove(dfn) os.rename(self.baseFilename, dfn) if self.backupCount > 0: # find the oldest log file and delete it #s = glob.glob(self.baseFilename + ".20*") #if len(s) > self.backupCount: # s.sort() # os.remove(s[0]) for s in self.getFilesToDelete(): os.remove(s) def doRollover(self): if self.stream: self.stream.close() self.stream = None with self.__class__.lock: if self.__class__.version.value==self._version: self.rename_file() self.__class__.version.value+=1 self._version+=1 #print "%s -> %s" % (self.baseFilename, dfn) self.mode = 'a' self.stream = self._open() currentTime = int(time.time()) newRolloverAt = self.computeRollover(currentTime) while newRolloverAt <= currentTime: newRolloverAt = newRolloverAt + self.interval #If DST changes and midnight or weekly rollover, adjust for this. if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: dstNow = time.localtime(currentTime)[-1] dstAtRollover = time.localtime(newRolloverAt)[-1] if dstNow != dstAtRollover: if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour newRolloverAt = newRolloverAt - 3600 else: # DST bows out before next rollover, so we need to add an hour newRolloverAt = newRolloverAt + 3600 self.rolloverAt = newRolloverAt
==================================================
http://blog.csdn.net/chosen0ne/article/details/7319306
http://my.oschina.net/leejun2005/blog/126713
1. logging介绍
Python的logging模块提供了通用的日志系统,可以方便第三方模块或者是应用使用。这个模块提供不同的日志级别,并可以采用不同的方式记录日志,比如文件,HTTP GET/POST,SMTP,Socket等,甚至可以自己实现具体的日志记录方式。
logging模块与log4j的机制是一样的,只是具体的实现细节不同。模块提供logger,handler,filter,formatter。
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的构造方法需要两个参数:消息的格式字符串和日期字符串,这两个参数都是可选的。
与log4j类似,logger,handler和日志消息的调用可以有具体的日志级别(Level),只有在日志消息的级别大于logger和handler的级别。
import logging import logging.handlers LOG_FILE = 'tst.log' handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes = 1024*1024, backupCount = 5) # 实例化handler fmt = '%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(message)s' formatter = logging.Formatter(fmt) # 实例化formatter handler.setFormatter(formatter) # 为handler添加formatter logger = logging.getLogger('tst') # 获取名为tst的logger logger.addHandler(handler) # 为logger添加handler logger.setLevel(logging.DEBUG) logger.info('first info message') logger.debug('first debug message')
输出:
- 2012-03-04 23:21:59,682 - log_test.py:16 - tst - first info message
- 2012-03-04 23:21:59,682 - log_test.py:17 - tst - first debug message
2. logging的配置
logging的配置可以采用python代码或是配置文件。python代码的方式就是在应用的主模块中,构建handler,handler,formatter等对象。而配置文件的方式是将这些对象的依赖关系分离出来放在文件中。比如前面的例子就类似于python代码的配置方式。这里看一下采用配置文件的方式。
- import logging
- import logging.config
- logging.config.fileConfig("logging.conf") # 采用配置文件
- # create logger
- logger = logging.getLogger("simpleExample")
- # "application" code
- logger.debug("debug message")
- logger.info("info message")
- logger.warn("warn message")
- logger.error("error message")
- logger.critical("critical message")
loggin.conf采用了模式匹配的方式进行配置,正则表达式是r'^[(.*)]$',从而匹配出所有的组件。对于同一个组件具有多个实例的情况使用逗号‘,’进行分隔。对于一个实例的配置采用componentName_instanceName配置块。使用这种方式还是蛮简单的。
- [loggers]
- keys=root,simpleExample
- [handlers]
- keys=consoleHandler
- [formatters]
- keys=simpleFormatter
- [logger_root]
- level=DEBUG
- handlers=consoleHandler
- [logger_simpleExample]
- level=DEBUG
- handlers=consoleHandler
- qualname=simpleExample
- propagate=0
- [handler_consoleHandler]
- class=StreamHandler
- level=DEBUG
- formatter=simpleFormatter
- args=(sys.stdout,)
- [formatter_simpleFormatter]
- format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
- datefmt=
在指定handler的配置时,class是具体的handler类的类名,可以是相对logging模块或是全路径类名,比如需要RotatingFileHandler,则class的值可以为:RotatingFileHandler或者logging.handlers.RotatingFileHandler。args就是要传给这个类的构造方法的参数,就是一个元组,按照构造方法声明的参数的顺序。
输出:
- 2012-03-06 00:09:35,713 - simpleExample - DEBUG - debug message
- 2012-03-06 00:09:35,713 - simpleExample - INFO - info message
- 2012-03-06 00:09:35,714 - simpleExample - WARNING - warn message
- 2012-03-06 00:09:35,714 - simpleExample - ERROR - error message
- 2012-03-06 00:09:35,714 - simpleExample - CRITICAL - critical message
这里还要明确一点,logger对象是有继承关系的,比如名为a.b和a.c的logger都是名为a的子logger,并且所有的logger对象都继承于root。如果子对象没有添加handler等一些配置,会从父对象那继承。这样就可以通过这种继承关系来复用配置。
3. 多模块使用logging
=====================================
Java 中最通用的日志模块莫过于 Log4j 了,在 python 中,也自带了 logging 模块,该模块的用法其实和 Log4j 类似。
Python 使用logging模块记录日志涉及四个主要类,使用官方文档中的概括最为合适:
logger提供了应用程序可以直接使用的接口;
handler将(logger创建的)日志记录发送到合适的目的输出;
filter提供了细度设备来决定输出哪条日志记录;
formatter决定日志记录的最终输出格式。
logging模块是在2.3新引进的功能,下面是一些常用的类和模块级函数
模块级函数
logging.getLogger([name]):返回一个logger对象,如果没有指定名字将返回root logger
logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical():设定root logger的日志级别
logging.basicConfig():用默认Formatter为日志系统建立一个StreamHandler,设置基础配置并加到root logger中
Logger
每个程序在输出信息之前都要获得一个Logger。Logger通常对应了程序的模块名,比如聊天工具的图形界面模块可以这样获得它的Logger:
LOG=logging.getLogger(”chat.gui”)
而核心模块可以这样:
LOG=logging.getLogger(”chat.kernel”)
Logger.setLevel(lel):指定最低的日志级别,低于lel的级别将被忽略。debug是最低的内置级别,critical为最高
Logger.addFilter(filt)、Logger.removeFilter(filt):添加或删除指定的filter
Logger.addHandler(hdlr)、Logger.removeHandler(hdlr):增加或删除指定的handler
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical():可以设置的日志级别
设置logger的level, level有以下几个级别:
NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL
如果把looger的级别设置为INFO, 那么小于INFO级别的日志都不输出, 大于等于INFO级别的日志都输出
Handlers
handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler
Handler.setLevel(lel):指定被处理的信息级别,低于lel级别的信息将被忽略
Handler.setFormatter():给这个handler选择一个格式
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或删除一个filter对象
Formatters
Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S,下面是Formatter常用的一些信息
%(name)s |
Logger的名字 |
%(levelno)s |
数字形式的日志级别 |
%(levelname)s |
文本形式的日志级别 |
%(pathname)s |
调用日志输出函数的模块的完整路径名,可能没有 |
%(filename)s |
调用日志输出函数的模块的文件名 |
%(module)s |
调用日志输出函数的模块名 |
%(funcName)s |
调用日志输出函数的函数名 |
%(lineno)d |
调用日志输出函数的语句所在的代码行 |
%(created)f |
当前时间,用UNIX标准的表示时间的浮 点数表示 |
%(relativeCreated)d |
输出日志信息时的,自Logger创建以 来的毫秒数 |
%(asctime)s |
字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 |
%(thread)d |
线程ID。可能没有 |
%(threadName)s |
线程名。可能没有 |
%(process)d |
进程ID。可能没有 |
%(message)s |
用户输出的消息 |
设置过滤器
细心的朋友一定会发现前文调用logging.getLogger()时参数的格式类似于“A.B.C”。采取这样的格式其实就是为了可以配置过滤器。看一下这段代码:
LOG=logging.getLogger(”chat.gui.statistic”)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter(’%(asctime)s %(levelname)s %(message)s’)
console.setFormatter(formatter)
filter=logging.Filter(”chat.gui”)
console.addFilter(filter)
LOG.addHandler(console)
和前面不同的是我们在Handler上添加了一个过滤器。现在我们输出日志信息的时候就会经过过滤器的处理。名为“A.B”的过滤器只让名字带有 “A.B”前缀的Logger输出信息。可以添加多个过滤器,只要有一个过滤器拒绝,日志信息就不会被输出。另外,在Logger中也可以添加过滤器。
每个Logger可以附加多个Handler。接下来我们就来介绍一些常用的Handler:
1) logging.StreamHandler
使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。它的构造函数是:
StreamHandler([strm])
其中strm参数是一个文件对象。默认是sys.stderr
2) logging.FileHandler
和StreamHandler类似,用于向一个文件输出日志信息。不过FileHandler会帮你打开这个文件。它的构造函数是:
FileHandler(filename[,mode])
filename是文件名,必须指定一个文件名。
mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。
3) logging.handlers.RotatingFileHandler
这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建 一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把 文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。它的构造函数是:
RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
其中filename和mode两个参数和FileHandler一样。
maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。
backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。
4) logging.handlers.TimedRotatingFileHandler
这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。它的构造函数是:
TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。
interval是时间间隔。
when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:
S 秒
M 分
H 小时
D 天
W 每星期(interval==0时代表星期一)
midnight 每天凌晨
5) logging.handlers.SocketHandler
6) logging.handlers.DatagramHandler
以上两个Handler类似,都是将日志信息发送到网络。不同的是前者使用TCP协议,后者使用UDP协议。它们的构造函数是:
Handler(host, port)
其中host是主机名,port是端口名
7) logging.handlers.SysLogHandler
8) logging.handlers.NTEventLogHandler
9) logging.handlers.SMTPHandler
10) logging.handlers.MemoryHandler
11) logging.handlers.HTTPHandler
最后举个例子吧:
# encoding:utf-8
#import logging
#FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
#logging.basicConfig(format=FORMAT)
#d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
#logger = logging.getLogger('tcpserver')
#logger.warning('Protocol problem: %s', 'connection reset', extra=d)
#FORMAT = '%(asctime)-15s %(message)s'
#logging.basicConfig(filename = "C:\\Users\\june\\Desktop\\1.txt", level = logging.DEBUG, filemode = "a", format=FORMAT)
#logging.debug('this is a message')
#logging.debug('test')
#import logging
#import datetime
#
#curDate = datetime.date.today() - datetime.timedelta(days=0)
#logName = 'C:\\Users\\june\\Desktop\\error_%s.log' %curDate
#
#logging.basicConfig(level=logging.INFO,
# format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
# #datefmt='%a, %d %b %Y %H:%M:%S',
# filename=logName,
# filemode='a')
#
##2013-10-21 03:25:51,509 writeLog.py[line:14] INFO This is info message
##2013-10-21 03:25:51,510 writeLog.py[line:15] WARNING This is warning message
#logging.debug('This is debug message')
#logging.info('This is info message')
#logging.warning('This is warning message')import logging
import logging.config
logging.config.fileConfig("logging.conf")
#create logger
loggerInfo = logging.getLogger("infoLogger")
#"application" code
loggerInfo.debug("debug message")
loggerInfo.info("info message")
loggerInfo.warn("warn message")
loggerInfo.error("error message")
loggerInfo.critical("critical message")
loggerError = logging.getLogger("errorLogger")
loggerError.error("Error: Hello world!")
# 定义logger模块,root是父类,必需存在的,其它的是自定义。
# logging.getLogger(NAME)便相当于向logging模块注册了一种日志打印
# name 中用 . 表示 log 的继承关系
[loggers]
keys=root,infoLogger,errorLogger
# 定义handler
[handlers]
keys=infoHandler,errorHandler
# 定义格式化输出
[formatters]
keys=infoFmt,errorFmt
#--------------------------------------------------
# 实现上面定义的logger模块,必需是[logger_xxxx]这样的形式
#--------------------------------------------------
# [logger_xxxx] logger_模块名称
# level 级别,级别有DEBUG、INFO、WARNING、ERROR、CRITICAL
# handlers 处理类,可以有多个,用逗号分开
# qualname logger名称,应用程序通过 logging.getLogger获取。对于不能获取的名称,则记录到root模块。
# propagate 是否继承父类的log信息,0:否 1:是
[logger_root]
level=INFO
handlers=errorHandler
[logger_errorLogger]
level=ERROR
handlers=errorHandler
propagate=0
qualname=errorLogger
[logger_infoLogger]
level=INFO
handlers=infoHandler
propagate=0
qualname=infoLogger
#--------------------------------------------------
# handler
#--------------------------------------------------
# [handler_xxxx]
# class handler类名
# level 日志级别
# formatter,上面定义的formatter
# args handler初始化函数参数
[handler_infoHandler]
class=StreamHandler
level=INFO
formatter=infoFmt
args=(sys.stdout,)
[handler_errorHandler]
class=logging.handlers.TimedRotatingFileHandler
level=ERROR
formatter=errorFmt
# When computing the next rollover time for the first time (when the handler is created),
# the last modification time of an existing log file, or else the current time,
# is used to compute when the next rotation will occur.
# 这个功能太鸡肋了,是从handler被创建的时间算起,不能按自然时间 rotation 切分,除非程序一直运行,否则这个功能会有问题
# 临时解决方案参考下面的链接:Python 多进程日志记录
# http://blogread.cn/it/article/4175?f=wb2
args=('C:\\Users\\june\\Desktop\\error.log', 'M', 1, 5)
#--------------------------------------------------
# 日志格式
#--------------------------------------------------
# %(asctime)s 年-月-日 时-分-秒,毫秒 2013-04-26 20:10:43,745
# %(filename)s 文件名,不含目录
# %(pathname)s 目录名,完整路径
# %(funcName)s 函数名
# %(levelname)s 级别名
# %(lineno)d 行号
# %(module)s 模块名
# %(message)s 消息体
# %(name)s 日志模块名
# %(process)d 进程id
# %(processName)s 进程名
# %(thread)d 线程id
# %(threadName)s 线程名
[formatter_infoFmt]
format=%(asctime)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter
[formatter_errorFmt]
format=%(asctime)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter
#coding=utf-8
import logging
import datetime
format='%(asctime)s - %(filename)s - [line:%(lineno)d] - %(levelname)s - %(message)s'
curDate = datetime.date.today() - datetime.timedelta(days=0)
infoLogName = r'C:/Users/june/Desktop/info_%s.log' %curDate
errorLogName = r'C:/Users/june/Desktop/error_%s.log' %curDate
formatter = logging.Formatter(format)
infoLogger = logging.getLogger("infoLog")
errorLogger = logging.getLogger("errorLog")
infoLogger.setLevel(logging.INFO)
errorLogger.setLevel(logging.ERROR)
infoHandler = logging.FileHandler(infoLogName, 'a')
infoHandler.setLevel(logging.INFO)
infoHandler.setFormatter(formatter)
errorHandler = logging.FileHandler(errorLogName, 'a')
errorHandler.setLevel(logging.ERROR)
errorHandler.setFormatter(formatter)
testHandler = logging.StreamHandler()
testHandler.setFormatter(formatter)
testHandler.setLevel(logging.ERROR)
infoLogger.addHandler(infoHandler)
infoLogger.addHandler(testHandler)
errorLogger.addHandler(errorHandler)
#infoLogger.debug("debug message")
#infoLogger.info("info message")
#infoLogger.warn("warn message")
# # 下面这行会同时打印在文件和终端上
#infoLogger.error("error message")
#
#errorLogger.error("error message")
#errorLogger.critical("critical message")
Python标准模块logging http://blog.csdn.net/fxjtoday/article/details/6307285
Logging Cookbook http://docs.python.org/2/howto/logging-cookbook.html
REF:
[1] logging — Logging facility for Python
http://docs.python.org/2/library/logging.html
[2] logging.config — Logging configuration
http://docs.python.org/2/library/logging.config.html#module-logging.config
[3] 使用python的logging模块
http://kenby.iteye.com/blog/1162698
http://blog.sina.com.cn/s/blog_51294ca50100gc3x.html
http://www.cnblogs.com/captain_jack/archive/2011/01/21/1941453.html
[4] 使用Python的logging.config
http://www.cppblog.com/jinq0123/archive/2007/09/03/UsingLoggingConfig.html
[5] python之强大的日志模块logging
[6] 每个 Python 程序员都要知道的日志实践
http://python.jobbole.com/81666/
[7] python日志管理:logging
http://wjdadi-gmail-com.iteye.com/blog/1984354
[8] Python Howto 之 logging 模块
http://python.jobbole.com/82221/
[9] 关于 logging 的一些琐事
http://python.jobbole.com/84092/
==================
https://stackoverflow.com/questions/9317250/python-logging-module-custom-loggers
别人写的: 复杂了
def get_logger(name='live.alarm'):
if name in logging.Logger.manager.loggerDict:
return logging.Logger.manager.loggerDict[name]
查看logging的源码:
help(logging)
help相应的module,例如stat位于os module,那么可以help(os),出现如上图的结果,其中FILE就是其源文件位置,当然,如果这个module是c module,那就看不到相应的源码了。
Looking at the source code we can see the following:
root = RootLogger(WARNING)
def getLogger(name=None):
if name:
return Logger.manager.getLogger(name)
else:
return root
That is, a root logger is created by default when the module is imported.