New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Job Log Model #1030
Job Log Model #1030
Changes from 25 commits
2d752eb
b3aa0b0
99676bd
0deff07
45dbaf4
31a72b4
121a62f
31b9f94
0bc4bbb
7f7720e
d48404a
7928e4a
7c72484
6fec78c
ad5788c
8e09e96
ed9e2aa
a4d9615
ed47263
d7fc021
7e5083e
332ba95
d0f0585
9ed41ca
0c0e032
eef30c0
721953e
4563c4a
4819cfe
6561bbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Job Log Entry | ||
|
||
As of Nautobot 1.2, log messages from jobs are now stored in the JobLogEntry model. This allows more performant querying of log messages and even allows viewing of logs while the job is still running. | ||
smk4664 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Records of this type store the following data: | ||
|
||
- A reference to the JobResult object that created the log. | ||
smk4664 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- Timestamps indicating when the log message was created. | ||
- The logging level of the log message. | ||
- The log message. | ||
- If provided, the string format of the logged object and it's absolute url. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,8 @@ | |
from django.test.client import RequestFactory | ||
from django.utils import timezone | ||
|
||
from nautobot.extras.choices import JobResultStatusChoices | ||
from nautobot.extras.models import JobResult | ||
from nautobot.extras.choices import LogLevelChoices, JobResultStatusChoices | ||
from nautobot.extras.models import JobLogEntry, JobResult | ||
from nautobot.extras.jobs import get_job, run_job | ||
from nautobot.utilities.utils import copy_safe_request | ||
|
||
|
@@ -74,23 +74,26 @@ def handle(self, *args, **options): | |
job_result = JobResult.objects.get(pk=job_result.pk) | ||
|
||
# Report on success/failure | ||
for test_name, attrs in job_result.data.items(): | ||
|
||
if test_name in ["total", "output"]: | ||
continue | ||
groups = set(JobLogEntry.objects.filter(job_result=job_result).values_list("grouping", flat=True)) | ||
for group in sorted(groups): | ||
logs = JobLogEntry.objects.filter(job_result__pk=job_result.pk, grouping=group) | ||
Comment on lines
+77
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This smells like possible bad query performance to me. Have you considered the following?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was asked to add sorting to as a set does not have an order. I could change the filter, but it would still be in an unordered set. @glennmatthews was concerned about the sorting. I will look at the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also to note, that this is only for running the jobs from the CLI, which I would think is rare. |
||
success_count = logs.filter(log_level=LogLevelChoices.LOG_SUCCESS).count() | ||
info_count = logs.filter(log_level=LogLevelChoices.LOG_INFO).count() | ||
warning_count = logs.filter(log_level=LogLevelChoices.LOG_WARNING).count() | ||
failure_count = logs.filter(log_level=LogLevelChoices.LOG_FAILURE).count() | ||
|
||
self.stdout.write( | ||
"\t{}: {} success, {} info, {} warning, {} failure".format( | ||
test_name, | ||
attrs["success"], | ||
attrs["info"], | ||
attrs["warning"], | ||
attrs["failure"], | ||
group, | ||
success_count, | ||
info_count, | ||
warning_count, | ||
failure_count, | ||
) | ||
) | ||
|
||
for log_entry in attrs["log"]: | ||
status = log_entry[1] | ||
for log_entry in logs: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will look into iterator, but this is only used when running jobs through management commands, which has limited use as they can't take arguments. |
||
status = log_entry.log_level | ||
if status == "success": | ||
status = self.style.SUCCESS(status) | ||
elif status == "info": | ||
|
@@ -100,10 +103,10 @@ def handle(self, *args, **options): | |
elif status == "failure": | ||
status = self.style.NOTICE(status) | ||
|
||
if log_entry[2]: # object associated with log entry | ||
self.stdout.write(f"\t\t{status}: {log_entry[2]}: {log_entry[-1]}") | ||
if log_entry.log_object: | ||
self.stdout.write(f"\t\t{status}: {log_entry.log_object}: {log_entry.message}") | ||
glennmatthews marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else: | ||
self.stdout.write(f"\t\t{status}: {log_entry[-1]}") | ||
self.stdout.write(f"\t\t{status}: {log_entry.message}") | ||
|
||
if job_result.data["output"]: | ||
self.stdout.write(job_result.data["output"]) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Generated by Django 3.1.13 on 2021-10-18 02:47 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
import uuid | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("extras", "0016_secret"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="JobLogEntry", | ||
fields=[ | ||
( | ||
"id", | ||
models.UUIDField( | ||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True | ||
), | ||
), | ||
("log_level", models.CharField(default="default", max_length=32)), | ||
("grouping", models.CharField(default="main", max_length=100)), | ||
("message", models.TextField(blank=True)), | ||
("created", models.DateTimeField(default=django.utils.timezone.now)), | ||
("log_object", models.CharField(blank=True, max_length=200, null=True)), | ||
("absolute_url", models.CharField(blank=True, max_length=255, null=True)), | ||
("job_result", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="extras.jobresult")), | ||
], | ||
options={ | ||
"abstract": False, | ||
}, | ||
), | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This worries me in that it is a magic setting that we're trying to move away from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if we move away from running jobs in Atomic Transactions then this can go away as well. This is not meant to be modified, so that is why we stuck it here.