Repository: urllib3/urllib3

Issue:
urllib3 does not send http.client.connect audit events
### Subject

Despite being implemented in terms of `http.client`, urllib3 doesn't cause `http.client.connect` audit events to be emitted

### Steps to Reproduce

```python
import http.client
import sys

import urllib3


def _hook(event: str, args: tuple):
    if event == "http.client.connect":
        print(event, args)
    if event == 'socket.connect':
        print(event, args)


sys.addaudithook(_hook)

print('using http.client')
http.client.HTTPConnection('www.python.org').connect()
print()
print('using urllib3')
http = urllib3.PoolManager()
resp = http.request("GET", "https://www.python.org")
```

```
using http.client
http.client.connect (<http.client.HTTPConnection object at 0x7f1303983fd0>, 'www.python.org', 80)
socket.connect (<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('0.0.0.0', 0)>, ('199.232.36.223', 80))

using urllib3
socket.connect (<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('0.0.0.0', 0)>, ('199.232.36.223', 443))
```

This is because the urllib3 `HTTPConnection` object overrides `connect`, which is where the audit events are sent from.
https://github.com/python/cpython/blob/e87ada48a9e5d9d03f9759138869216df0d7383a/Lib/http/client.py#L954

### Expected Behavior

`http.client.connect` audit events should be emitted

### Actual Behavior

They aren't


Discussion:
ravi-kale: Duplicate of https://github.com/urllib3/urllib3/issues/2757
ravi-kale: Duplicate of https://github.com/urllib3/urllib3/pull/2847
illia-v: @CCLDArjun please add `from unittest.mock import Mock, patch` to the test file
illia-v: I've pushed the suggested changes so that this can be included in #3084 quicker 🙂

Patch:
diff --git a/changelog/2757.bugfix.rst b/changelog/2757.bugfix.rst
new file mode 100644
index 00000000..40ea75f6
--- /dev/null
+++ b/changelog/2757.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed ``urllib3.connection.HTTPConnection`` to raise the ``http.client.connect`` audit event to have the same behavior as the standard library HTTP client
diff --git a/src/urllib3/connection.py b/src/urllib3/connection.py
index 50f5e616..10109fa9 100644
--- a/src/urllib3/connection.py
+++ b/src/urllib3/connection.py
@@ -5,6 +5,7 @@ import logging
 import os
 import re
 import socket
+import sys
 import typing
 import warnings
 from http.client import HTTPConnection as _HTTPConnection
@@ -76,6 +77,8 @@ RECENT_DATE = datetime.date(2022, 1, 1)
 
 _CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")
 
+_HAS_SYS_AUDIT = hasattr(sys, "audit")
+
 
 class HTTPConnection(_HTTPConnection):
     """
@@ -216,6 +219,10 @@ class HTTPConnection(_HTTPConnection):
                 self, f"Failed to establish a new connection: {e}"
             ) from e
 
+        # Audit hooks are only available in Python 3.8+
+        if _HAS_SYS_AUDIT:
+            sys.audit("http.client.connect", self, self.host, self.port)
+
         return sock
 
     def set_tunnel(


Test patch:
diff --git a/test/with_dummyserver/test_connection.py b/test/with_dummyserver/test_connection.py
index d06a7551..2442c8ad 100644
--- a/test/with_dummyserver/test_connection.py
+++ b/test/with_dummyserver/test_connection.py
@@ -1,7 +1,9 @@
 from __future__ import annotations
 
+import sys
 import typing
 from http.client import ResponseNotReady
+from unittest import mock
 
 import pytest
 
@@ -33,6 +35,19 @@ def test_returns_urllib3_HTTPResponse(pool: HTTPConnectionPool) -> None:
     assert isinstance(response, HTTPResponse)
 
 
+@pytest.mark.skipif(not hasattr(sys, "audit"), reason="requires python 3.8+")
+@mock.patch("urllib3.connection.sys.audit")
+def test_audit_event(audit_mock: mock.Mock, pool: HTTPConnectionPool) -> None:
+    conn = pool._get_conn()
+    conn.request("GET", "/")
+    audit_mock.assert_any_call("http.client.connect", conn, conn.host, conn.port)
+    # Ensure the event is raised only once.
+    connect_events = [
+        call for call in audit_mock.mock_calls if call.args[0] == "http.client.connect"
+    ]
+    assert len(connect_events) == 1
+
+
 def test_does_not_release_conn(pool: HTTPConnectionPool) -> None:
     conn = pool._get_conn()