Skip to content

Conversation

@ian28223
Copy link
Contributor

@ian28223 ian28223 commented Oct 29, 2025

What does this PR do?

Introduces a centralized RequestsWrapper to standardize HTTP session handling across the agent.
This replaces direct requests calls in Flare, the Forwarder, and API key validation with a unified wrapper that applies consistent configuration for timeouts, proxy usage, and SSL verification.


Motivation

  • Previously, the agent used ad-hoc requests calls in different components, which led to inconsistent HTTP behavior.

    • For example, skip_ssl_validation was respected in API key validation but not in the Forwarder or Flare upload paths.
  • By introducing a RequestsWrapper, all HTTP clients now share the same session setup and respect the same configuration flags, improving consistency, maintainability, and testability.


Describe how you validated your changes

  • Tested on Python 3.7 running on Ubuntu.

  • Verified correct handling of:

    • skip_ssl_validation
    • Proxy configuration (via environment and config)
    • Timeout behavior and request header handling
  • Updated unit tests for Flare and Forwarder to use the RequestsWrapper.

  • Confirmed all tests pass locally.

  • Have not yet tested on AIX.


Additional Notes

  • This refactor lays the groundwork for future improvements to HTTP behavior consistency across the agent.
  • Once merged, all future HTTP interactions should use RequestsWrapper instead of calling requests directly.
  • Supersedes PR [#140](skip_ssl_validation  #140) (fix_skip_ssl)
  • Supersedes PR [#141](Improve forwarder logging #141) (Improve forwarder logging)
  • Addresses TEEP-2680 [AIX Agent] improve forwarder logging
  • Addresses TEEP-2678 [AIX Agent] Implement skip_ssl_validation for Forwarder

Tests

  • Environment: Python 3.7 on Ubuntu
  • Tests run: pytest utils/tests/
  • Outcome: All tests passed except for some TestLogging failures likely due to running on a non-AIX environment.
    These failures are unrelated to the RequestsWrapper refactor.
🧪 Local test results

================================================================================================================== test session starts ===================================================================================================================
platform linux -- Python 3.7.17, pytest-3.6.2, py-1.10.0, pluggy-0.6.0 -- /opt/datadog-agent/venv/bin/python
cachedir: .pytest_cache
rootdir: /opt/datadog-agent-local, inifile: pytest.ini
plugins: requests-mock-1.5.2
collected 51 items

utils/tests/test_api.py::test_validate_api_key PASSED [ 1%]
utils/tests/test_flare.py::test_flare_basic PASSED [ 3%]
utils/tests/test_flare.py::test_flare_400 PASSED [ 5%]
utils/tests/test_flare.py::test_flare_proxy_timeout PASSED [ 7%]
utils/tests/test_flare.py::test_flare_too_large PASSED [ 9%]
utils/tests/test_flare.py::test_flare_endpoint_with_case_id PASSED [ 11%]
utils/tests/test_flare.py::test_flare_includes_dd_api_key_header PASSED [ 13%]
utils/tests/test_hash.py::test_freeze PASSED [ 15%]
utils/tests/test_hash.py::test_freeze_tuple_mutables PASSED [ 17%]
utils/tests/test_hash.py::test_hash_mutable PASSED [ 19%]
utils/tests/test_hostname.py::test_is_valid_hostname PASSED [ 21%]
utils/tests/test_hostname.py::test_get_hostname_conf PASSED [ 23%]
utils/tests/test_hostname.py::test_get_hostname_bytes PASSED [ 25%]
utils/tests/test_hostname.py::test_get_hostname_bin PASSED [ 27%]
utils/tests/test_hostname.py::test_get_hostname_bin_nonrfc PASSED [ 29%]
utils/tests/test_hostname.py::test_get_hostname_socket PASSED [ 31%]
utils/tests/test_hostname.py::test_get_hostname_error PASSED [ 33%]
utils/tests/test_http.py::test_request_calls_session PASSED [ 35%]
utils/tests/test_http.py::test_verify_depends_on_skip_ssl_validation[True-False-True] PASSED [ 37%]
utils/tests/test_http.py::test_verify_depends_on_skip_ssl_validation[False-True-False] PASSED [ 39%]
utils/tests/test_http.py::test_requests_wrapper_proxies[proxies0] PASSED [ 41%]
utils/tests/test_http.py::test_requests_wrapper_proxies[proxies1] PASSED [ 43%]
utils/tests/test_http.py::test_forwarder_uses_custom_forwarder_timeout[None-20] PASSED [ 45%]
utils/tests/test_http.py::test_forwarder_uses_custom_forwarder_timeout[15-15] PASSED [ 47%]
utils/tests/test_http.py::test_forwarder_passes_proxies_to_transaction PASSED [ 49%]
utils/tests/test_logs.py::TestLogging::test_console_logging_enabled_by_default PASSED [ 50%]
utils/tests/test_logs.py::TestLogging::test_console_logging_disabled PASSED [ 52%]
utils/tests/test_logs.py::TestLogging::test_environment_variable_disable_console_logging PASSED [ 54%]
utils/tests/test_logs.py::TestLogging::test_file_and_console_logging_together PASSED [ 56%]
utils/tests/test_logs.py::TestLogging::test_file_logging_only PASSED [ 58%]
utils/tests/test_logs.py::TestLogging::test_log_level_configuration PASSED [ 60%]
utils/tests/test_network.py::test_mapto_v6 PASSED [ 62%]
utils/tests/test_network.py::test_get_socket_address PASSED [ 64%]
utils/tests/test_network.py::test_get_proxy PASSED [ 66%]
utils/tests/test_network.py::test_get_proxy_from_env PASSED [ 68%]
utils/tests/test_network.py::test_get_site PASSED [ 70%]
utils/tests/test_platform.py::test_get_os_freebsd PASSED [ 72%]
utils/tests/test_platform.py::test_get_os_linux PASSED [ 74%]
utils/tests/test_platform.py::test_get_os_sunos PASSED [ 76%]
utils/tests/test_platform.py::test_get_os_aix6 PASSED [ 78%]
utils/tests/test_platform.py::test_get_os_aix7 PASSED [ 80%]
utils/tests/test_platform.py::test_get_os_custom PASSED [ 82%]
utils/tests/test_platform.py::test_running_root PASSED [ 84%]
utils/tests/test_signals.py::test_signal_registration PASSED [ 86%]
utils/tests/test_signals.py::test_signal_delivery PASSED [ 88%]
utils/tests/test_stats.py::test_stats PASSED [ 90%]
utils/tests/test_strip.py::test_strip_basic PASSED [ 92%]
utils/tests/test_strip.py::test_strip_hints PASSED [ 94%]
utils/tests/test_strip.py::test_key_matcher PASSED [ 96%]
utils/tests/test_unicode.py::test_ensure_unicode PASSED [ 98%]
utils/tests/test_util.py::test_is_affirmative PASSED [100%]

=============================================================================================================== 51 passed in 2.15 seconds ================================================================================================================
ian.bucad@IB-MBP-M3 Docker % make test
Running tests inside container: unix-agent
docker compose exec -it unix-agent /opt/datadog-agent/venv/bin/python -m pytest -v
================================================================================================================== test session starts ===================================================================================================================
platform linux -- Python 3.7.17, pytest-3.6.2, py-1.10.0, pluggy-0.6.0 -- /opt/datadog-agent/venv/bin/python
cachedir: .pytest_cache
rootdir: /opt/datadog-agent-local, inifile: pytest.ini
plugins: requests-mock-1.5.2
collected 183 items

aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_formatter PASSED [ 0%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_running_beacon PASSED [ 1%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_counter_normalization PASSED [ 1%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_histogram_normalization PASSED [ 2%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_tags PASSED [ 2%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_magic_tags PASSED [ 3%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_counter PASSED [ 3%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_sampled_counter PASSED [ 4%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_gauge PASSED [ 4%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_sets PASSED [ 5%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_string_sets PASSED [ 6%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_ignore_distribution PASSED [ 6%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_rate PASSED [ 7%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_rate_errors PASSED [ 7%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_gauge_sample_rate PASSED [ 8%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_histogram PASSED [ 8%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_sampled_histogram PASSED [ 9%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_batch_submission PASSED [ 9%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_monokey_batching_notags PASSED [ 10%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_monokey_batching_withtags PASSED [ 10%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_monokey_batching_withtags_with_sampling PASSED [ 11%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_bad_packets_throw_errors PASSED [ 12%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_metrics_expiry PASSED [ 12%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_diagnostic_stats PASSED [ 13%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_histogram_counter PASSED [ 13%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_scientific_notation PASSED [ 14%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_event_tags PASSED [ 14%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_event_title PASSED [ 15%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_event_text PASSED [ 15%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_event_text_utf8 PASSED [ 16%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_service_check_basic PASSED [ 16%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_service_check_message PASSED [ 17%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_service_check_tags PASSED [ 18%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_service_check_tag_key_ends_with_m PASSED [ 18%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_recent_point_threshold PASSED [ 19%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_packet_string_endings PASSED [ 19%]
aggregator/tests/test_aggregator.py::TestMetricsAggregator::test_source_classification PASSED [ 20%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_counter_normalization PASSED [ 20%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_histogram_normalization PASSED [ 21%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_tags PASSED [ 21%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_counter PASSED [ 22%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_empty_counter PASSED [ 22%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_counter_buckets PASSED [ 23%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_counter_flush_during_bucket PASSED [ 24%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_sampled_counter PASSED [ 24%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_gauge PASSED [ 25%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_gauge_buckets PASSED [ 25%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_gauge_flush_during_bucket PASSED [ 26%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_sets PASSED [ 26%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_string_sets PASSED [ 27%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_sets_buckets PASSED [ 27%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_sets_flush_during_bucket PASSED [ 28%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_gauge_sample_rate PASSED [ 28%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_histogram PASSED [ 29%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_sampled_histogram PASSED [ 30%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_histogram_buckets PASSED [ 30%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_histogram_flush_during_bucket PASSED [ 31%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_batch_submission PASSED [ 31%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_bad_packets_throw_errors PASSED [ 32%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_metrics_expiry PASSED [ 32%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_diagnostic_stats PASSED [ 33%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_histogram_counter PASSED [ 33%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_scientific_notation PASSED [ 34%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_event_tags PASSED [ 34%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_event_title PASSED [ 35%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_event_text PASSED [ 36%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_recent_point_threshold PASSED [ 36%]
aggregator/tests/test_bucket_aggregator.py::TestUnitMetricsBucketAggregator::test_calculate_bucket_start PASSED [ 37%]
aggregator/tests/test_histogram.py::TestHistogram::test_default PASSED [ 37%]
aggregator/tests/test_histogram.py::TestHistogram::test_custom_single_percentile PASSED [ 38%]
aggregator/tests/test_histogram.py::TestHistogram::test_custom_multiple_percentile PASSED [ 38%]
aggregator/tests/test_histogram.py::TestHistogram::test_custom_invalid_percentile PASSED [ 39%]
aggregator/tests/test_histogram.py::TestHistogram::test_custom_invalid_percentile2 PASSED [ 39%]
aggregator/tests/test_histogram.py::TestHistogram::test_custom_invalid_percentile3skip PASSED [ 40%]
aggregator/tests/test_histogram.py::TestHistogram::test_custom_aggregate PASSED [ 40%]
aggregator/tests/test_types.py::TestMetricResolver::test_metric_resolver PASSED [ 41%]
aggregator/tests/test_types.py::TestMetricResolver::test_bucket_metric_resolver PASSED [ 42%]
checks/corechecks/system/tests/test_cpu.py::test_cpu PASSED [ 42%]
checks/corechecks/system/tests/test_filesystem.py::test_load_aix PASSED [ 43%]
checks/corechecks/system/tests/test_iostat.py::test_iostat_aix PASSED [ 43%]
checks/corechecks/system/tests/test_iostat.py::test_iostat_value_extract PASSED [ 44%]
checks/corechecks/system/tests/test_load.py::test_load PASSED [ 44%]
checks/corechecks/system/tests/test_load.py::test_load_no_cpu_count PASSED [ 45%]
checks/corechecks/system/tests/test_load.py::test_load_aix PASSED [ 45%]
checks/corechecks/system/tests/test_memory.py::test_memory_linux PASSED [ 46%]
checks/corechecks/system/tests/test_uptime_check.py::test_uptime_check PASSED [ 46%]
checks/corechecks/system/tests/test_uptime_check.py::test_uptime_check_subprocess PASSED [ 47%]
checks/corechecks/system/tests/test_uptime_check.py::test_uptime_check_subprocess_nodays PASSED [ 48%]
collector/tests/test_loaders.py::TestCheckLoader::test_load PASSED [ 48%]
collector/tests/test_loaders.py::TestWheelLoader::test_load PASSED [ 49%]
config/tests/test_config.py::TestConfig::test_init PASSED [ 49%]
config/tests/test_config.py::TestConfig::test_empty_conf PASSED [ 50%]
config/tests/test_config.py::TestConfig::test_default PASSED [ 50%]
config/tests/test_config.py::TestConfig::test_init_default PASSED [ 51%]
config/tests/test_config.py::TestConfig::test_init_default_merge PASSED [ 51%]
config/tests/test_config.py::TestConfig::test_set_and_reset PASSED [ 52%]
config/tests/test_config.py::TestConfig::test_load PASSED [ 53%]
config/tests/test_config.py::TestConfig::test_env_load PASSED [ 53%]
config/tests/test_config.py::TestConfig::test_get PASSED [ 54%]
config/tests/test_config.py::TestConfig::test_validate_aggregates_sane PASSED [ 54%]
config/tests/test_config.py::TestConfig::test_validate_aggregates_sanitized PASSED [ 55%]
config/tests/test_config.py::TestConfig::test_validate_percentiles PASSED [ 55%]
config/tests/test_config.py::TestConfig::test_validate_percentiles_hich_precision PASSED [ 56%]
config/tests/test_config.py::TestConfig::test_validate_percentiles_badval PASSED [ 56%]
config/tests/test_config.py::TestConfig::test_validate_percentiles_bounds PASSED [ 57%]
config/tests/test_config.py::TestConfig::test_config_providers PASSED [ 57%]
config/tests/test_config.py::TestConfig::test_env_namespaces PASSED [ 58%]
config/tests/test_config.py::TestConfig::test_env_override PASSED [ 59%]
config/tests/test_config.py::TestConfig::test_build_defaults PASSED [ 59%]
config/tests/test_providers.py::TestFileProvider::test_config_flatten PASSED [ 60%]
config/tests/test_providers.py::TestFileProvider::test_checkname_extract PASSED [ 60%]
config/tests/test_providers.py::TestFileProvider::test_provider PASSED [ 61%]
forwarder/tests/test_forwarder.py::test_forwarder_creation PASSED [ 61%]
forwarder/tests/test_forwarder.py::test_forwarder_start_stop PASSED [ 62%]
forwarder/tests/test_forwarder.py::test_submit_payload_ PASSED [ 62%]
forwarder/tests/test_forwarder.py::test_submit_v1_series PASSED [ 63%]
forwarder/tests/test_forwarder.py::test_submit_v1_service_checks PASSED [ 63%]
forwarder/tests/test_transaction.py::test_transaction_creation PASSED [ 64%]
forwarder/tests/test_transaction.py::test_get_endpoint PASSED [ 65%]
forwarder/tests/test_transaction.py::test_reschedule PASSED [ 65%]
forwarder/tests/test_transaction.py::test_process_success PASSED [ 66%]
forwarder/tests/test_transaction.py::test_process_error PASSED [ 66%]
forwarder/tests/test_worker.py::test_init PASSED [ 67%]
forwarder/tests/test_worker.py::test_worker_process_transactions PASSED [ 67%]
forwarder/tests/test_worker.py::test_worker_stop PASSED [ 68%]
forwarder/tests/test_worker.py::test_retry_worker_flush PASSED [ 68%]
forwarder/tests/test_worker.py::test_retry_worker_process_transaction PASSED [ 69%]
forwarder/tests/test_worker.py::test_retryworker_stop PASSED [ 69%]
metadata/tests/test_metadata.py::test_get_metadata PASSED [ 70%]
serialize/tests/test_serialize.py::test_split PASSED [ 71%]
serialize/tests/test_serialize.py::test_serialize PASSED [ 71%]
serialize/tests/test_serialize.py::test_serialize_and_push PASSED [ 72%]
utils/tests/test_api.py::test_validate_api_key PASSED [ 72%]
utils/tests/test_flare.py::test_flare_basic PASSED [ 73%]
utils/tests/test_flare.py::test_flare_400 PASSED [ 73%]
utils/tests/test_flare.py::test_flare_proxy_timeout PASSED [ 74%]
utils/tests/test_flare.py::test_flare_too_large PASSED [ 74%]
utils/tests/test_flare.py::test_flare_endpoint_with_case_id PASSED [ 75%]
utils/tests/test_flare.py::test_flare_includes_dd_api_key_header PASSED [ 75%]
utils/tests/test_hash.py::test_freeze PASSED [ 76%]
utils/tests/test_hash.py::test_freeze_tuple_mutables PASSED [ 77%]
utils/tests/test_hash.py::test_hash_mutable PASSED [ 77%]
utils/tests/test_hostname.py::test_is_valid_hostname PASSED [ 78%]
utils/tests/test_hostname.py::test_get_hostname_conf PASSED [ 78%]
utils/tests/test_hostname.py::test_get_hostname_bytes PASSED [ 79%]
utils/tests/test_hostname.py::test_get_hostname_bin PASSED [ 79%]
utils/tests/test_hostname.py::test_get_hostname_bin_nonrfc PASSED [ 80%]
utils/tests/test_hostname.py::test_get_hostname_socket PASSED [ 80%]
utils/tests/test_hostname.py::test_get_hostname_error PASSED [ 81%]
utils/tests/test_http.py::test_request_calls_session PASSED [ 81%]
utils/tests/test_http.py::test_verify_depends_on_skip_ssl_validation[True-False-True] PASSED [ 82%]
utils/tests/test_http.py::test_verify_depends_on_skip_ssl_validation[False-True-False] PASSED [ 83%]
utils/tests/test_http.py::test_requests_wrapper_proxies[proxies0] PASSED [ 83%]
utils/tests/test_http.py::test_requests_wrapper_proxies[proxies1] PASSED [ 84%]
utils/tests/test_http.py::test_forwarder_uses_custom_forwarder_timeout[None-20] PASSED [ 84%]
utils/tests/test_http.py::test_forwarder_uses_custom_forwarder_timeout[15-15] PASSED [ 85%]
utils/tests/test_http.py::test_forwarder_passes_proxies_to_transaction PASSED [ 85%]
utils/tests/test_logs.py::TestLogging::test_console_logging_enabled_by_default PASSED [ 86%]
utils/tests/test_logs.py::TestLogging::test_console_logging_disabled PASSED [ 86%]
utils/tests/test_logs.py::TestLogging::test_environment_variable_disable_console_logging PASSED [ 87%]
utils/tests/test_logs.py::TestLogging::test_file_and_console_logging_together FAILED [ 87%]
utils/tests/test_logs.py::TestLogging::test_file_logging_only FAILED [ 88%]
utils/tests/test_logs.py::TestLogging::test_log_level_configuration PASSED [ 89%]
utils/tests/test_network.py::test_mapto_v6 PASSED [ 89%]
utils/tests/test_network.py::test_get_socket_address PASSED [ 90%]
utils/tests/test_network.py::test_get_proxy PASSED [ 90%]
utils/tests/test_network.py::test_get_proxy_from_env PASSED [ 91%]
utils/tests/test_network.py::test_get_site PASSED [ 91%]
utils/tests/test_platform.py::test_get_os_freebsd PASSED [ 92%]
utils/tests/test_platform.py::test_get_os_linux PASSED [ 92%]
utils/tests/test_platform.py::test_get_os_sunos PASSED [ 93%]
utils/tests/test_platform.py::test_get_os_aix6 PASSED [ 93%]
utils/tests/test_platform.py::test_get_os_aix7 PASSED [ 94%]
utils/tests/test_platform.py::test_get_os_custom PASSED [ 95%]
utils/tests/test_platform.py::test_running_root PASSED [ 95%]
utils/tests/test_signals.py::test_signal_registration PASSED [ 96%]
utils/tests/test_signals.py::test_signal_delivery PASSED [ 96%]
utils/tests/test_stats.py::test_stats PASSED [ 97%]
utils/tests/test_strip.py::test_strip_basic PASSED [ 97%]
utils/tests/test_strip.py::test_strip_hints PASSED [ 98%]
utils/tests/test_strip.py::test_key_matcher PASSED [ 98%]
utils/tests/test_unicode.py::test_ensure_unicode PASSED [ 99%]
utils/tests/test_util.py::test_is_affirmative PASSED [100%]

======================================================================================================================== FAILURES ========================================================================================================================
___________________________________________________________________________________________________ TestLogging.test_file_and_console_logging_together ___________________________________________________________________________________________________

self = <utils.tests.test_logs.TestLogging object at 0xffffa6fea990>

def test_file_and_console_logging_together(self):
    """Test that both file and console logging can work together"""
    # Create temp directory for log file
    temp_dir = tempfile.mkdtemp()
    log_file_path = os.path.join(temp_dir, 'test_agent.log')

    # Create a config with both file and console logging
    with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
        yaml.dump({
            'api_key': 'test',
            'log_level': 'info',
            'logging': {
                'disable_console_logging': False,
                'disable_file_logging': False,
                'agent_log_file': log_file_path
            }
        }, f)
        config_file = f.name

    try:
        # Load test config
        config.add_search_path(os.path.dirname(config_file))
        config.conf_name = os.path.basename(config_file)
        config.load()

        # Initialize logging
        initialize_logging('agent')

        # Test logging
        logger = logging.getLogger('test')
        logger.info("Test message for dual logging")

        # Check handlers
        root_logger = logging.getLogger()
        has_file_handler = any(isinstance(h, logging.handlers.RotatingFileHandler)
                               for h in root_logger.handlers)

        # Check if log file was created and has content
        log_file_exists = os.path.exists(log_file_path)
        log_file_size = os.path.getsize(log_file_path) if log_file_exists else 0

        # Verify config values
        assert not self._get_config_disable_console_value(), "Console logging should be enabled"
        assert not config.get('logging', {}).get('disable_file_logging', False), "File logging should be enabled"

        # Verify file logging works
        assert has_file_handler, "File handler should be present"
      assert log_file_exists, "Log file should be created"

E AssertionError: Log file should be created
E assert False

utils/tests/test_logs.py:200: AssertionError
------------------------------------------------------------------------------------------------------------------- Captured log call --------------------------------------------------------------------------------------------------------------------
config.py 103 INFO loaded config from: /tmp/tmpctncmetq.yaml
config.py 103 INFO loaded config from: /tmp/tmpctncmetq.yaml
test_logs.py 183 INFO Test message for dual logging
___________________________________________________________________________________________________________ TestLogging.test_file_logging_only ___________________________________________________________________________________________________________

self = <utils.tests.test_logs.TestLogging object at 0xffffa6f836d0>

def test_file_logging_only(self):
    """Test that file logging works when console logging is disabled"""
    # Create temp directory for log file
    temp_dir = tempfile.mkdtemp()
    log_file_path = os.path.join(temp_dir, 'test_agent.log')

    # Create a config with only file logging
    with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
        yaml.dump({
            'api_key': 'test',
            'log_level': 'info',
            'logging': {
                'disable_console_logging': True,
                'disable_file_logging': False,
                'agent_log_file': log_file_path
            }
        }, f)
        config_file = f.name

    try:
        # Load test config
        config.add_search_path(os.path.dirname(config_file))
        config.conf_name = os.path.basename(config_file)
        config.load()

        # Initialize logging
        initialize_logging('agent')

        # Test logging
        logger = logging.getLogger('test')
        logger.info("Test message for file-only logging")

        # Check handlers
        root_logger = logging.getLogger()
        has_file_handler = any(isinstance(h, logging.handlers.RotatingFileHandler)
                               for h in root_logger.handlers)

        # Check if log file was created and has content
        log_file_exists = os.path.exists(log_file_path)
        log_file_size = os.path.getsize(log_file_path) if log_file_exists else 0

        # Verify config values
        assert self._get_config_disable_console_value(), "Console logging should be disabled"
        assert not config.get('logging', {}).get('disable_file_logging', False), "File logging should be enabled"

        # Verify file logging works even with console disabled
        assert has_file_handler, "File handler should be present"
      assert log_file_exists, "Log file should be created"

E AssertionError: Log file should be created
E assert False

utils/tests/test_logs.py:256: AssertionError
------------------------------------------------------------------------------------------------------------------- Captured log call --------------------------------------------------------------------------------------------------------------------
config.py 103 INFO loaded config from: /tmp/tmpz1gffy5w.yaml
config.py 103 INFO loaded config from: /tmp/tmpz1gffy5w.yaml
test_logs.py 239 INFO Test message for file-only logging
========================================================================================================= 2 failed, 181 passed in 131.48 seconds =========================================================================================================
make: *** [test] Error 1


…and API key validation

Introduces a shared_requests wrapper to unify HTTP session handling across multiple
subsystems. This ensures consistent configuration for retries, timeouts, proxies, and
SSL verification. Upcoming work will migrate remaining components and update tests.
… tests

Adds unit tests for the new shared_requests wrapper to verify session reuse,
timeout handling, and proxy configuration. Updates existing tests (Flare,
Forwarder, and API key validation) to use shared_requests instead of direct
requests calls.
@ian28223 ian28223 requested a review from a team as a code owner October 29, 2025 23:33
@ian28223 ian28223 requested review from a team and nathan-b October 29, 2025 23:59
@ian28223 ian28223 added bug Something isn't working enhancement New feature or request labels Oct 30, 2025
Copy link

@nathan-b nathan-b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems good (and I've been wanting something like this in agent v7 for some time!), but the Unix agent is on extremely reduced maintenance mode.

Basically, in order to spin a new release of the Unix agent, you will have to prove that this is a critical customer issue. Note that we're not even confident that we are able to do a Unix agent release at this point 😅.

Given that I don't think this change meets the exceptionally high bar for spinning a new release, there are two options:

  1. Check this in anyway in the hope that at some point in the future we do a new release and this code is included.
  2. Reject the PR so that what's at head isn't too far removed from the latest release.

It seems as though other product code has gone in over the past few months, so there is precedent for checking this in. I won't block it, but I also want to make sure you understand that we're not (and possibly / probably won't ever) going to release this code.

@ian28223
Copy link
Contributor Author

This change seems good (and I've been wanting something like this in agent v7 for some time!), but the Unix agent is on extremely reduced maintenance mode.

Basically, in order to spin a new release of the Unix agent, you will have to prove that this is a critical customer issue. Note that we're not even confident that we are able to do a Unix agent release at this point 😅.

Given that I don't think this change meets the exceptionally high bar for spinning a new release, there are two options:

  1. Check this in anyway in the hope that at some point in the future we do a new release and this code is included.
  2. Reject the PR so that what's at head isn't too far removed from the latest release.

It seems as though other product code has gone in over the past few months, so there is precedent for checking this in. I won't block it, but I also want to make sure you understand that we're not (and possibly / probably won't ever) going to release this code.

Yes, I'm fully aware that a release is highly unlikely. For now, getting fixes (for the majority of the issues we encounter) merged is the priority and am not expecting a release to follow anytime soon.
That said, checking this in will close up the 2 other PRs this supersedes and am hoping that as more things are merged, it fuels the need for a release.

@ian28223
Copy link
Contributor Author

ian28223 commented Oct 30, 2025

... (and I've been wanting something like this in agent v7 for some time!), ...

Agent v7 does have and uses one here though only used by Python-based checks. That partly inspired this PR

@ian28223
Copy link
Contributor Author

/merge

@dd-devflow-routing-codex
Copy link

dd-devflow-routing-codex bot commented Oct 31, 2025

View all feedbacks in Devflow UI.

2025-10-31 14:34:59 UTC ℹ️ Start processing command /merge


2025-10-31 14:35:05 UTC ℹ️ MergeQueue: waiting for PR to be ready

This pull request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
It will be added to the queue as soon as checks pass and/or get approvals.
Note: if you pushed new commits since the last approval, you may need additional approval.
You can remove it from the waiting list with /remove command.


2025-10-31 14:37:03 UTC ⚠️ MergeQueue: This merge request was unqueued

[email protected] unqueued this merge request

@ian28223
Copy link
Contributor Author

/remove

@dd-devflow-routing-codex
Copy link

dd-devflow-routing-codex bot commented Oct 31, 2025

View all feedbacks in Devflow UI.

2025-10-31 14:36:57 UTC ℹ️ Start processing command /remove


2025-10-31 14:37:00 UTC ℹ️ Devflow: /remove

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request mergequeue-status: removed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants