🎁

Box Python SDKでのAPI呼び出し時の標準エラーログ抑制

2024/09/02に公開

TL;DR

logging.getLogger(boxsdk.network.default_network.__name__).setLevel(logging.ERROR)

背景

例えば既に存在するフォルダを作成しようとすると409エラーとなるが、何もしないと強制的に標準エラーにエラーログが出力されてしまう。

example.py
import boxsdk.network.default_network
from boxsdk import BoxAPIException, Client, OAuth2


def main() -> None:
    try:
        client: Client = Client(
            OAuth2(
                client_id="***",
                client_secret="***",
                access_token="***",
                refresh_token="***",
            )
        )
        folder = client.folder("***").create_subfolder("***")
        print("created", folder)
    except BoxAPIException as e:
        print("error", e.context_info)


if __name__ == "__main__":
    main()
"POST https://api.box.com/2.0/folders" 409 303
{'date': '***, ** *** **** **:**:** GMT', 'content-type': 'application/json', 'x-envoy-upstream-service-time': '122', 'box-request-id': '***', 'cache-control': 'no-cache, no-store', 'strict-transport-security': 'max-age=31536000', 'via': '1.1 google', 'Alt-Svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000', 'Transfer-Encoding': 'chunked'}
{'code': '---_use',
 'context_info': {'conflicts': [{'etag': '***',
                                 'id': '***',
                                 'name': '***',
                                 'sequence_id': '***',
                                 'type': 'folder'}]},
 'help_url': 'http://developers.box.com/docs/#errors',
 'message': 'Item with the same name already exists',
 'request_id': '****',
 'status': 409,
 'type': 'error'}

error {'conflicts': [{'type': 'folder', 'id': '***', 'sequence_id': '***', 'etag': '***', 'name': '***'}]}

自前でエラーログを出したい場合に重複して邪魔となり、かなり使いづらい。
確認したところ、boxsdk.network.default_network.DefaultNetworkResponse.log()においてloggingモジュールで出力していて、レスポンスがokでないときにLoggerのログレベルを見て出力するか判定しているようである。

boxsdk/network/default_network.py
class DefaultNetworkResponse(NetworkResponse):
・・・
        if self.ok:
            logger_method, logger_level, response_format = self._logger.info, logging.INFO, self.SUCCESSFUL_RESPONSE_FORMAT
        else:
            logger_method, logger_level, response_format = self._logger.warning, logging.WARNING, self.ERROR_RESPONSE_FORMAT

        if not self._logger.isEnabledFor(logger_level):
            return
・・・
        logger_method(
            response_format,
            {
                'method': self.request_response.request.method,
                'url': self.request_response.request.url,
                'status_code': self.status_code,
                'content_length': content_length,
                'headers': pformat(self.headers),
                'content': content,
            },
        )

何もしていない場合、logging.WARNINGは有効となっているようなのでlogger_method()が呼ばれてしまい標準エラーが出力される。

対策

APIを呼び出すより前にDefaultNetworkResponseで用いるLoggerのログレベルを変更し、logger_method()が呼ばれないようにする。

コンストラクタでメンバに設定しているLoggergetLogger(__name__)で、この__name__の値はpython:boxsdk.network.default_networkである。

boxsdk/network/default_network.py
class DefaultNetworkResponse(NetworkResponse):
・・・
    def __init__(self, request_response: 'Response', access_token_used: str, log_response_content: bool = True):
        self._logger = getLogger(__name__)

以下のようにログレベルを先回りして書き換える。

example.py
from logging import ERROR, Logger, getLogger

import boxsdk.network.default_network
from boxsdk import BoxAPIException, Client, OAuth2


def main() -> None:
    try:
        logger: Logger = getLogger(boxsdk.network.default_network.__name__)
        logger.setLevel(ERROR)

        client: Client = Client(
            OAuth2(
                client_id="***",
                client_secret="***",
                access_token="***",
                refresh_token="***",
            )
        )
        folder = client.folder("***").create_subfolder("***")
        print("created", folder)
    except BoxAPIException as e:
        print("error", e.context_info)


if __name__ == "__main__":
    main()
error {'conflicts': [{'type': 'folder', 'id': '***', 'sequence_id': '***', 'etag': '***', 'name': '***'}]}

参考

Discussion