mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
fix: Redirect LiteLLM logs from stderr to stdout
LiteLLM's StreamHandlers output to stderr by default. In cloud environments like GCP, stderr output is treated as ERROR severity regardless of actual log level, causing INFO-level logs to be incorrectly classified as errors. This change redirects LiteLLM loggers to stdout in two places: - In `lite_llm.py`: Immediately after litellm import - In `logs.py`: When `setup_adk_logger()` is called (with guard to check if litellm is imported) Close #3824 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 843393874
This commit is contained in:
committed by
Copybara-Service
parent
055dfc7974
commit
db711cd789
@@ -12,7 +12,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the Licens
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import ANY
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import Mock
|
||||
@@ -29,6 +35,7 @@ from google.adk.models.lite_llm import _message_to_generate_content_response
|
||||
from google.adk.models.lite_llm import _model_response_to_chunk
|
||||
from google.adk.models.lite_llm import _model_response_to_generate_content_response
|
||||
from google.adk.models.lite_llm import _parse_tool_calls_from_text
|
||||
from google.adk.models.lite_llm import _redirect_litellm_loggers_to_stdout
|
||||
from google.adk.models.lite_llm import _schema_to_dict
|
||||
from google.adk.models.lite_llm import _split_message_content_and_tool_calls
|
||||
from google.adk.models.lite_llm import _to_litellm_response_format
|
||||
@@ -3133,3 +3140,79 @@ async def test_get_completion_inputs_non_openai_no_file_upload(mocker):
|
||||
assert "file_id" not in content[1]["file"]
|
||||
|
||||
mock_acreate_file.assert_not_called()
|
||||
|
||||
|
||||
class TestRedirectLitellmLoggersToStdout(unittest.TestCase):
|
||||
"""Tests for _redirect_litellm_loggers_to_stdout function."""
|
||||
|
||||
def test_redirects_stderr_handler_to_stdout(self):
|
||||
"""Test that handlers pointing to stderr are redirected to stdout."""
|
||||
test_logger = logging.getLogger("LiteLLM")
|
||||
# Create a handler pointing to stderr
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
test_logger.addHandler(handler)
|
||||
|
||||
try:
|
||||
self.assertIs(handler.stream, sys.stderr)
|
||||
|
||||
_redirect_litellm_loggers_to_stdout()
|
||||
|
||||
self.assertIs(handler.stream, sys.stdout)
|
||||
finally:
|
||||
# Clean up
|
||||
test_logger.removeHandler(handler)
|
||||
|
||||
def test_preserves_stdout_handler(self):
|
||||
"""Test that handlers already pointing to stdout are not modified."""
|
||||
test_logger = logging.getLogger("LiteLLM Proxy")
|
||||
# Create a handler already pointing to stdout
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
test_logger.addHandler(handler)
|
||||
|
||||
try:
|
||||
_redirect_litellm_loggers_to_stdout()
|
||||
|
||||
self.assertIs(handler.stream, sys.stdout)
|
||||
finally:
|
||||
# Clean up
|
||||
test_logger.removeHandler(handler)
|
||||
|
||||
def test_does_not_affect_non_stream_handlers(self):
|
||||
"""Test that non-StreamHandler handlers are not affected."""
|
||||
test_logger = logging.getLogger("LiteLLM Router")
|
||||
# Create a FileHandler (not a StreamHandler)
|
||||
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
||||
temp_file_name = temp_file.name
|
||||
with contextlib.closing(
|
||||
logging.FileHandler(temp_file_name)
|
||||
) as file_handler:
|
||||
test_logger.addHandler(file_handler)
|
||||
|
||||
try:
|
||||
_redirect_litellm_loggers_to_stdout()
|
||||
# FileHandler should not be modified (it doesn't point to stderr or stdout)
|
||||
self.assertEqual(file_handler.baseFilename, temp_file_name)
|
||||
finally:
|
||||
# Clean up
|
||||
test_logger.removeHandler(file_handler)
|
||||
os.unlink(temp_file_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"logger_name",
|
||||
["LiteLLM", "LiteLLM Proxy", "LiteLLM Router"],
|
||||
ids=["LiteLLM", "LiteLLM Proxy", "LiteLLM Router"],
|
||||
)
|
||||
def test_handles_litellm_logger_names(logger_name):
|
||||
"""Test that LiteLLM logger names are processed."""
|
||||
test_logger = logging.getLogger(logger_name)
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
test_logger.addHandler(handler)
|
||||
|
||||
try:
|
||||
_redirect_litellm_loggers_to_stdout()
|
||||
|
||||
assert handler.stream is sys.stdout
|
||||
finally:
|
||||
# Clean up
|
||||
test_logger.removeHandler(handler)
|
||||
|
||||
Reference in New Issue
Block a user