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_TOとPRINT_NEWLINE_TOは拡張版print statementで使われるのがわかる。それぞれ出力を行う命令なので、writeが二度呼ばれるものとして扱ってよさそう。
素直にそのままloggingすると、最後の改行までログに残ってしまうので邪魔。ただ、単純に'\n'だけ渡されたときに無視するようにすると、改行だけのやつ(fnlみたいなやつ)は捨てられてしまうので(ログに出すことしか考えていないので、許容できる場面もあると思うけど)簡単に対応しておく。
- 前回の出力が改行じゃないときは無視(PRINT_ITEM_TO->PRINT_NEWLINE_TOで呼ばれる場合)
- それ以外は空文字列を出力