はじめに
七尾百合子さん、お誕生日 145日目 おめでとうございます! nikkieです。
あのGoogleが作っているAgent Development Kit (ADK)でもまた、現実世界のロギングに苦しめられています...
目次
- はじめに
- 目次
- ドキュメント「Logging in the Agent Development Kit (ADK)」
- DEBUGレベルでロギングしてみる
- 伸びしろ:ADKはルートロガーを設定する実装をしている
- Workaround:google_adkロガーのログだけフィルタする
- 終わりに
ドキュメント「Logging in the Agent Development Kit (ADK)」
Observabilityのセクションの1ページ
ADKは標準ライブラリのloggingを使ってロギングしています1。
ロガーの命名ではgoogle_adk.
というプレフィックスを付けています2。
デバッグ目的にはログレベル DEBUG を指定することになります3。
DEBUGレベルでロギングされるものは4
ADKのCLIを使うとき、DEBUGレベルでのロギングは以下のように指定できます5
% adk web --log_level DEBUG % # または % adk web -v
--log_level DEBUG
のショートカットが-v
(--verbose
)です。
adk web
だけでなく、adk api_server
についても当てはまります。
DEBUGレベルでロギングしてみる
サンプルの YouTube Shorts エージェントを動かします。
% uvx --from google-adk adk web --log_level DEBUG
「write me a script on how to build AI agents」
LLM RequestやLLM Responseが見えて、どのように動いているかを知るのにとても助かります!
2025-08-09 13:06:45,169 - DEBUG - google_llm.py:117 - LLM Request: ----------------------------------------------------------- System Instruction: You are the Shorts Content Orchestrator. <https://github.com/google/adk-docs/blob/main/examples/python/agent-samples/youtube-shorts-assistant/shorts_agent_instruction.txt の内容が続く> You are an agent. Your internal name is "youtube_shorts_agent". The description about you is "You are an agent that can write scripts, visuals and format youtube short videos. You have subagents that can do this" ----------------------------------------------------------- Contents: {"parts":[{"text":"write me a script on how to build AI agents"}],"role":"user"} <ここまでの履歴 略> ----------------------------------------------------------- Functions: ShortsScriptwriter: {'request': {'type': <Type.STRING: 'STRING'>}} ShortsVisualizer: {'request': {'type': <Type.STRING: 'STRING'>}} ConceptFormatter: {'request': {'type': <Type.STRING: 'STRING'>}} -----------------------------------------------------------
2025-08-09 13:06:53,241 - DEBUG - google_llm.py:204 - LLM Response: ----------------------------------------------------------- Text: Of course! Here is a complete script and visual plan for a YouTube Short on how to build an AI agent. <省略> ----------------------------------------------------------- Function calls: ----------------------------------------------------------- Raw response: <省略> -----------------------------------------------------------
その一方で、LLM RequestでもLLM Responseでもない、関係ないログも出力されてノイジーです。
2025-08-09 13:06:45,169 - INFO - models.py:7804 - AFC is enabled with max remote calls: 10. 2025-08-09 13:06:45,192 - DEBUG - _trace.py:87 - start_tls.started ssl_context=<ssl.SSLContext object at 0x12acbccb0> server_hostname='generativelanguage.googleapis.com' timeout=None 2025-08-09 13:06:45,247 - DEBUG - _trace.py:87 - start_tls.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x12aea6a50> 2025-08-09 13:06:45,247 - DEBUG - _trace.py:87 - send_request_body.started request=<Request [b'POST']> 2025-08-09 13:06:45,247 - DEBUG - _trace.py:87 - send_request_body.complete 2025-08-09 13:06:53,238 - INFO - _client.py:1740 - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent "HTTP/1.1 200 OK"
これはADKのロギングの実装の伸びしろです
伸びしろ:ADKはルートロガーを設定する実装をしている
adk web
やadk api_server
はlogs.setup_adk_logger()
関数を呼び出しています。
adk web
https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/cli/cli_tools_click.py#L743adk api_server
https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/cli/cli_tools_click.py#L834
logs.setup_adk_logger()
の実装に伸びしろがあります。
https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/cli/utils/logs.py#L27-L32
def setup_adk_logger(level=logging.INFO): logging.basicConfig(level=level, format=LOGGING_FORMAT) adk_logger = logging.getLogger('google_adk') adk_logger.setLevel(level)
basicConfig()
でルートロガーを設定- 渡したログレベル(先の例では
DEBUG
) - 標準出力へのハンドラ + フォーマッタ6
- 渡したログレベル(先の例では
adk_logger
を渡したログレベルに設定- ハンドラは設定しない7
まず、ロガーには階層関係(=親子関係)があります。
foo.bar
ロガーの親はfoo
ロガーfoo
ロガーの親はルートロガー
つまり、google_adkロガーの親はルートロガーです。
ロガーが(自身のレベル以上の呼び出しによって)ログレコードを作る時、ログレコードは親のロガーに渡ります(propagate)
https://docs.python.org/ja/3/library/logging.html#logging.Logger.propagate
単にlogging.getLogger()
すると、そのロガーはNOTSET
レベルです。
NOTSET
レベルとは、ロガーを親へ親へと辿っていき、最初に見つかったNOTSET
以外のレベルとして働きます。
つまり、ルートロガーがDEBUGレベルのとき、google_adk
ロガーはDEBUG
レベルになります。
adk_logger.setLevel(level)
はあってもなくても変わらないでしょう。
一番の伸びしろは、ライブラリがbasicConfig()
でルートロガーを設定してしまっていることです。
https://docs.python.org/ja/3/library/logging.html#logging.basicConfig
ルートロガーはすべてのロガーの親です。
そしてADKの依存するライブラリの中に、ルートロガーの子に当たるロガーがNOTSET
レベルでたくさんあります。
ルートロガーがDEBUGレベルの時、google_adk
ロガーやライブラリの中のロガーはDEBUGレベルになります。
google_adk
ロガーやライブラリの中のロガーが作ったログレコードは、propagateによりルートロガーにわたります。
basicConfig()
により、ルートロガーには標準出力へのハンドラが設定されています。
よって、全ロガーのDEBUG
レベル(以上)のログが標準出力に出力されているわけです!
つまり伸びしろは、ユーザはADKのDEBUGレベルのログだけを見たいのに、実装はADKが依存するライブラリのDEBUGレベルも含めてすべてロギングしてしまっていることにあります。
Workaround:google_adkロガーのログだけフィルタする
adk web
を叩くとルートロガーが設定されてしまうので(それはリポジトリ側に働きかけるとして)、ルートロガーのハンドラにフィルタを設定して、google_adkロガーのログだけ出力するようにします。
https://docs.python.org/ja/3/library/logging.html#filter-objects
root_logger = logging.getLogger() root_logger.handlers[0].addFilter(logging.Filter("google_adk"))
Logging Flowに図示されているのですが、
- 子のロガー(DEBUGレベル)でログレコード作成
- -> 親のルートロガーにpropagate
- -> ルートロガーのハンドラのフィルタをチェック
これにより、LLM RequestとLLM Responseが中心で(一部ADKのロギングが混じる)出力となりました8
終わりに
ADKのCLIでは--log_level DEBUG
または-v
/--verbose
の指定で、LLMへのリクエストやLLM
からのレスポンスをロギングして確認できます。
ただし、v1.10.0時点のロギングの実装には、logging.basicConfig()
でルートロガーを設定してしまっているという伸びしろがあり、ログ出力はノイジーです。
エージェントの実装の中で、ルートロガーにフィルタを設定するというWorkaroundを紹介しました。
結びに、Python公式ドキュメント内の「ライブラリのためのロギングの設定」より
ルートロガーに直接ログを記録することにより、あなたのライブラリを利用するアプリケーション開発者が、ロギングの詳細度 (verbosity) やハンドラを望みのとおりに設定することを困難にしたり、不可能にしてしまいます。
ライブラリでルートロガーを触ってはいけません
- 「Standard Library」 https://google.github.io/adk-docs/observability/logging/#logging-philosophy↩
- 「Hierarchical Loggers」 https://google.github.io/adk-docs/observability/logging/#logging-philosophy↩
- Crucial for debugging https://google.github.io/adk-docs/observability/logging/#log-levels↩
- 全容は https://google.github.io/adk-docs/observability/logging/#what-is-logged↩
- https://google.github.io/adk-docs/observability/logging/#configuring-logging-with-the-adk-cli↩
- ファイル名(ファイルパスではない9)と行数が出るフォーマットです https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/cli/utils/logs.py#L22-L24↩
- ルートロガーへのpropagateを使って、ルートロガーのハンドラで出力するためです↩
- LLM RequestとLLM Responseが作られるファイルは https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/models/google_llm.py なので、フィルタでもっときつく絞ることもできます↩
-
個人的にはログレコードの
name
とfuncName
を出力したいです https://docs.python.org/ja/3/library/logging.html#logrecord-attributes (ライブラリ側でbasicConfigされていると、この差し替えも大変)↩