Repository: celery/celery

Issue:
Celery not using kwargsrepr when creating task message
## Checklist

- [x] I have included the output of ``celery -A proj report`` in the issue.
      (if you are not able to do this, then at least specify the Celery
       version affected).
- [x] I have verified that the issue exists against the `master` branch of Celery.


```
software -> celery:4.1.0 (latentcall) kombu:4.1.0 py:3.6.1
            billiard:3.5.0.3 py-amqp:2.2.1
platform -> system:Linux arch:64bit imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:amqp results:redis://redis:6379/
```

## Steps to reproduce

1. Ensure that you are using task protocol v2 (this is the default)
1. Call a task with:
```
my_task.apply_async(kwargs={'foo': 'bar'}, kwargsrepr='')
```

## Expected behavior

The `kwargsrepr` task header should contain the empty string `''`

## Actual behavior

The `kwargsrepr` header uses the automatically generated `safe_repr` of the kwargs:
```
kwargsrepr: {'foo': 'bar'}
```

This also shows up in the worker logging if the level is set to DEBUG:
```
TaskPool: Apply <function _trace_task_ret at 0x7f52e2b887b8> (args:('my_app.my_task', 'dfc0d5b7-dcef-4ee4-aa9f-a3d3581345a0', {'lang': 'py', 'task': 'my_app.my_task', 'id': 'dfc0d5b7-dcef-4ee4-aa9f-a3d3581345a0', 'eta': None, 'expires': '2017-09-12T15:33:38.453707+00:00', 'group': None, 'retries': 0, 'timelimit': [None, None], 'root_id': 'dfc0d5b7-dcef-4ee4-aa9f-a3d3581345a0', 'parent_id': None, 'argsrepr': '()', 'kwargsrepr': "{'foo': 'bar'}", 'origin': 'gen8@d8d88575bcec', 'reply_to': 'b836236d-0ff5-31b7-b0fa-e4948dccfcf4', 'correlation_id': 'dfc0d5b7-dcef-4ee4-aa9f-a3d3581345a0', 'delivery_info': {'exchange': '', 'routing_key': 'my_app', 'priority': None, 'redelivered': False}}, '[[], {}, {"callbacks": null, "errbacks": null, "chain": null, "chord": null}]', 'application/json', 'utf-8') kwargs:{})
```

It appears this can be traced to https://github.com/celery/celery/blob/master/celery/app/base.py#L735 - no code actually passes the `kwargsrepr` (or `argsrepr` for that matter) to the task creating method.

I will submit a PR shortly that addresses this by passing the arguments if the task protocol is v2 or higher. I looked through the unit tests, and it appears that the task-focused unit tests all use task protocol v1, meaning this isn't something that could be tested in the existing test cases.


Discussion:

Resolving patch:
diff --git a/celery/app/amqp.py b/celery/app/amqp.py
--- a/celery/app/amqp.py
+++ b/celery/app/amqp.py
@@ -398,7 +398,8 @@ def as_task_v1(self, task_id, name, args=None, kwargs=None,
                    chord=None, callbacks=None, errbacks=None, reply_to=None,
                    time_limit=None, soft_time_limit=None,
                    create_sent_event=False, root_id=None, parent_id=None,
-                   shadow=None, now=None, timezone=None):
+                   shadow=None, now=None, timezone=None,
+                   **compat_kwargs):
         args = args or ()
         kwargs = kwargs or {}
         utc = self.utc
diff --git a/celery/app/base.py b/celery/app/base.py
--- a/celery/app/base.py
+++ b/celery/app/base.py
@@ -739,6 +739,8 @@ def send_task(self, name, args=None, kwargs=None, countdown=None,
             reply_to or self.oid, time_limit, soft_time_limit,
             self.conf.task_send_sent_event,
             root_id, parent_id, shadow, chain,
+            argsrepr=options.get('argsrepr'),
+            kwargsrepr=options.get('kwargsrepr'),
         )
 
         if connection:


Test patch:
diff --git a/t/unit/tasks/test_tasks.py b/t/unit/tasks/test_tasks.py
--- a/t/unit/tasks/test_tasks.py
+++ b/t/unit/tasks/test_tasks.py
@@ -41,7 +41,6 @@ def apply_async(self, *args, **kwargs):
 class TasksCase:
 
     def setup(self):
-        self.app.conf.task_protocol = 1  # XXX  Still using proto1
         self.mytask = self.app.task(shared=False)(return_True)
 
         @self.app.task(bind=True, count=0, shared=False)
@@ -412,20 +411,28 @@ def test_AsyncResult(self):
 
     def assert_next_task_data_equal(self, consumer, presult, task_name,
                                     test_eta=False, test_expires=False,
-                                    **kwargs):
+                                    properties=None, headers=None, **kwargs):
         next_task = consumer.queues[0].get(accept=['pickle', 'json'])
-        task_data = next_task.decode()
-        assert task_data['id'] == presult.id
-        assert task_data['task'] == task_name
-        task_kwargs = task_data.get('kwargs', {})
+        task_properties = next_task.properties
+        task_headers = next_task.headers
+        task_body = next_task.decode()
+        task_args, task_kwargs, embed = task_body
+        assert task_headers['id'] == presult.id
+        assert task_headers['task'] == task_name
         if test_eta:
-            assert isinstance(task_data.get('eta'), string_t)
-            to_datetime = parse_iso8601(task_data.get('eta'))
+            assert isinstance(task_headers.get('eta'), string_t)
+            to_datetime = parse_iso8601(task_headers.get('eta'))
             assert isinstance(to_datetime, datetime)
         if test_expires:
-            assert isinstance(task_data.get('expires'), string_t)
-            to_datetime = parse_iso8601(task_data.get('expires'))
+            assert isinstance(task_headers.get('expires'), string_t)
+            to_datetime = parse_iso8601(task_headers.get('expires'))
             assert isinstance(to_datetime, datetime)
+        properties = properties or {}
+        for arg_name, arg_value in items(properties):
+            assert task_properties.get(arg_name) == arg_value
+        headers = headers or {}
+        for arg_name, arg_value in items(headers):
+            assert task_headers.get(arg_name) == arg_value
         for arg_name, arg_value in items(kwargs):
             assert task_kwargs.get(arg_name) == arg_value
 
@@ -500,6 +507,27 @@ def test_regular_task(self):
                 name='George Costanza', test_eta=True, test_expires=True,
             )
 
+            # Default argsrepr/kwargsrepr behavior
+            presult2 = self.mytask.apply_async(
+                args=('spam',), kwargs={'name': 'Jerry Seinfeld'}
+            )
+            self.assert_next_task_data_equal(
+                consumer, presult2, self.mytask.name,
+                headers={'argsrepr': "('spam',)",
+                         'kwargsrepr': "{'name': 'Jerry Seinfeld'}"},
+            )
+
+            # With argsrepr/kwargsrepr
+            presult2 = self.mytask.apply_async(
+                args=('secret',), argsrepr="'***'",
+                kwargs={'password': 'foo'}, kwargsrepr="{'password': '***'}",
+            )
+            self.assert_next_task_data_equal(
+                consumer, presult2, self.mytask.name,
+                headers={'argsrepr': "'***'",
+                         'kwargsrepr': "{'password': '***'}"},
+            )
+
             # Discarding all tasks.
             consumer.purge()
             self.mytask.apply_async()