Skip to content

Commit 073fc41

Browse files
authored
Merge pull request #130 from pricingassistant/traceback_histories
Traceback histories
2 parents 9282528 + 54ec966 commit 073fc41

7 files changed

Lines changed: 126 additions & 10 deletions

File tree

mrq/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ def get_config(
419419
from_args[k] = v
420420

421421
# If we were given another config file, use it
422+
422423
if file_path is not None:
423424
config_file = file_path
424425
elif from_args.get("config"):

mrq/dashboard/app.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,27 @@ def api_job_result(job_id):
286286
@app.route('/api/job/<job_id>/traceback')
287287
@requires_auth
288288
def api_job_traceback(job_id):
289-
collection = connections.mongodb_jobs.mrq_jobs
289+
collection = connections.mongodb_jobs.mrq_jobss
290+
if get_current_config().get("save_traceback_history"):
291+
292+
field_sent = "traceback_history"
293+
else:
294+
field_sent = "traceback"
295+
290296
job_data = collection.find_one(
291-
{"_id": ObjectId(job_id)}, projection=["traceback"])
297+
{"_id": ObjectId(job_id)}, projection=[field_sent])
292298

293299
if not job_data:
294-
job_data = {}
300+
# If a job has no traceback history, we fallback onto traceback
301+
if field_sent == "traceback_history":
302+
field_sent = "traceback"
303+
job_data = collection.find_one(
304+
{"_id": ObjectId(job_id)}, projection=[field_sent])
305+
if not job_data:
306+
job_data = {}
295307

296308
return jsonify({
297-
"traceback": job_data.get("traceback", "No exception raised")
309+
field_sent: job_data.get(field_sent, "No exception raised")
298310
})
299311

300312

mrq/dashboard/static/js/views/jobs.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,14 @@ define(["jquery", "underscore", "views/generic/datatablepage", "models"],functio
5656
$.ajax("/api/job/"+jobId+"/traceback", {
5757
"type": "GET",
5858
"success": function(data) {
59-
var stack = self.format_traceback(data["traceback"]);
60-
self.$(".js-jobs-modal .js-jobs-modal-content").html(stack);
59+
if (data["traceback"]) {
60+
var stack = self.format_traceback(data["traceback"]);
61+
self.$(".js-jobs-modal .js-jobs-modal-content").html(stack);
62+
}
63+
else {
64+
var stack = self.format_traceback_history(data["traceback_history"]);
65+
self.$(".js-jobs-modal .js-jobs-modal-content").html(stack);
66+
}
6167
self.$(".js-jobs-modal h4").html("Stack Trace");
6268
self.$(".js-jobs-modal").modal({});
6369
},
@@ -129,7 +135,21 @@ define(["jquery", "underscore", "views/generic/datatablepage", "models"],functio
129135

130136
return stack;
131137
},
132-
138+
format_traceback_history: function(stacks) {
139+
var self = this;
140+
var full_history = "";
141+
_.each(stacks, function(stack) {
142+
full_history += "<br/><b>" + stack["date"] + "</b><br/>";
143+
if (stack["original_traceback"]) {
144+
full_history += "<b>Original trace</b>";
145+
full_history += self.format_traceback(stack["original_traceback"] || "");
146+
}
147+
full_history += "<b>Trace</b>" + "<br/>";
148+
full_history += self.format_traceback(stack["traceback"] || "");
149+
full_history += "---------------------------------------------------------------------" + "<br/>";
150+
});
151+
return full_history;
152+
},
133153
row_jobaction:function(evt) {
134154
evt.preventDefault();
135155
evt.stopPropagation();

mrq/job.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def insert(cls, jobs_data, queue=None, statuses_no_storage=None, return_jobs=Tru
203203

204204
def _attach_original_exception(self, exc):
205205
""" Often, a retry will be raised inside an "except" block.
206-
This Keep track of the first exception for debugging purposes. """
206+
This Keep track of the first exception for debugging purposes """
207207

208208
original_exception = sys.exc_info()
209209
if original_exception[0] is not None:
@@ -350,6 +350,27 @@ def save_retry(self, retry_exc):
350350

351351
self._save_status("retry", updates, exception=True)
352352

353+
def _save_traceback_history(self, status, trace, job_exc):
354+
""" Create traceback history or add a new traceback to history. """
355+
failure_date = datetime.datetime.utcnow()
356+
357+
new_history = {
358+
"date": failure_date,
359+
"status": status,
360+
"exceptiontype": job_exc.__name__
361+
}
362+
363+
traces = trace.split("---- Original exception: -----")
364+
if len(traces) > 1:
365+
new_history["original_traceback"] = traces[1]
366+
worker = context.get_current_worker()
367+
if worker:
368+
new_history["worker"] = worker.id
369+
new_history["traceback"] = traces[0]
370+
self.collection.update({
371+
"_id": self.id
372+
}, {"$push": {"traceback_history": new_history}})
373+
353374
def save_success(self, result=None):
354375

355376
dateexpires = datetime.datetime.utcnow() + datetime.timedelta(seconds=self.result_ttl)
@@ -413,7 +434,12 @@ def _save_status(self, status, updates=None, exception=False, w=None, j=None):
413434
trace = traceback.format_exc()
414435
context.log.error(trace)
415436
db_updates["traceback"] = trace
416-
db_updates["exceptiontype"] = sys.exc_info()[0].__name__
437+
exc = sys.exc_info()[0]
438+
db_updates["exceptiontype"] = exc.__name__
439+
440+
if context.get_current_config().get("save_traceback_history"):
441+
442+
self._save_traceback_history(status, trace, exc)
417443

418444
# In the most common case, we allow an optimization on Mongo writes
419445
if status == "success":
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SAVE_TRACEBACK_HISTORY = True

tests/tasks/general.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
class Add(Task):
1313

1414
def run(self, params):
15-
1615
log.info("adding", params)
1716
res = params.get("a", 0) + params.get("b", 0)
1817

@@ -86,6 +85,30 @@ def run(self, params):
8685
raise Exception("Should not be reached")
8786

8887

88+
class InRetryException(BaseException):
89+
pass
90+
91+
92+
class RetryOnFailed(Task):
93+
94+
def run(self, params):
95+
96+
log.info("Retrying in %s on %s" %
97+
(params.get("delay"), params.get("queue")))
98+
99+
connections.mongodb_jobs.tests_inserts.insert(params)
100+
try:
101+
raise InRetryException
102+
except InRetryException:
103+
retry_current_job(
104+
queue=params.get("queue"),
105+
delay=params.get("delay"),
106+
max_retries=params.get("max_retries")
107+
)
108+
109+
raise Exception("Should not be reached")
110+
111+
89112
class WaitForFlag(Task):
90113

91114
def run(self, params):

tests/test_retry.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,36 @@ def test_retry_max_retries_zero(worker):
159159

160160
job = Job(job_id).fetch()
161161
assert job.data["status"] == "maxretries"
162+
163+
164+
def test_retry_traceback_history(worker):
165+
166+
worker.start(flags="--config tests/fixtures/config-tracebackhistory.py")
167+
# delay = 0 should requeue right away.
168+
169+
worker.send_task(
170+
"tests.tasks.general.Retry", {"queue": "noexec", "delay": 60}, block=True, accept_statuses=["retry"]
171+
)
172+
173+
job = worker.mongodb_jobs.mrq_jobs.find()[0]
174+
175+
assert len(job["traceback_history"]) == 1
176+
assert not job["traceback_history"][0].get("original_traceback")
177+
178+
worker.send_task(
179+
"tests.tasks.general.RetryOnFailed", {"queue": "default", "delay": 1}, block=True, accept_statuses=["retry"]
180+
)
181+
182+
job = worker.mongodb_jobs.mrq_jobs.find({
183+
"path": "tests.tasks.general.RetryOnFailed"})[0]
184+
185+
assert len(job["traceback_history"]) == 1
186+
assert "InRetryException" in job["traceback_history"][0].get("original_traceback")
187+
time.sleep(2)
188+
worker.send_task("mrq.basetasks.cleaning.RequeueRetryJobs", {}, block=True)
189+
time.sleep(2)
190+
job = worker.mongodb_jobs.mrq_jobs.find({
191+
"path": "tests.tasks.general.RetryOnFailed"})[0]
192+
193+
assert len(job["traceback_history"]) == 2
194+
assert job["traceback_history"][0]["date"] < job["traceback_history"][1]["date"]

0 commit comments

Comments
 (0)