Repository: getsentry/sentry

Issue:
Unicode error in logging module
## Reporting Bugs

Sentry version: 8.19
Installation: 8.19 onpremise Docker images with docker-compose
Stacktrace:

```
web_1        | Traceback (most recent call last):
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 345, in authenticate
web_1        |     self._authenticate_user_dn(password)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 455, in _authenticate_user_dn
web_1        |     if self.dn is None:
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 419, in _get_user_dn
web_1        |     self._load_user_dn()
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 488, in _load_user_dn
web_1        |     self.settings.GROUP_CACHE_TIMEOUT
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 1045, in cache_get_or_set
web_1        |     value = default() if callable(default) else default
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 511, in _search_for_user_dn
web_1        |     results = search.execute(self.connection, {'user': self._username})
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/config.py", line 178, in execute
web_1        |     return self._process_results(results)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/config.py", line 252, in _process_results
web_1        |     "; ".join(result_dns)))
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1147, in debug
web_1        |     Log 'msg % args' with severity 'DEBUG'.
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1155, in debug
web_1        |     self._log(DEBUG, msg, args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1286, in _log
web_1        |     self.handle(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1296, in handle
web_1        |     self.callHandlers(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1336, in callHandlers
web_1        |     hdlr.handle(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 759, in handle
web_1        |     self.emit(record)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/logging/handlers.py", line 85, in emit
web_1        |     logger.log(**kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/stdlib.py", line 102, in log
web_1        |     return self._proxy_to_logger(_LEVEL_TO_NAME[level], event, *args, **kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/stdlib.py", line 119, in _proxy_to_logger
web_1        |     **event_kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_base.py", line 177, in _proxy_to_logger
web_1        |     return getattr(self._logger, method_name)(*args, **kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_loggers.py", line 75, in msg
web_1        |     until_not_interrupted(self._write, message + '\n')
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_utils.py", line 24, in until_not_interrupted
web_1        |     return f(*args, **kw)
web_1        | UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 167: ordinal not in range(128)
web_1        | 14:32:47 [ERROR] django_auth_ldap: Caught Exception while authenticating example.user
web_1        | Traceback (most recent call last):
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 345, in authenticate
web_1        |     self._authenticate_user_dn(password)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 455, in _authenticate_user_dn
web_1        |     if self.dn is None:
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 419, in _get_user_dn
web_1        |     self._load_user_dn()
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 488, in _load_user_dn
web_1        |     self.settings.GROUP_CACHE_TIMEOUT
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 1045, in cache_get_or_set
web_1        |     value = default() if callable(default) else default
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 511, in _search_for_user_dn
web_1        |     results = search.execute(self.connection, {'user': self._username})
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/config.py", line 178, in execute
web_1        |     return self._process_results(results)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/config.py", line 252, in _process_results
web_1        |     "; ".join(result_dns)))
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1147, in debug
web_1        |     Log 'msg % args' with severity 'DEBUG'.
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1155, in debug
web_1        |     self._log(DEBUG, msg, args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1286, in _log
web_1        |     self.handle(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1296, in handle
web_1        |     self.callHandlers(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1336, in callHandlers
web_1        |     hdlr.handle(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 759, in handle
web_1        |     self.emit(record)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/logging/handlers.py", line 85, in emit
web_1        |     logger.log(**kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/stdlib.py", line 102, in log
web_1        |     return self._proxy_to_logger(_LEVEL_TO_NAME[level], event, *args, **kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/stdlib.py", line 119, in _proxy_to_logger
web_1        |     **event_kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_base.py", line 177, in _proxy_to_logger
web_1        |     return getattr(self._logger, method_name)(*args, **kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_loggers.py", line 75, in msg
web_1        |     until_not_interrupted(self._write, message + '\n')
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_utils.py", line 24, in until_not_interrupted
web_1        |     return f(*args, **kw)
web_1        | UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 167: ordinal not in range(128)
web_1        | 14:32:47 [ERROR] django_auth_ldap: Caught Exception while authenticating example.user
web_1        | Traceback (most recent call last):
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/core/handlers/base.py", line 112, in get_response
web_1        |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/views/generic/base.py", line 69, in view
web_1        |     return self.dispatch(request, *args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/utils/decorators.py", line 29, in _wrapper
web_1        |     return bound_func(*args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/utils/decorators.py", line 99, in _wrapped_view
web_1        |     response = view_func(request, *args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/utils/decorators.py", line 25, in bound_func
web_1        |     return func(self, *args2, **kwargs2)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/web/frontend/base.py", line 183, in dispatch
web_1        |     return self.handle(request, *args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/views/decorators/cache.py", line 52, in _wrapped_view_func
web_1        |     response = view_func(request, *args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/db/transaction.py", line 371, in inner
web_1        |     return func(*args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/web/frontend/auth_organization_login.py", line 124, in handle
web_1        |     response = self.handle_basic_auth(request, organization)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/web/frontend/auth_organization_login.py", line 63, in handle_basic_auth
web_1        |     elif login_form.is_valid():
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/forms/forms.py", line 129, in is_valid
web_1        |     return self.is_bound and not bool(self.errors)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/forms/forms.py", line 121, in errors
web_1        |     self.full_clean()
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/forms/forms.py", line 274, in full_clean
web_1        |     self._clean_form()
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/forms/forms.py", line 300, in _clean_form
web_1        |     self.cleaned_data = self.clean()
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/web/forms/accounts.py", line 151, in clean
web_1        |     self.user_cache = authenticate(username=username, password=password)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../django/contrib/auth/__init__.py", line 49, in authenticate
web_1        |     user = backend.authenticate(**credentials)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 172, in authenticate
web_1        |     user = ldap_user.authenticate(password)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 345, in authenticate
web_1        |     self._authenticate_user_dn(password)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 455, in _authenticate_user_dn
web_1        |     if self.dn is None:
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 419, in _get_user_dn
web_1        |     self._load_user_dn()
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 488, in _load_user_dn
web_1        |     self.settings.GROUP_CACHE_TIMEOUT
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 1045, in cache_get_or_set
web_1        |     value = default() if callable(default) else default
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 511, in _search_for_user_dn
web_1        |     results = search.execute(self.connection, {'user': self._username})
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/config.py", line 178, in execute
web_1        |     return self._process_results(results)
web_1        |   File "/usr/local/lib/python2.7/site-packages/django_auth_ldap/config.py", line 252, in _process_results
web_1        |     "; ".join(result_dns)))
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1147, in debug
web_1        |     Log 'msg % args' with severity 'DEBUG'.
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1155, in debug
web_1        |     self._log(DEBUG, msg, args, **kwargs)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1286, in _log
web_1        |     self.handle(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1296, in handle
web_1        |     self.callHandlers(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 1336, in callHandlers
web_1        |     hdlr.handle(record)
web_1        |   File "/usr/local/lib/python2.7/logging/__init__.py", line 759, in handle
web_1        |     self.emit(record)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/logging/handlers.py", line 85, in emit
web_1        |     logger.log(**kwargs)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/stdlib.py", line 102, in log
web_1        |     return self._proxy_to_logger(_LEVEL_TO_NAME[level], event, *args, **kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/stdlib.py", line 119, in _proxy_to_logger
web_1        |     **event_kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_base.py", line 177, in _proxy_to_logger
web_1        |     return getattr(self._logger, method_name)(*args, **kw)
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_loggers.py", line 75, in msg
web_1        |     until_not_interrupted(self._write, message + '\n')
web_1        |   File "/usr/local/lib/python2.7/site-packages/sentry/../structlog/_utils.py", line 24, in until_not_interrupted
web_1        |     return f(*args, **kw)
web_1        | UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 167: ordinal not in range(128)
web_1        | 14:32:47 [ERROR] django.request: Internal Server Error: /auth/login/example_organization/ (status_code=500 request=<WSGIRequest
```

Configuration that introduces the failures:

Using [`django-auth-ldap`](https://bitbucket.org/illocution/django-auth-ldap) with [`sentry-ldap-auth`](https://github.com/Banno/getsentry-ldap-auth) plugin that introduces unicode characters in log output when doing verbose authentication backend logging.

When the remote LDAP user has unicode fields in their account details or fields the logging output crashes. For example, the following `CN` record in LDAP causes a crash in the logging module.

```
CN=Exämple User,OU=users,DC=ad,DC=example,DC=org
```

To demonstrate the following logging was used with a LDAP record containing unicode characters:

```python
# This causes the server to crash when there are
# Unicode characters in the logging output
# and they are converted to ASCII in Sentry

LOGGING['overridable'] = ['sentry', 'django_auth_ldap']
LOGGING['loggers']['django_auth_ldap'] = {
    'handlers': ['console']
}

import logging
logger = logging.getLogger('django_auth_ldap')
logger.addHandler(logging.StreamHandler())
logger.setLevel('DEBUG')

print(sys.getfilesystemencoding())  # Outputs UTF-8
```

[I reported the error to `django-auth-ldap` forums](https://groups.google.com/forum/#!topic/django-auth-ldap/wo5i1OjSL6E), but backtracked the error to Sentry logging modules today.

I suspect that the Sentry logging module does not correctly cast / encode the loggable value.

One solution could to be to encode with the ignore errors flag :-)



Discussion:

Resolving patch:
diff --git a/src/sentry/runner/initializer.py b/src/sentry/runner/initializer.py
--- a/src/sentry/runner/initializer.py
+++ b/src/sentry/runner/initializer.py
@@ -198,6 +198,7 @@ def configure_structlog():
             structlog.stdlib.PositionalArgumentsFormatter(),
             structlog.processors.format_exc_info,
             structlog.processors.StackInfoRenderer(),
+            structlog.processors.UnicodeDecoder(),
         ]
     }
 


Test patch: