Django多进程日志文件问题

最近使用Django做一个项目。在部署的时候发现日志文件不能滚动(我使用的是RotatingFileHandler),只有一个日志文件。

查看Log发现一个错误消息:PermissionError: [WinError 32] 另一个程序正在使用此文件

因为我有一些进程需要使用Django的模型层来操作数据库。所以再这些单独的进程中引入了Django:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
import django
django.setup()

估计这就是主要原因,在使用Django时,Django本身就会使用setting中的log配置来初始化日志模块。而我正好使用的是基于文件的Handler,所以多个进程启动后,都会按照这个方式来初始化日志模块,导致多个进程都在引用此日志文件。在日志文件滚动时,是需要把当前日志文件重命名为xxx.1或者xxx.2。但是由于其他进程也在使用此文件,所以不能修改文件名。

后来再网上查python的多进程日志文件的滚动问题,发现大家都有类似问题。看来这是python自身的一个常见问题,但是还没有什么标准的解决方法,有的是采用多进程共享queue的方式,多个进程把日志往queue中写,然后一个线程负责把queue中的日志消息往文件中写。但多进程共享queue在linux上和windows上表现还有差异,真是越来越烦。具体讨论请参考这个文章:

https://stackoverflow.com/questions/641420/how-should-i-log-while-using-multiprocessing-in-python/894284

代码如下:

import logging
import multiprocessing
import threading
import time
from logging.handlers import RotatingFileHandler class MultiProcessingLogHandler(logging.Handler):
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
logging.Handler.__init__(self) self._handler = RotatingFileHandler(filename, mode, maxBytes, backupCount, encoding, delay)
self.queue = multiprocessing.Queue(-1) t = threading.Thread(target=self.receive)
t.daemon = True
t.start() def setFormatter(self, fmt):
logging.Handler.setFormatter(self, fmt)
self._handler.setFormatter(fmt) def receive(self):
while True:
try:
record = self.queue.get()
self._handler.emit(record)
except (KeyboardInterrupt, SystemExit):
raise
except EOFError:
break
except:
traceback.print_exc(file=sys.stderr) def send(self, s):
self.queue.put_nowait(s) def _format_record(self, record):
# ensure that exc_info and args
# have been stringified. Removes any chance of
# unpickleable things inside and possibly reduces
# message size sent over the pipe
if record.args:
record.msg = record.msg % record.args
record.args = None
if record.exc_info:
dummy = self.format(record)
record.exc_info = None return record def emit(self, record):
try:
s = self._format_record(record)
self.send(s)
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record) def close(self):
self._handler.close()
logging.Handler.close(self)

然后把这个类写在settings.py中的handler的class中。

我测试的时候还是没有好使,不知道什么原因:( 我宝贵的时间呀!

使用zeromq的方案倒是吸引了我,因为我项目中正好使用zeromq,比较简单可靠。说白了就是把日志发给一个socket,然后socket服务端读取消息,并写入到日志文件中。

这个方案涉及到要自定义一个Handler,这就是查查python如何自定义handler(zeromq好像已经提供了一个PUBHandler: http://pyzmq.readthedocs.io/en/latest/api/zmq.log.handlers.html#zmq.log.handlers.PUBHandler)。

关于如何自定义handler,这里有篇文件讲了:

http://www.cnblogs.com/shizioo/p/python_logging_handler_custom.html#undefined

formatters = {
logging.DEBUG: logging.Formatter("[%(name)s] %(message)s"),
logging.INFO: logging.Formatter("[%(name)s] %(message)s"),
logging.WARN: logging.Formatter("[%(name)s] %(message)s"),
logging.ERROR: logging.Formatter("[%(name)s] %(message)s"),
logging.CRITICAL: logging.Formatter("[%(name)s] %(message)s")
} # This one will be used by publishing processes
class PUBLogger:
def __init__(self, host, port=config.PUBSUB_LOGGER_PORT):
self._logger = logging.getLogger(__name__)
self._logger.setLevel(logging.DEBUG)
self.ctx = zmq.Context()
self.pub = self.ctx.socket(zmq.PUB)
self.pub.connect('tcp://{0}:{1}'.format(socket.gethostbyname(host), port))
self._handler = PUBHandler(self.pub)
self._handler.formatters = formatters
self._logger.addHandler(self._handler) @property
def logger(self):
return self._logger # This one will be used by listener process
class SUBLogger:
def __init__(self, ip, output_dir="", port=config.PUBSUB_LOGGER_PORT):
self.output_dir = output_dir
self._logger = logging.getLogger()
self._logger.setLevel(logging.DEBUG) self.ctx = zmq.Context()
self._sub = self.ctx.socket(zmq.SUB)
self._sub.bind('tcp://*:{1}'.format(ip, port))
self._sub.setsockopt(zmq.SUBSCRIBE, "") handler = handlers.RotatingFileHandler(os.path.join(output_dir, "client_debug.log"), "w", 100 * 1024 * 1024, 10)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s;%(levelname)s - %(message)s")
handler.setFormatter(formatter)
self._logger.addHandler(handler) @property
def sub(self):
return self._sub @property
def logger(self):
return self._logger # And that's the way we actually run things: # Listener process will forever listen on SUB socket for incoming messages
def run_sub_logger(ip, event):
sub_logger = SUBLogger(ip)
while not event.is_set():
try:
topic, message = sub_logger.sub.recv_multipart(flags=zmq.NOBLOCK)
log_msg = getattr(logging, topic.lower())
log_msg(message)
except zmq.ZMQError as zmq_error:
if zmq_error.errno == zmq.EAGAIN:
pass # Publisher processes loggers should be initialized as follows: class Publisher:
def __init__(self, stop_event, proc_id):
self.stop_event = stop_event
self.proc_id = proc_id
self._logger = pub_logger.PUBLogger('127.0.0.1').logger def run(self):
self._logger.info("{0} - Sending message".format(proc_id)) def run_worker(event, proc_id):
worker = Publisher(event, proc_id)
worker.run() # Starting subscriber process so we won't loose publisher's messages
sub_logger_process = Process(target=run_sub_logger,
args=('127.0.0.1'), stop_event,))
sub_logger_process.start() #Starting publisher processes
for i in range(MAX_WORKERS_PER_CLIENT):
processes.append(Process(target=run_worker,
args=(stop_event, i,)))
for p in processes:
p.start()

上面是别人写的关于zeromq方式多进程日志的解决方法。

上面看起来有些复杂,最后使用的是zeromq自己提供的PUBHandler。同时自己再写一个接收端,并使用Python自身的logging模块往文件中记录。

PUBHandler使用的是PUB方式:

self.ctx = context or zmq.Context()
self.socket = self.ctx.socket(zmq.PUB)
self.socket.bind(interface_or_socket)

其中inproc://log方式不行,因为这个是线程间通信,对于并不是进程间通信(参考:http://www.cnblogs.com/fengbohello/p/4328772.html)。所以想使用进程间通信需要用ipc://address。但是看到此方式只在UNIX系统上完全实现了,心又凉一半,你让我再windows上怎么测试。看来只能换成tcp方式了。

具体实现请看这篇文章:http://seewind.blog.51cto.com/249547/288343

使用PUB是不行的,因为多个进程调用此处会导致Address in use的错误。哎~~~,继续调查,最后决定使用PUSH和PULL模式。

最后的代码如下:

# -*- coding:utf-8 -*-
import logging import zmq
from zmq.utils.strtypes import cast_bytes default_formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(pathname)s[line:%(lineno)d] %(message)s') class PUSHHandler(logging.Handler):
"""
改造了zeromq的PUBHandler,而使用PUSH(client), PULL(server)模式
"""
socket = None formatters = {
logging.DEBUG: default_formatter,
logging.INFO: default_formatter,
logging.WARN: default_formatter,
logging.ERROR: default_formatter,
logging.CRITICAL: default_formatter
} def __init__(self, interface_or_socket, context=None):
logging.Handler.__init__(self)
if isinstance(interface_or_socket, zmq.Socket):
self.socket = interface_or_socket
self.ctx = self.socket.context
else:
self.ctx = context or zmq.Context()
self.socket = self.ctx.socket(zmq.PUSH)
self.socket.connect(interface_or_socket) def format(self, record):
"""Format a record."""
return self.formatters[record.levelno].format(record) def emit(self, record):
"""Emit a log message on my socket."""
try:
bmsg = cast_bytes(self.format(record))
except Exception:
self.handleError(record)
return self.socket.send(bmsg)

在django的settings.py中使用此handler:

    'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'detail',
},
'file': {
'level': 'INFO',
'class': 'common.zeromq_loghandler.PUSHHandler',
'interface_or_socket': 'tcp://localhost:%s' % LOG_LISTEN_PORT,
},
},

最后log的服务端代码如下:

# -*- coding:utf-8 -*-
import logging import zmq from common.message_log import get_logger
from myproject.settings import LOG_BASE_DIR, LOG_LISTEN_PORT simple_formatter = logging.Formatter('%(message)s') logger = get_logger(LOG_BASE_DIR + '/django.log', formatter=simple_formatter) class LoggingServer(object):
"""
日志接收服务端 (为了解决多进程访问相同的日志文件问题)
""" def run(self):
logger.info("start LoggingServer")
context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.bind("tcp://*:%s" % LOG_LISTEN_PORT)
logger.info("Listen on %s" % LOG_LISTEN_PORT) while True:
try:
msg = socket.recv_string()
logger.info(msg)
except Exception as e:
logger.error("Error logging server: %s" % e) def main():
try:
server = LoggingServer()
server.run()
except Exception as e:
logger.error('Error: %s' % e) if __name__ == "__main__":
main()

就这么简单的一个日志问题,花费了我一天时间,非常不开心

Django多进程日志文件问题的更多相关文章

  1. 解决log4net多进程日志文件被占用

    <log4net debug="true"> <appender name="RollingLogFileAppender" type=&qu ...

  2. Django多进程滚动日志的问题

    使用RotatingFileHandler控制日志文件的大小 # settings.py LOGGING = { ... 'handlers': { ... 'file': { 'level': 'I ...

  3. Django实现web端tailf日志文件

    这是Django Channels系列文章的第二篇,以web端实现tailf的案例讲解Channels的具体使用以及跟Celery的结合 通过上一篇<Django使用Channels实现WebS ...

  4. Django 的 logging日志文件配置

    在Django的settings配置文件里配置以下信息: import os BASE_LOG_DIR = os.path.join(BASE_DIR , "log") # log ...

  5. 【python小随笔】Django+错误日志(配置Django报错文件指定位置)

    1:  自定义日志文件.py----------几个文件需要创建日志,就需要重新定义几份 # 定义一个日志文件 创建一个操作日志对象logger file_1 = logging.FileHandle ...

  6. Django(八)模型:Mysql8.0日志文件配置开启/关闭、查询

    Mysql日志文件开启.配置.查看 mysql.log是mysql的日志文件,里面记录的对MySQL数据库的操作记录.默认情况下mysql的日志文件没有产生,需要修改mysql的配置文件,步骤如下: ...

  7. python的日志模块:logging;django的日志系统;django日志输出时间修改

    Django的log,主要是复用Python标准库中的logging模块,在settings.py中进行配置 源代码 1.__init__.py包含以下类: StreamHandler Formatt ...

  8. log4net 既要按日期分割日志文件,又要按文件大小分割。

    <appender name="DebugAppender" type="log4net.Appender.RollingFileAppender"> ...

  9. logging日志文件配置

    Django配置如下 简洁版: LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console' ...

随机推荐

  1. memcached整理の缓存问题

    声明:博客来源http://www.cnblogs.com/AloneSword/p/3931905.html,谢谢他的分享! 缓存穿透与缓存雪崩 缓存系统不得不考虑的另一个问题是缓存穿透与失效时的雪 ...

  2. 《JavaScript权威指南》(第6版)翻译错误集 更新中。。。

    §6.2.2  P126 原文:If o inherits x,and that property is an accessor property with a setter method. 原译:如 ...

  3. Time 模块中asctime()、time()、localtime()、ctime()、gmtime()的用法

    Time 模块包含了以下内置函数,既有时间处理的,也有转换时间格式的: time.asctime([tupletime]) 接受时间元组并返回一个可读的形式“Tue Dec 11 18:07:14 2 ...

  4. 求帮忙解决封装jquery图片滚动问题

    今天用jquery封装了点击图片滚动,但是发现在屏幕自适应时,图片停在的位置会随着屏幕大小而错位(我引入了pocketgrid.css响应式文件,但没办法去那边修改onsize事件...),求大神.. ...

  5. NuGet文件下载与应用

    nuget是一款.net下强大的包管理开发工具,Visual Studio 2013和Visual Studio 2015都缺省支持Nuget.在线开发能享受到Nuget的便利,但是如果是离线开发,还 ...

  6. webapi发布常见错误及解决方案

    webapi发布常见错误及解决方案 错误一: 错误:404 (Not Found) 解决方案: 在  <system.webServer>节点中添加如下模块: <modules ru ...

  7. linux上 查看mysql的binglog日志

    查看mysqlbinglog日志 mysqlbinlog --no-defaults --database=数据库名称 --start-datetime= > /mysql.txt 备注: -- ...

  8. 从哈希结构去理解PHP数组

    php的数组实际上就是hash_table,无论是 数字索引数组array(1, 2, 3) 还是关联数组array(1 => 2, 2=> 4)等等. 一,这里的hash_table有几 ...

  9. [ActionScript 3.0] 简单倒计时

    import flash.utils.Timer; import flash.events.TimerEvent; import flash.text.TextField; var text:Text ...

  10. anaconda安装出现failed to create anacoda menue

    1.卸载Anaconda后重新安装Anaconda出现各种问题,粗暴解决方式:直接将安装目录放在C盘主路径下,完美解决. 2.然后无选择忽略,忽略,忽略,提示安装成功,依旧没有 菜单 进入 cmd,找 ...