今の案件ではtornadoを使ってるですが、リクエスト単位でまとまったログを出したいなと思って、色々調べてみました。
(と言っても数時間かけてtornadoのソースとにらめっこして何となくできたコードを元に@methaneさんに色々教えてもらった。python知らないのにtornado.stack_contextはむちゃだったw)
ポイントとしてはこんな感じ。
- tornado.stack_context.StackContext()とwithを使ってtornadoにコンテキストの管理をやってもらう
- StackContext()に渡すコンテキストファクトリはリクエスト単位で固定のコンテキストを返す関数を渡す
- 自前のコンテキストの__enter__ではグローバル変数に自身を設定する
- グローバル変数に設定することでリクエスト処理中のどこからでもコンテキストにアクセス可能
- グローバル変数使えるのはユーザが実装したリクエスト処理部分はシングルスレッドで実行されるから
- もしtornado自体をマルチスレッドで動かすなら、グローバル変数じゃなくてスレッドローカルな変数にすること
- まああまり無いと思うけど・・・
- グローバル変数に設定することでリクエスト処理中のどこからでもコンテキストにアクセス可能
# -*- coding: utf-8 -*- import tornado.ioloop import tornado.web import tornado.httpclient import tornado.stack_context global context context = None class MainHandler(tornado.web.RequestHandler): def __init__(self, application, request, **kwargs): super(MainHandler, self).__init__(application, request, **kwargs) self._context = Context() def get(self): #非同期処理の場合、コールバックが呼び出されるときにtornadoの機能によってコンテキストが復元される。 #それを利用して、リクエストを受けた時のコンテキストをコールバック実行時のコンテキストと設定できる。 #tornadoにコンテキストの管理を行わせるにはwith文に指定するStackContextにコンテキストのファクトリ関数を渡す。 #通常、ファクトリ関数は毎回コンテキストを生成するようなもの(例えばクラス自身)を渡すのだが、 #リクエスト時のコンテキストをコールバック時にも使いたいため、常に固定のコンテキストを返す関数をファクトリ関数として渡す。 #http://www.tornadoweb.org/documentation/stack_context.html#tornado.stack_context.StackContext with tornado.stack_context.StackContext(lambda: self._context): context.add_log(self.request.uri) if self.request.uri == "/async": self.async() else: self.sync() @tornado.web.asynchronous def async(self): http_client = tornado.httpclient.AsyncHTTPClient() http_client.fetch("http://example.com/sleep", self.callback) #sleepはレスポンスに数秒かかるダミーのAPI def callback(self, response): context.add_log("this is async process") print context.get_log() #/async, this is async process, self.write("async ok\n") self.finish() def sync(self): context.add_log("this is sync process") print context.get_log() #/sync, this is sync process, self.write("sync ok\n") #リクエスト実行時のコンテキストとして使用するクラス。 #tornado.stack_context.StackContext()とwith文で使用されることを想定している。 #__enter__でグローバル変数のcontextに自身を代入しているのは #各処理のどこからでもコンテキストにアクセスできるようにするため。 #グローバル変数を使用するのはtornadoのリクスと処理がシングルスレッドで実行されることに依存している。 #もしtornado自身を複数スレッドで動かす場合はグローバル変数の代わりにスレッドローカルな変数を使用する必要がある。 #このコンテキストには簡易なロギング機能を持たせてあるので、 #各リクエスト単位でログを集めることができる。(リクエスト単位でContextが独立しているため) #つまり非同期処理をおこなってもログが混ざり合うことはない。 class Context: def __init__(self): self._log = "" def __enter__(self): global context self._old_context = context context = self def __exit__(self, type, value, traceback): global context context = self._old_context def add_log(self, log): self._log += log + ", " def get_log(self): return self._log application = tornado.web.Application([ (r"/async", MainHandler), (r"/sync", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
何か間違えてたら教えて下さい。
ではでは。