Repository: urllib3/urllib3

Issue:
urllib3 should not log an error on certificate mismatch
Logging an error and then reraising the exception does not achieve much as the application code will have to handle the exception anyway. It's better to let the application decide if it's a noteworthy event or not.

I personally don't think that a certificate mismatch is different from a server not responding or the other dozens of failures that can happen while marking an HTTP request (where urllib3 does not log an error).

In my case the "error" is reported to Sentry but there is nothing I can do as a client to fix it, which is usually a pretty good indicator that the log level should be changed to something else. If it was up to me I would change it to an `info` level.

https://github.com/urllib3/urllib3/blob/2c055b4a60affa3529660b928fff375fde3993f7/src/urllib3/connection.py#L428-L440


Discussion:
I agree that logging this error is probably not necessary especially because we raise an exception right afterwards. Would be interesting to dig up the commit where this line was added and look at the reasoning.
So the reality is that not everyone is using urllib3 directly and `_peer_cert` isn't always accessible to users. Having a log message that provides details can help users debug this without having to jump through hoops, modify random code they don't own, etc. The log level, I'm fine with changing, but also it seems like this is only a problem because the sentry client is intercepting logs which has other ramifications (i.e., it's probably bypassing any kind of log-scrubbing and potentially leaking any data that hasn't yet been sanitized).
Should we be doing more `log.X()` calls? Connections can fail in so many different areas, do you have an insight into why is this one different than the rest?
So I think this was added by SurveyMonkey / @msabramo because they were trying to pin down which server in a group was serving an invalid certificate. 

I'm also on the fence about logging done by libraries. In some cases they can be super helpful, in others, they can be incredibly unhelpful. I'm 99% certain that OpenStack silences all urllib3 logs by default because the connectionpools are _incredibly_ noisy otherwise.

I think applications should control what logs and when, but, with libraries architected the way Requests and urllib3 are architected, logging makes perfect sense to have so that a user can flip a switch when needed. I don't know where else to add logging since people only ever complain about it's presence and rarely complain about it's absence.
The main problem here is that the logging framework does not allow a final user to opt out of this specific error in a good way. I could:

- Disable the logger `urllib3.connection` or set it to `CRITICAL` but then I'd miss all other messages that could be important, now and in a future release of `urllib3`.
- Create a `logging.Filter` that parses this specific log record and silences it, very fragile as it could break any time the message is changed.

The only solutions I can think of would be:

- Lower this specific message to `WARNING`, it would still show up on most logging setups (by default levels of `WARNING` and more are enabled) while still allowing to opt out of it by configuring `urllib3.connection` to `ERROR`.
- Move all these nice-to-have-but-not-really-errors to their own named logger so that application code can easily silence them while keeping other valuable logs.

Resolving patch:
diff --git a/src/urllib3/connection.py b/src/urllib3/connection.py
--- a/src/urllib3/connection.py
+++ b/src/urllib3/connection.py
@@ -429,7 +429,7 @@ def _match_hostname(cert, asserted_hostname):
     try:
         match_hostname(cert, asserted_hostname)
     except CertificateError as e:
-        log.error(
+        log.warning(
             "Certificate did not match expected hostname: %s. " "Certificate: %s",
             asserted_hostname,
             cert,


Test patch:
diff --git a/test/test_connection.py b/test/test_connection.py
--- a/test/test_connection.py
+++ b/test/test_connection.py
@@ -32,7 +32,7 @@ def test_match_hostname_mismatch(self):
         cert = {"subjectAltName": [("DNS", "foo")]}
         asserted_hostname = "bar"
         try:
-            with mock.patch("urllib3.connection.log.error") as mock_log:
+            with mock.patch("urllib3.connection.log.warning") as mock_log:
                 _match_hostname(cert, asserted_hostname)
         except CertificateError as e:
             assert "hostname 'bar' doesn't match 'foo'" in str(e)