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

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 日志多个服务连接_Django多进程日志文件问题相关推荐

  1. Linux日常运维(rsync通过服务连接,linux日志,screen)

    一.rsync通过服务同步 分为服务端(server1) 和客户端(server2) 服务端(server1): [root@litongyao ~]# vim /etc/rsyncd.conf po ...

  2. 广告业务系统 之 数据中转站 —— “日志中心-实时服务监控”

    文章目录 广告业务系统 之 数据中转站 -- "日志中心-实时服务监控" 日志中心 实时服务监控 -- 前链路日志分析 日志收敛手段 -- "手术开口" 基于 ...

  3. django http 收发_django的日志发往http server

    # https://docs.djangoproject.com/zh-hans/2.1/topics/logging/ LOGGING ={'version': 1,'disable_existin ...

  4. asp.net core使用serilog将日志推送到腾讯云日志服务

    为什么是serilog? Serilog是 .NET 中最著名的结构化日志类库. 基于日志事件log events,而不是日志消息log message. 你可以将日志事件格式化为控制台的可读文本或者 ...

  5. Asp.Net Core 轻松学-利用日志监视进行服务遥测

    原文:Asp.Net Core 轻松学-利用日志监视进行服务遥测 前言     在 Net Core 2.2 中,官方文档表示,对 EventListener 这个日志监视类的内容进行了扩充,同时赋予 ...

  6. tableau无法建立连接_外部服务连接疑难解答 - Tableau

    本主题描述 Tableau 连接到外部分析扩展程序服务时可能会收到的错误. 注意:Tableau 技术支持人员无法协助编写.调整外部脚本或排除其问题. 与任何分析扩展程序通信时可能会发生的错误 错误消 ...

  7. python多进程log日志问题_Python 如何安全地实现实现多进程日志以及日志正常的分割...

    在Python中我们经常需要使用到多进程来提高我们程序性能,但是多进程的编程中经常有各种各样的问题来困扰我们,比如多进程和多线程的公用导致的子进程的卡死,进程间的通信等问题.还有一个问题我们也许不经常 ...

  8. 技术分享|基于图神经网络的微服务系统调用链和日志融合异常检测方法

    基于图神经网络的微服务系统调用链和日志融合异常检测方法 微服务系统运行时环境具有高度的复杂性和动态性,由此带来的各种问题导致微服务系统常常出现各种故障.为了尽早发现故障,快速准确的异常检测方法成为保证 ...

  9. spring cloud 日志_微服务架构开发实战:ElasticStack实现日志集中化

    常见日志集中化的实现方式 有许多现成的可用于实现集中式日志记录的解决方案,它们使用不同的方法.体系结构和技术.理解所需的功能并选择满足需求的正确解决方案非常重要. 日志托运 有一些日志托运组件可以与其 ...

最新文章

  1. python合并word内容相同单元格_python:怎样合并文档中有重复部分的行?
  2. mysql grant show_mysql之基础操作grant、show、repair、log_error等
  3. java内存溢出分析工具:jmap使用实战
  4. js 判断字符串为空的方法
  5. SAP License:SAP创建中国版免费在线公开课程openSAP
  6. spring-页面模板配置
  7. Symbols andSymbol Tables
  8. 【汉字识别】基于matlab SVM汉字识别【含Matlab源码 830期】
  9. matlab画图的参数,matlab画图参数
  10. 【数学建模】数学建模学习4---动态规划(例题+matlab代码实现)
  11. android studio : Could not find org.jetbrains.kotlin:kotlin-stdlib-jre7:1.5.31
  12. 如何将ThinkPad T490拆机加内存条
  13. Git Extension 合并分支
  14. 区块链技术应用场景有哪些?
  15. 酒水知识(六大基酒之白兰地_Brandy)
  16. VT系列一:VT简述
  17. CPU当中的分支预测
  18. (十一)jmeter接口自动化难点系列---设置响应超时时间
  19. PAT A1155 Heap Paths ——三更灯火五更鸡?
  20. bark 自建服务器,自建iOS消息推送服务Bark

热门文章

  1. 字符串算法 —— 两字符串相同的单词
  2. 字面量(literal)与 C 语言复合字面量(compound literals)
  3. 【脑筋急转弯】—— 谁是诚实人?
  4. 仿函数(functors/function objects)原理及使用
  5. python有几种_Python常见的几种算法
  6. 用python画漂亮图-大部分人都不知道-Python竟能画这么漂亮的花,帅呆了
  7. 零基础学python-零基础如何开始学习 Python?看完这篇从小白变大牛!
  8. 学python语言有前途吗-Python语言是什么?学Python语言有前途吗?
  9. 学python有前途吗-2019年转行学Python有还前途吗?如何学习Python?
  10. python表白代码-如何用Python代码向心爱的姑娘花式表白?