OpenHands/tests/unit/test_logging.py
Graham Neubig c7dff3e4d2
Remove third-party runtimes (daytona, modal, e2b, runloop) from main codebase (#9213)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
2025-06-26 07:39:39 -04:00

188 lines
6.2 KiB
Python

import json
import logging
from io import StringIO
from unittest.mock import patch
import pytest
from openhands.core.config import LLMConfig, OpenHandsConfig
from openhands.core.logger import OpenHandsLoggerAdapter, json_log_handler
from openhands.core.logger import openhands_logger as openhands_logger
@pytest.fixture
def test_handler():
stream = StringIO()
handler = logging.StreamHandler(stream)
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(message)s')
handler.setFormatter(formatter)
openhands_logger.addHandler(handler)
yield openhands_logger, stream
openhands_logger.removeHandler(handler)
@pytest.fixture
def json_handler():
stream = StringIO()
json_handler = json_log_handler(logging.INFO, _out=stream)
openhands_logger.addHandler(json_handler)
yield openhands_logger, stream
openhands_logger.removeHandler(json_handler)
def test_openai_api_key_masking(test_handler):
logger, stream = test_handler
api_key = 'sk-1234567890abcdef'
message = f"OpenAI API key: api_key='{api_key}'and there's some stuff here"
logger.info(message)
log_output = stream.getvalue()
assert api_key not in log_output
def test_azure_api_key_masking(test_handler):
logger, stream = test_handler
api_key = '1a2b3c4d5e6f7g8h9i0j'
message = f"Azure API key: api_key='{api_key}' and chatty chat with ' and \" and '"
logger.info(message)
log_output = stream.getvalue()
assert api_key not in log_output
def test_google_vertex_api_key_masking(test_handler):
logger, stream = test_handler
api_key = 'AIzaSyA1B2C3D4E5F6G7H8I9J0'
message = f"Google Vertex API key: api_key='{api_key}' or not"
logger.info(message)
log_output = stream.getvalue()
assert api_key not in log_output
def test_anthropic_api_key_masking(test_handler):
logger, stream = test_handler
api_key = 'sk-ant-1234567890abcdef-some-more-stuff-here'
message = f"Anthropic API key: api_key='{api_key}' and there's some 'stuff' here"
logger.info(message)
log_output = stream.getvalue()
assert api_key not in log_output
def test_llm_config_attributes_masking(test_handler):
logger, stream = test_handler
llm_config = LLMConfig(
api_key='sk-abc123',
aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
)
logger.info(f'LLM Config: {llm_config}')
log_output = stream.getvalue()
assert 'sk-abc123' not in log_output
assert 'AKIAIOSFODNN7EXAMPLE' not in log_output
assert 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' not in log_output
def test_app_config_attributes_masking(test_handler):
logger, stream = test_handler
app_config = OpenHandsConfig(search_api_key='search-xyz789')
logger.info(f'App Config: {app_config}')
log_output = stream.getvalue()
assert 'github_token' not in log_output
assert 'search-xyz789' not in log_output
assert 'ghp_abcdefghijklmnopqrstuvwxyz' not in log_output
def test_sensitive_env_vars_masking(test_handler):
logger, stream = test_handler
environ = {
'API_KEY': 'API_KEY_VALUE',
'AWS_ACCESS_KEY_ID': 'AWS_ACCESS_KEY_ID_VALUE',
'AWS_SECRET_ACCESS_KEY': 'AWS_SECRET_ACCESS_KEY_VALUE',
'E2B_API_KEY': 'E2B_API_KEY_VALUE',
'GITHUB_TOKEN': 'GITHUB_TOKEN_VALUE',
'JWT_SECRET': 'JWT_SECRET_VALUE',
}
with patch.dict('openhands.core.logger.os.environ', environ, clear=True):
log_message = ' '.join(f"{attr}='{value}'" for attr, value in environ.items())
logger.info(log_message)
log_output = stream.getvalue()
for _, value in environ.items():
assert value not in log_output
def test_special_cases_masking(test_handler):
logger, stream = test_handler
environ = {
'LLM_API_KEY': 'LLM_API_KEY_VALUE',
'SANDBOX_ENV_GITHUB_TOKEN': 'SANDBOX_ENV_GITHUB_TOKEN_VALUE',
}
with patch.dict('openhands.core.logger.os.environ', environ, clear=True):
log_message = ' '.join(
f"{attr}={value} with no single quotes' and something"
for attr, value in environ.items()
)
logger.info(log_message)
log_output = stream.getvalue()
for attr, value in environ.items():
assert value not in log_output
class TestJsonOutput:
def test_info(self, json_handler):
logger, string_io = json_handler
logger.info('Test message')
output = json.loads(string_io.getvalue())
assert 'timestamp' in output
del output['timestamp']
assert output == {'message': 'Test message', 'level': 'INFO'}
def test_error(self, json_handler):
logger, string_io = json_handler
logger.error('Test message')
output = json.loads(string_io.getvalue())
del output['timestamp']
assert output == {'message': 'Test message', 'level': 'ERROR'}
def test_extra_fields(self, json_handler):
logger, string_io = json_handler
logger.info('Test message', extra={'key': '..val..'})
output = json.loads(string_io.getvalue())
del output['timestamp']
assert output == {
'key': '..val..',
'message': 'Test message',
'level': 'INFO',
}
def test_extra_fields_from_adapter(self, json_handler):
logger, string_io = json_handler
subject = OpenHandsLoggerAdapter(logger, extra={'context_field': '..val..'})
subject.info('Test message', extra={'log_fied': '..val..'})
output = json.loads(string_io.getvalue())
del output['timestamp']
assert output == {
'context_field': '..val..',
'log_fied': '..val..',
'message': 'Test message',
'level': 'INFO',
}
def test_extra_fields_from_adapter_can_override(self, json_handler):
logger, string_io = json_handler
subject = OpenHandsLoggerAdapter(logger, extra={'override': 'a'})
subject.info('Test message', extra={'override': 'b'})
output = json.loads(string_io.getvalue())
del output['timestamp']
assert output == {
'override': 'b',
'message': 'Test message',
'level': 'INFO',
}