2009年6月3日水曜日

print >>sys.stderrによる出力をloggingで出したい

sys.stderrにメッセージを出力しているモジュールがあって、それを書き換えずに何とかしたいので考えてみた。

とりあえずうまくいったけど、いいのかわからない。
(追記
すっかり忘れてたけど、sys.stderrをほかの変数で参照してたらだめだった。当たり前なんだけど…
別モジュールで、from sys import stderrなら
モジュール名.stderrを書き換えればOK)

import sys
import logging
from StringIO import StringIO

class redir(object):
  def __init__(self, f):
    self.f = f
    self.eol = True
  def write(self, buf):
    if buf == '\n':
      if self.eol:
        self.f('')
      else:
        self.eol = True
    else:
      self.eol = False
      self.f(buf)

print >>sys.stderr, "hoge"
logging.basicConfig(filename='hoge.log', level=logging.DEBUG)
sys.stderr = redir(logging.error)
print >>sys.stderr, "hoge1"
print >>sys.stderr
print >>sys.stderr, "hoge2"

これで、最初の出力(hoge)はstderrに、以降の出力(hoge1,改行のみ,hoge2)はhoge.logに出力される。


調べ方メモ
6.6. The print statementを見て、出力先のオブジェクトにwriteが必要なことと、(必要なときは)最後に'\n'がくることはわかった。
writeに渡されるバッファに最後の改行が含まれているのか、改行だけ別に呼ばれるのか気になるので調べてみた。

適当な関数を作って、disで調べた。

>>> import dis
>>> def f():
...  print >>None, None,
...
>>> def fln():
...  print >>None, None
...
>>> def fnl():

...  print >>None

...

>>> dis.dis(f)

  2           0 LOAD_CONST               0 (None)

              3 DUP_TOP

              4 LOAD_CONST               0 (None)

              7 ROT_TWO

              8 PRINT_ITEM_TO

              9 POP_TOP

             10 LOAD_CONST               0 (None)

             13 RETURN_VALUE

>>> dis.dis(fln)
  2           0 LOAD_CONST               0 (None)
              3 DUP_TOP
              4 LOAD_CONST               0 (None)
              7 ROT_TWO
              8 PRINT_ITEM_TO
              9 PRINT_NEWLINE_TO
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
>>> dis(fnl)
  2           0 LOAD_CONST               0 (None)
              3 PRINT_NEWLINE_TO
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE
>>>

たぶん、PRINT_ITEM_TOがデータの出力で、PRINT_NEWLINE_TOは改行であろうことはわかる。
改行付きのprintがPRINT_ITEM_TOと、PRINT_NEWLINE_TOと二つに分かれているので、2回に分けてwriteが呼ばれそうなこともわかる。
ともかく、PRINT_ITEM_TOとPRINT_NEWLINE_TOは間違いなくキーワードなので調べると、PRINT_ITEM_TOPRINT_NEWLINE_TOは拡張版print statementで使われるのがわかる。それぞれ出力を行う命令なので、writeが二度呼ばれるものとして扱ってよさそう。

素直にそのままloggingすると、最後の改行までログに残ってしまうので邪魔。ただ、単純に'\n'だけ渡されたときに無視するようにすると、改行だけのやつ(fnlみたいなやつ)は捨てられてしまうので(ログに出すことしか考えていないので、許容できる場面もあると思うけど)簡単に対応しておく。

  • 前回の出力が改行じゃないときは無視(PRINT_ITEM_TO->PRINT_NEWLINE_TOで呼ばれる場合)
  • それ以外は空文字列を出力



0 件のコメント:

コメントを投稿