Skip to content

Commit 5010e54

Browse files
authored
Merge pull request #55 from taskbadger/sk/external-id
Record originating task ID as external_id
2 parents c35df8a + 4a4fc3f commit 5010e54

11 files changed

Lines changed: 260 additions & 18 deletions

taskbadger.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,11 @@ components:
694694
type: string
695695
description: Queue the task is from
696696
maxLength: 255
697+
external_id:
698+
type: string
699+
description: Identifier from the originating system (e.g. Celery task ID)
700+
for correlating with logs
701+
maxLength: 255
697702
status:
698703
allOf:
699704
- $ref: '#/components/schemas/StatusEnum'
@@ -794,6 +799,11 @@ components:
794799
type: string
795800
description: Queue the task is from
796801
maxLength: 255
802+
external_id:
803+
type: string
804+
description: Identifier from the originating system (e.g. Celery task ID)
805+
for correlating with logs
806+
maxLength: 255
797807
status:
798808
allOf:
799809
- $ref: '#/components/schemas/StatusEnum'
@@ -893,6 +903,11 @@ components:
893903
type: string
894904
description: Queue the task is from
895905
maxLength: 255
906+
external_id:
907+
type: string
908+
description: Identifier from the originating system (e.g. Celery task ID)
909+
for correlating with logs
910+
maxLength: 255
896911
status:
897912
allOf:
898913
- $ref: '#/components/schemas/StatusEnum'

taskbadger/celery.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
137137
ctask = celery.current_app.tasks.get(sender)
138138

139139
# get kwargs from the task class (set via decorator)
140-
kwargs = getattr(ctask, TB_KWARGS_ARG, {})
140+
# copy: the raw attr is shared across invocations, and we mutate per-publish
141+
# values (external_id, status, name) into it below
142+
kwargs = dict(getattr(ctask, TB_KWARGS_ARG, {}))
141143
for attr in dir(ctask):
142144
if attr.startswith(KWARG_PREFIX) and attr not in IGNORE_ARGS:
143145
kwargs[attr.removeprefix(KWARG_PREFIX)] = getattr(ctask, attr)
@@ -147,6 +149,7 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
147149
kwargs["status"] = StatusEnum.PENDING
148150
if routing_key and "queue" not in kwargs:
149151
kwargs["queue"] = routing_key
152+
kwargs.setdefault("external_id", headers["id"])
150153
name = kwargs.pop("name", headers["task"])
151154

152155
global_record_task_args = celery_system and celery_system.record_task_args
@@ -247,7 +250,8 @@ def _maybe_create_task(signal_sender):
247250

248251
delivery_info = getattr(signal_sender.request, "delivery_info", None) or {}
249252
queue = delivery_info.get("routing_key")
250-
task = create_task_safe(task_name, status=StatusEnum.PENDING, data=data, queue=queue)
253+
external_id = signal_sender.request.id
254+
task = create_task_safe(task_name, status=StatusEnum.PENDING, data=data, queue=queue, external_id=external_id)
251255
if task:
252256
# Store the task ID in the request so _update_task can find it
253257
signal_sender.request.update({TB_TASK_ID: task.id})

taskbadger/internal/models/patched_task_request.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class PatchedTaskRequest:
2424
Attributes:
2525
name (str | Unset): Name of the task
2626
queue (str | Unset): Queue the task is from
27+
external_id (str | Unset): Identifier from the originating system (e.g. Celery task ID) for correlating with
28+
logs
2729
status (StatusEnum | Unset): * `pending` - pending
2830
* `pre_processing` - pre_processing
2931
* `processing` - processing
@@ -50,6 +52,7 @@ class PatchedTaskRequest:
5052

5153
name: str | Unset = UNSET
5254
queue: str | Unset = UNSET
55+
external_id: str | Unset = UNSET
5356
status: StatusEnum | Unset = StatusEnum.PENDING
5457
value: int | None | Unset = UNSET
5558
value_max: int | Unset = UNSET
@@ -69,6 +72,8 @@ def to_dict(self) -> dict[str, Any]:
6972

7073
queue = self.queue
7174

75+
external_id = self.external_id
76+
7277
status: str | Unset = UNSET
7378
if not isinstance(self.status, Unset):
7479
status = self.status.value
@@ -128,6 +133,8 @@ def to_dict(self) -> dict[str, Any]:
128133
field_dict["name"] = name
129134
if queue is not UNSET:
130135
field_dict["queue"] = queue
136+
if external_id is not UNSET:
137+
field_dict["external_id"] = external_id
131138
if status is not UNSET:
132139
field_dict["status"] = status
133140
if value is not UNSET:
@@ -160,6 +167,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
160167

161168
queue = d.pop("queue", UNSET)
162169

170+
external_id = d.pop("external_id", UNSET)
171+
163172
_status = d.pop("status", UNSET)
164173
status: StatusEnum | Unset
165174
if isinstance(_status, Unset):
@@ -251,6 +260,7 @@ def _parse_stale_timeout(data: object) -> int | None | Unset:
251260
patched_task_request = cls(
252261
name=name,
253262
queue=queue,
263+
external_id=external_id,
254264
status=status,
255265
value=value,
256266
value_max=value_max,

taskbadger/internal/models/task.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class Task:
3232
url (str):
3333
public_url (str):
3434
queue (str | Unset): Queue the task is from
35+
external_id (str | Unset): Identifier from the originating system (e.g. Celery task ID) for correlating with
36+
logs
3537
status (StatusEnum | Unset): * `pending` - pending
3638
* `pre_processing` - pre_processing
3739
* `processing` - processing
@@ -66,6 +68,7 @@ class Task:
6668
url: str
6769
public_url: str
6870
queue: str | Unset = UNSET
71+
external_id: str | Unset = UNSET
6972
status: StatusEnum | Unset = StatusEnum.PENDING
7073
value: int | None | Unset = UNSET
7174
value_max: int | Unset = UNSET
@@ -102,6 +105,8 @@ def to_dict(self) -> dict[str, Any]:
102105

103106
queue = self.queue
104107

108+
external_id = self.external_id
109+
105110
status: str | Unset = UNSET
106111
if not isinstance(self.status, Unset):
107112
status = self.status.value
@@ -171,6 +176,8 @@ def to_dict(self) -> dict[str, Any]:
171176
)
172177
if queue is not UNSET:
173178
field_dict["queue"] = queue
179+
if external_id is not UNSET:
180+
field_dict["external_id"] = external_id
174181
if status is not UNSET:
175182
field_dict["status"] = status
176183
if value is not UNSET:
@@ -224,6 +231,8 @@ def _parse_value_percent(data: object) -> int | None:
224231

225232
queue = d.pop("queue", UNSET)
226233

234+
external_id = d.pop("external_id", UNSET)
235+
227236
_status = d.pop("status", UNSET)
228237
status: StatusEnum | Unset
229238
if isinstance(_status, Unset):
@@ -323,6 +332,7 @@ def _parse_stale_timeout(data: object) -> int | None | Unset:
323332
url=url,
324333
public_url=public_url,
325334
queue=queue,
335+
external_id=external_id,
326336
status=status,
327337
value=value,
328338
value_max=value_max,

taskbadger/internal/models/task_request.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class TaskRequest:
2424
Attributes:
2525
name (str): Name of the task
2626
queue (str | Unset): Queue the task is from
27+
external_id (str | Unset): Identifier from the originating system (e.g. Celery task ID) for correlating with
28+
logs
2729
status (StatusEnum | Unset): * `pending` - pending
2830
* `pre_processing` - pre_processing
2931
* `processing` - processing
@@ -50,6 +52,7 @@ class TaskRequest:
5052

5153
name: str
5254
queue: str | Unset = UNSET
55+
external_id: str | Unset = UNSET
5356
status: StatusEnum | Unset = StatusEnum.PENDING
5457
value: int | None | Unset = UNSET
5558
value_max: int | Unset = UNSET
@@ -69,6 +72,8 @@ def to_dict(self) -> dict[str, Any]:
6972

7073
queue = self.queue
7174

75+
external_id = self.external_id
76+
7277
status: str | Unset = UNSET
7378
if not isinstance(self.status, Unset):
7479
status = self.status.value
@@ -130,6 +135,8 @@ def to_dict(self) -> dict[str, Any]:
130135
)
131136
if queue is not UNSET:
132137
field_dict["queue"] = queue
138+
if external_id is not UNSET:
139+
field_dict["external_id"] = external_id
133140
if status is not UNSET:
134141
field_dict["status"] = status
135142
if value is not UNSET:
@@ -162,6 +169,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
162169

163170
queue = d.pop("queue", UNSET)
164171

172+
external_id = d.pop("external_id", UNSET)
173+
165174
_status = d.pop("status", UNSET)
166175
status: StatusEnum | Unset
167176
if isinstance(_status, Unset):
@@ -253,6 +262,7 @@ def _parse_stale_timeout(data: object) -> int | None | Unset:
253262
task_request = cls(
254263
name=name,
255264
queue=queue,
265+
external_id=external_id,
256266
status=status,
257267
value=value,
258268
value_max=value_max,

taskbadger/procrastinate.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,16 @@ def _wrap_defer(task):
149149
@functools.wraps(original_defer)
150150
def defer(**kwargs):
151151
kwargs = _maybe_create_pending(task, kwargs)
152-
return original_defer(**kwargs)
152+
job_id = original_defer(**kwargs)
153+
_record_external_id(kwargs, job_id)
154+
return job_id
153155

154156
@functools.wraps(original_defer_async)
155157
async def defer_async(**kwargs):
156158
kwargs = _maybe_create_pending(task, kwargs)
157-
return await original_defer_async(**kwargs)
159+
job_id = await original_defer_async(**kwargs)
160+
_record_external_id(kwargs, job_id)
161+
return job_id
158162

159163
task.defer = defer
160164
task.defer_async = defer_async
@@ -214,6 +218,18 @@ def _maybe_create_pending(task, kwargs):
214218
return new_kwargs
215219

216220

221+
def _record_external_id(kwargs, job_id):
222+
"""Record the Procrastinate job id as the TaskBadger task's ``external_id``.
223+
224+
``defer`` only returns the DB-assigned job id once the job is enqueued, so this
225+
runs as a follow-up update after both ids are known. No-op if the defer wasn't
226+
tracked (no injected id) or no job id came back."""
227+
tb_id = kwargs.get(TB_TASK_ID_KWARG)
228+
if tb_id is None or job_id is None:
229+
return
230+
update_task_safe(tb_id, external_id=str(job_id))
231+
232+
217233
def _serialize_kwargs(kwargs):
218234
"""Return a JSON-roundtrippable copy of the defer kwargs.
219235
@@ -296,14 +312,19 @@ def _patch_job_manager(app, system):
296312
@functools.wraps(original)
297313
async def patched(*, job, periodic_id, defer_timestamp):
298314
task = app.tasks.get(job.task_name)
315+
tb_id = None
299316
if task is not None:
300317
tb_task = _create_pending_task(task, job.task_kwargs, queue=job.queue)
301318
if tb_task is not None:
302319
new_kwargs = {**job.task_kwargs, TB_TASK_ID_KWARG: tb_task.id}
303320
job = job.evolve(task_kwargs=new_kwargs)
304-
return await jm._taskbadger_original_defer_periodic_job(
321+
tb_id = tb_task.id
322+
job_id = await jm._taskbadger_original_defer_periodic_job(
305323
job=job, periodic_id=periodic_id, defer_timestamp=defer_timestamp
306324
)
325+
if tb_id is not None and job_id is not None:
326+
update_task_safe(tb_id, external_id=str(job_id))
327+
return job_id
307328

308329
jm.defer_periodic_job = patched
309330

taskbadger/sdk.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def create_task(
152152
monitor_id: str = None,
153153
tags: dict[str, str] = None,
154154
queue: str = None,
155+
external_id: str = None,
155156
) -> "Task":
156157
"""Create a Task.
157158
@@ -167,6 +168,7 @@ def create_task(
167168
monitor_id: ID of the monitor to associate this task with.
168169
tags: Dictionary of namespace -> value tags.
169170
queue: Name of the queue the task is from.
171+
external_id: Identifier from the originating system (e.g. Celery task ID) for correlating with logs.
170172
171173
Returns:
172174
Task: The created Task object.
@@ -177,6 +179,8 @@ def create_task(
177179
}
178180
if queue is not None:
179181
task_dict["queue"] = queue
182+
if external_id is not None:
183+
task_dict["external_id"] = external_id
180184
if value is not None:
181185
task_dict["value"] = value
182186
if value_max is not None:
@@ -222,6 +226,7 @@ def update_task(
222226
actions: list[Action] = None,
223227
tags: dict[str, str] = None,
224228
queue: str = None,
229+
external_id: str = None,
225230
) -> "Task":
226231
"""Update a task.
227232
Requires only the task ID and fields to update.
@@ -238,6 +243,7 @@ def update_task(
238243
actions: Task actions. **Deprecated:** use project-level actions instead.
239244
tags: Dictionary of namespace -> value tags.
240245
queue: Name of the queue the task is from.
246+
external_id: Identifier from the originating system (e.g. Celery task ID) for correlating with logs.
241247
242248
Returns:
243249
Task: The updated Task object.
@@ -250,6 +256,7 @@ def update_task(
250256
max_runtime = _none_to_unset(max_runtime)
251257
stale_timeout = _none_to_unset(stale_timeout)
252258
queue = _none_to_unset(queue)
259+
external_id = _none_to_unset(external_id)
253260

254261
data = data or UNSET
255262
body = PatchedTaskRequest(
@@ -261,6 +268,7 @@ def update_task(
261268
max_runtime=max_runtime,
262269
stale_timeout=stale_timeout,
263270
queue=queue,
271+
external_id=external_id,
264272
)
265273
if actions:
266274
_warn_actions_deprecated()
@@ -335,6 +343,7 @@ def create(
335343
monitor_id: str = None,
336344
tags: dict[str, str] = None,
337345
queue: str = None,
346+
external_id: str = None,
338347
) -> "Task":
339348
"""Create a new task
340349
@@ -352,6 +361,7 @@ def create(
352361
monitor_id=monitor_id,
353362
tags=tags,
354363
queue=queue,
364+
external_id=external_id,
355365
)
356366

357367
def __init__(self, task):
@@ -437,6 +447,7 @@ def update(
437447
actions: list[Action] = None,
438448
tags: dict[str, str] = None,
439449
queue: str = None,
450+
external_id: str = None,
440451
data_merge_strategy: Any = None,
441452
):
442453
"""Generic update method used to update any of the task fields.
@@ -465,6 +476,7 @@ def update(
465476
actions=actions,
466477
tags=tags,
467478
queue=queue,
479+
external_id=external_id,
468480
)
469481
self._task = task._task
470482

0 commit comments

Comments
 (0)