在生产环境中,log一般按照时间进行切分,如在23:59:59切分一天的日志,以便进行分析。在python中常用内建的logging模块实现
logger = logging.getLogger() logger.setLevel(logging.DEBUG) log_file = 'tornadolog.log' timelog = timelog = logging.handlers.TimedRotatingFileHandler(log_file, 'midnight', 1, 0) logger.addHandler(timelog) logger.info('Hello log')
但是,tornado的多进程部署方式会发生错误,原因是单个的日志文件作为进程间的共享资源,当其中一个进程进行日志切分的时候,实际上是将原来的日志文件改名,然后新建一个日志文件,文件操作描述符fd发生了改变,其它的进程不知道这个操作,再次写入日志的时候因为找不到新的文件描述符发生异常。
解决方法也很简单,我们可以是每个tornado进程都有一个自己的日志文件,通过进程的task_id进行区分,避免进程间共享资源的操作。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2012 Ethan Zhang<http://github.com/Ethan-Zhang> # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import signal import os import logging import logging.handlers import tornado import tornado.netutil import tornado.process from tornado.ioloop import IOLoop from tornado.httpserver import HTTPServer def handle_request(request): message = "You requested %s\n" % request.uri request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % ( len(message), message)) request.finish() def kill_server(sig, frame): IOLoop.instance().stop() def main(): signal.signal(signal.SIGPIPE, signal.SIG_IGN); signal.signal(signal.SIGINT, kill_server) signal.signal(signal.SIGQUIT, kill_server) signal.signal(signal.SIGTERM, kill_server) signal.signal(signal.SIGHUP, kill_server) sockets = tornado.netutil.bind_sockets(9204) task_id = tornado.process.fork_processes(2) #task_id为fork_processes返回的子进程编号 logger = logging.getLogger() logger.setLevel(logging.DEBUG) log_file = 'tornadolog.log.%d' % (task_id) #以子进程编号命名自己的log文件名 timelog = timelog = logging.handlers.TimedRotatingFileHandler(log_file, 'midnight', 1, 0) logger.addHandler(timelog) server = HTTPServer(handle_request) server.add_sockets(sockets) logger.info('Server Starting...') IOLoop.instance().start() server.stop() IOLoop.instance().stop() if __name__ == '__main__': main()
代码位置https://gist.github.com/Ethan-Zhang/7524628
-----------2013-11-25新增----------------
其实我们分析Pythonlogging模块的源码就能发现问题
def doRollover(self): """ do a rollover; in this case, a date/time stamp is appended to the filename when the rollover happens. However, you want the file to be named for the start of the interval, not the current time. If there is a backup count, then we have to get a list of matching filenames, sort them and remove the one with the oldest suffix. """ if self.stream: self.stream.close() # 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) #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
第18-31行,每次TimedRotatingHandler做切分操作时,所做的步骤如下:
如现在的文件为mownfish.log, 切分后的文件名为mownfish.log.2013-11-24
1. 判断切分后重命名的文件mownfish.log.2013-11-24是否存在,如果存在就删除
2. 将目前的日志文件mownfish.log重命名为mownfish.log.2013-11-24
3. 以“w”模式打开一个新文件mownfish.log。
如果是多进程的应用程序,如tornado,将会出现异常。比如某个进程刚刚完成了第二步,将mownfish.log重命名为mownfish.log.2013-11-24。另外一个进程刚刚开始执行第二步,此时进程1还没有进行到第3步,即创建一个新的mownfish.log,所以进程2找不到mownfish.log,程序抛出异常。另外每次以W模式打开,也会使新生成的mownfish.log文件内容被重复的清除多次。
解决方案为创建类,继承自TimedRotatingHandler,重写其切分方法doRollover()方法。流程修改为:
1. 判断切分后重命名的文件mownfish.log.2013-11-24是否存在,如不存在将日志文件mownfish.log重命名为mownfish.log.2013-11-24
2. 以’a‘模式打开文件mownfish.log
这样当进程1重命名之后,进程2就不会重复执行重命名的动作了。
def doRollover(self): """ do a rollover; in this case, a date/time stamp is appended to the filename when the rollover happens. However, you want the file to be named for the start of the interval, not the current time. If there is a backup count, then we have to get a list of matching filenames, sort them and remove the one with the oldest suffix. """ if self.stream: self.stream.close() # 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) if not os.path.exists(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) #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
相关推荐
本文主要研究的是tornado 多进程模式的相关内容,具体如下。 官方文档的helloworld实例中的启动方法: if __name__ == __main__: application.listen(8888) # listen is a shortcut for bind and start ,这点看...
Tornado通过这样的方式实现了多进程模型,每个子进程都有自己的IOLoop,能够独立处理客户端的请求。这样,即使一个子进程在处理某个请求时阻塞,其他子进程仍然可以继续接收和处理新的请求,提高了系统的并行处理...
多线程多进程对比和应用 多线程多进程是两个不同的概念,都是为了提高程序的执行效率和response速度,但它们之间有着很大的区别。 进程 进程是具有一定的独立功能的程序,对某一个数据集合上的一次运行活动,是...
Tornado支持创建多个子进程来分别处理请求,通过子进程的方式,每个进程可以运行独立的Python解释器,从而绕过GIL。 在Tornado中,我们可以通过`multiprocessing`模块创建多个进程。可以使用`tornado.process.fork_...
tornado2.2 安装lsn 解决方案, 不错的方法安装破解版的必备
sed -i ‘s/StreamHandler()/StreamHandler(sys.__stdout__)/’ /opt/python/python3/lib/python3.6/site-packages/tornado...以上这篇python tornado修改log输出方式就是小编分享给大家的全部内容了,希望能给大家一个
得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个理想框架。 二、多进程启动方法 正常启动方法: 复制代码 代码如下: server = HTTPServer(app) ...
`tornado.locale` 模块提供了一套完整的国际化解决方案,支持多语言环境下的应用开发。开发者可以轻松地为不同的语言和地区定制内容,提高用户体验。 #### 异步网络编程 **3.1 tornado.gen — 简化异步代码** `...
Python基于Tornado实现的系统核心调度能够有效地支持分布式扩展,这是一种高效、轻量级的解决方案,尤其适合处理大量并发连接。Tornado是一个Python Web框架和异步网络库,由FriendFeed团队开发,后来被Facebook收购...
10. 多进程和并发控制:tornado.process模块提供了多进程工具,而tornado.concurrent、tornado.locks、tornado.queues等模块支持线程和futures并行工作、同步事务和协程队列。 11. 第三方认证:tornado.auth模块提供...
在本文中,我们将详细探讨如何在Windows 7环境下安装Tornado,并解决可能遇到的问题。 首先,你需要下载适用于Windows 7的Tornado安装包,通常这可能是一个.iso镜像文件或.exe可执行文件。确保你从官方渠道或者可信...
此外,Tornado还提供了`tornado.process`和`tornado.autoreload`等模块,支持多进程和自动重载代码,方便开发和调试。 ### 7. 安全性 Tornado提供了基本的安全特性,如CSRF(跨站请求伪造)防护和XSS(跨站脚本)...
Tornado还提供了处理静态文件和实现文件缓存的功能,以及本地化支持,这使得Tornado能够轻松地创建支持多语言的应用程序。Tornado的UI模块允许开发者通过HTML、JavaScript和CSS来创建用户界面。 非阻塞和异步请求是...
Tornado通常在一个进程内只运行一个线程,这表明它采用了一种不同的异步编程方法。Tornado的官方指南中提供了关于如何使用其异步特性的更多信息,推荐开发者直接使用Tornado提供的接口(如tornado.web)来编写应用...
在 Python 的 Web 开发领域,Tornado 提供了一种轻量级且高效的解决方案,尤其适用于处理大量并发连接,如实时 Web 服务、长轮询和 WebSockets。 **一、Tornado 框架的核心组件** 1. **HTTP Server**: Tornado ...
Tornado 的设计初衷是解决 C10K 问题,即在一个单一进程内高效地处理成千上万个并发连接,这在传统的基于线程的 Web 服务器(如 Apache)中是非常困难的。 #### 二、Tornado 的优势与应用场景 ##### 2.1 高性能与...