Skip to content

Commit 5d9ca0f

Browse files
committed
Add task-specific log capturing to task diagnostics mechanism
TASK_DIAGNOSTICS and X-Task-Diagnostics can now capture all logs for specific tasks, including DEBUG logs, regardless of whether DEBUG logs are otherwise enabled for the system logger. Because the logs are specific to the task, the logs are linear and not broken up by unrelated logs from other services/workers/tasks. Assisted By: Claude closes #7214
1 parent 9ab49fb commit 5d9ca0f

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

CHANGES/7214.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a "logs" option to the TASK_DIAGNOSTICS / X-Task-Diagnostics feature - dumps task-specific logs into a profile artifact.

docs/dev/learn/tasks/diagnostics.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ The following diagnostics are supported currently:
3535
- memray:
3636
Dumps a profile which can be processed with `memray`, which shows which lines and functions were
3737
responsible for the most allocations at the time of peak RSS of the process
38-
38+
- logs:
39+
Dumps all logs specific to the task, including DEBUG logs, regardless of whether DEBUG logging
40+
is otherwise enabled
3941

4042
## Memory Logging
4143

pulpcore/app/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,9 @@
374374
# lines and functions, at the time of peak RSS of the task process. This adds significant
375375
# runtime overhead to the task process, 20-40%. Tweaking code might be warranted for
376376
# some advanced settings.
377+
# * "logs" - Dumps the logs specific to each task. All logs are captured, including DEBUG logs.
377378
# NOTE: "memray" and "pyinstrument" require additional packages to be installed on the system.
378-
TASK_DIAGNOSTICS = [] # ["memory", "pyinstrument", "memray"]
379+
TASK_DIAGNOSTICS = [] # ["memory", "pyinstrument", "memray", "logs"]
379380

380381
ANALYTICS = True
381382

pulpcore/tasking/_util.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ def _execute_task_and_profile(task, profile_options):
121121
_execute_task = _pyinstrument_diagnostic_decorator(temp_dir, _execute_task)
122122
if "memray" in profile_options:
123123
_execute_task = _memray_diagnostic_decorator(temp_dir, _execute_task)
124+
if "logs" in profile_options:
125+
_execute_task = _logging_decorator(temp_dir, _execute_task)
124126

125127
_execute_task(task)
126128

@@ -218,6 +220,50 @@ def __memray_diagnostic_decorator(task):
218220
return __memray_diagnostic_decorator
219221

220222

223+
def _logging_decorator(temp_dir, func):
224+
def __logging_decorator(task):
225+
log_file_path = os.path.join(temp_dir, "task_logs.log")
226+
227+
# Create a file handler that captures all logging levels
228+
file_handler = logging.FileHandler(log_file_path, mode="w", encoding="utf-8")
229+
file_handler.setLevel(logging.NOTSET) # Capture all levels
230+
231+
# Create a formatter for consistent log formatting
232+
formatter = logging.Formatter(
233+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
234+
)
235+
file_handler.setFormatter(formatter)
236+
237+
# Get the root logger to capture all logs
238+
root_logger = logging.getLogger()
239+
original_level = root_logger.level
240+
241+
try:
242+
# Add the handler to the root logger
243+
root_logger.addHandler(file_handler)
244+
245+
# Execute the task
246+
func(task)
247+
finally:
248+
# Always remove the handler and restore original level
249+
root_logger.removeHandler(file_handler)
250+
file_handler.close()
251+
252+
# Save the log file as a ProfileArtifact
253+
artifact = Artifact.init_and_validate(log_file_path)
254+
try:
255+
# it's unlikely for a log file to be identical, but we retain the same check as the
256+
# other decorators
257+
artifact.save()
258+
except IntegrityError:
259+
artifact = Artifact.objects.get(sha256=artifact.sha256)
260+
261+
ProfileArtifact.objects.get_or_create(artifact=artifact, name="task_logs", task=task)
262+
_logger.info("Created task logging diagnostic data.")
263+
264+
return __logging_decorator
265+
266+
221267
def dispatch_scheduled_tasks():
222268
# Warning, dispatch_scheduled_tasks is not race condition free!
223269
now = timezone.now()

0 commit comments

Comments
 (0)