Repository: celery/celery

Issue:
UnicodeDecodeError when storing exception result in backend 
## Output of ``celery -A proj report``

```
 -------------- celery@kubuntu-work v4.0.2 (latentcall)
---- **** ----- 
--- * ***  * -- Linux-3.19.0-15-generic-x86_64-with-Ubuntu-15.04-vivid 2017-02-22 09:58:51
-- * - **** --- 
- ** ---------- [config]
- ** ---------- .> app:         __main__:0x7fd477a8cd10
- ** ---------- .> transport:   redis://localhost:6379/0
- ** ---------- .> results:     redis://localhost/1
- *** --- * --- .> concurrency: 2 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> report           exchange=report(direct) key=report

[2017-02-22 09:59:14,653: ERROR/PoolWorker-2] Task proj.test[ca8aa518-dd4f-4697-a7f4-b90a11036fd2] raised unexpected: UnicodeEncodeError('ascii', u'\U0001f41f \U0001f421 \U0001f42c', 0, 1, 'ordinal not in range(128)')
Traceback (most recent call last):
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/app/trace.py", line 381, in trace_task
    I, R, state, retval = on_error(task_request, exc, uuid)
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/app/trace.py", line 323, in on_error
    task, request, eager=eager, call_errbacks=call_errbacks,
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/app/trace.py", line 157, in handle_error_state
    call_errbacks=call_errbacks)
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/app/trace.py", line 202, in handle_failure
    call_errbacks=call_errbacks,
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/backends/base.py", line 163, in mark_as_failure
    traceback=traceback, request=request)
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/backends/base.py", line 308, in store_result
    result = self.encode_result(result, state)
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/backends/base.py", line 298, in encode_result
    return self.prepare_exception(result)
  File "/home/yangfei/work/code/pyvenv/venv/local/lib/python2.7/site-packages/celery/backends/base.py", line 241, in prepare_exception
    return {'exc_type': type(exc).__name__, 'exc_message': str(exc)}
UnicodeEncodeError: 'ascii' codec can't encode character u'\U0001f41f' in position 0: ordinal not in range(128)
```

## Steps to reproduce
1. create proj.py, celery app starts with a backend. ``test`` task raises an Exception with a unicode.
```
# coding: utf-8  
from celery import Celery  
                                                                  
app = Celery(broker='redis://localhost/0', backend='redis://localhost/1') 
app.conf.task_queue_default = 'report'

@app.task
def test(msg):
    raise Exception(u'🐟 🐡 🐬')
```
2. run celery worker with ``celery -A proj worker -Q report
3. dispatch a test task
```
from proj import test 
test.apply_async(args=['hello'], queue='report')
```

## Expected behavior

save task result (with exception detail from task) in backend successfully.

## Actual behavior

backend stores the exception from celery itself.





Discussion:

Resolving patch:
diff --git a/celery/backends/base.py b/celery/backends/base.py
--- a/celery/backends/base.py
+++ b/celery/backends/base.py
@@ -29,7 +29,7 @@
 from celery.exceptions import (
     ChordError, TimeoutError, TaskRevokedError, ImproperlyConfigured,
 )
-from celery.five import items
+from celery.five import items, string
 from celery.result import (
     GroupResult, ResultBase, allow_join_result, result_from_tuple,
 )
@@ -237,7 +237,7 @@ def prepare_exception(self, exc, serializer=None):
         serializer = self.serializer if serializer is None else serializer
         if serializer in EXCEPTION_ABLE_CODECS:
             return get_pickleable_exception(exc)
-        return {'exc_type': type(exc).__name__, 'exc_message': str(exc)}
+        return {'exc_type': type(exc).__name__, 'exc_message': string(exc)}
 
     def exception_to_python(self, exc):
         """Convert serialized exception to Python exception."""


Test patch:
diff --git a/t/unit/backends/test_base.py b/t/unit/backends/test_base.py
--- a/t/unit/backends/test_base.py
+++ b/t/unit/backends/test_base.py
@@ -139,6 +139,11 @@ def test_regular(self):
         y = self.b.exception_to_python(x)
         assert isinstance(y, KeyError)
 
+    def test_unicode_message(self):
+        message = u'\u03ac'
+        x = self.b.prepare_exception(Exception(message))
+        assert x == {'exc_message': message, 'exc_type': 'Exception'}
+
 
 class KVBackend(KeyValueStoreBackend):
     mget_returns_dict = False