Archive for 十二月, 2008

在Django程序中如何用好logging模块

logging是Python 2.3起自带的标准模块,可以用来从运行状态的程序中记录日志。logging模块的功能非常强大,可以非常灵活的向各种预定或者自定的目标输出日志。而利用标准的logging模块,Django程序就可以轻松实现运行环境下的日志输出,这对于开发以及部署环境下程序运行具体情况的监控和调试都是不可或缺的,所以我在这里总结一下自己的一些经验。

Django程序使用logging输出的基本设置

要让Django程序正确得利用logging模块输出日志,首先需要在settings.py中配置好logging参数:

logging.basicConfig(
  level = logging.DEBUG, #设定DEBUG级别输出
  format = '%(asctime)s %(levelname)s %(module)s.%(funcName)s Line:%(lineno)d %(message)s',
)

logging.basicConfig是logging模块提供的简便配置logging参数的方法。经过以上的配置,在Django程序中只需要通过logging.debug,logging.info等方法就可以输出日志了。logging.DEBUG及以上级别的日志都会直接输出到django运行时当前命令窗口,而在生产环境下,只需要相应的提高logging输出级别就可以控制日志输出的内容,避免输出过多日志内容。(关于logging级别和logging的基本知识请参考pydoc) 在本地调试使用manage.py runserver的时候,logging内容就会直接出现在console里。

输出日志到文件

以上的基本设置只能让日志直接输出到命令行窗口。需要把日志输出到文件保存的话最简便的方法是这样

logging.basicConfig(
  level = logging.DEBUG, #设定DEBUG级别输出
  format = '%(asctime)s %(levelname)s %(module)s.%(funcName)s Line:%(lineno)d %(message)s',
  filename = '/path/to/logfile/filelog.log',
)

在logging.basicConfig方法中,只要指定了filename,那么日志就会直接输出到指定的文件了。

按日期循环保存日志文件

在生产环境下,不仅需要把日志写到文件,通常还需要把日志文件按日期分割保存。这样的任务用logging模块也很容易做到。在生产环境的settings.py里使用如下设置:

root = logging.getLogger()
if len(root.handlers) == 0: #避免重复设置handler,否则日志内容可能被重复输出
  level = logging.INFO
  filename = '/path/to/logfile/filelog.log'
  format = '%(asctime)s %(levelname)s %(module)s.%(funcName)s Line:%(lineno)d %(message)s'
  hdlr = TimedRotatingFileHandler(filename, "midnight", 1, 5)
  fmt = Formatter(format)
  hdlr.setFormatter(fmt)
  root.addHandler(hdlr)
root.setLevel(level)

在这里使用了logging模块预定义的TimedRotatingFileHandler类,在每天半夜滚动日志文件,而最多保留5个以往的日志文件。由于需要指定特殊的Handler,所以这里不能使用logging.basicConfig的简便方法。

使用logging模块的更多好处

用好日志功能可以对开发和调试起到很多帮助作用。使用了logging模块可以通过日志级别非常方便的控制输出,不需要增减任何程序代码,只需要在settings.py中更改logging级别一行代码就行了。所以在开发过程中,可以尽量多用logging.debug输出对调试有帮助的内容。而在生产环境下,通过日志也可以方便的分析真实运行环境下的一些问题,便于调试修改bug。

如果用在Google App Engine,不需要指定输出文件,程序输出的日志都会直接保存在App Engine运行日志中,还可以通过正则表达式来搜索以往日志。

另外,通过Django debug toolbar,可以方便的直接在调试环境下直接在每个页面上查看输出的日志,非常好用。

所以,如果你在写Django程序,就不要再用原始的print了,用好logging可以事半功倍。

如何在Django+Nginx fcgi方式下不间断服务地部署新代码

极品座驾“上线以来,用户访问量一直运行在高位,上周末创下了单日pv 139万的记录。在这样的情况下,保持服务不间断是最重要的事情。目前我们支撑这样访问量的架构,是使用Nginx作为web服务前端,用fcgi方式运行django的python进程,再和前端Nginx连接起来。fcgi模式下,python进程把字节码装入内存,来提供最佳的执行速度。但是这样带来的问题就是更新了python代码以后,服务器没法动态装入新代码,而是需要杀掉现有fcgi进程然后spawn新进程才能达到更新代码的目的。虽然重启django fcgi进程的速度很快,但是不可避免的会导致用户服务的中断,在访问量大的情况下更是很危险的方式。为了达到不中断服务的目的,我们采用了这样的方法:

  1. 更新python代码
  2. 在另一个端口spawn一组新的fcgi进程
  3. 更改Nginx配置,把proxy_pass转发端口指向新fcgi进程的端口
  4. 动态重载Nginx配置
  5. 过一段时间等到原fcgi端口不再有未完成的用户请求,再把原端口上的进程全部杀掉

由于Nginx可以在运行状态下不间断的重载配置改动,所以在重载配置以后所有的访问请求都被转发到新的fcgi端口了。而在用户操作频繁的时候,访问请求从原fcgi端口被转到新端口也只需要很短的时间(可以借助netstat命令来观察)。这样一来,就可以实现在不影响用户操作的情况下不间断的切换到新代码了。