mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-04 21:05:26 +00:00
Update logging configuration to write logs to database
This commit is contained in:
parent
f08ac87888
commit
c18d2ea1dd
9 changed files with 212 additions and 11 deletions
|
@ -1,6 +1,11 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## v3.1-dev - Unreleased
|
||||
|
||||
### Improvements
|
||||
- Update logging configuration to write logs to database (to table `logs_record`)
|
||||
|
||||
## v3.0.1 - 2023-10-30
|
||||
|
||||
### Bug Fixes
|
||||
|
|
27
hc/logs/__init__.py
Normal file
27
hc/logs/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import Error
|
||||
|
||||
FORMATTER = logging.Formatter()
|
||||
|
||||
|
||||
class Handler(logging.Handler):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
# Import Record now not earlier, to avoid AppRegistryNotReady exception
|
||||
from hc.logs.models import Record
|
||||
|
||||
traceback = ""
|
||||
if record.exc_info:
|
||||
traceback = FORMATTER.formatException(record.exc_info)
|
||||
|
||||
try:
|
||||
Record.objects.create(
|
||||
name=record.name,
|
||||
level=record.levelno,
|
||||
message=record.getMessage(),
|
||||
traceback=traceback,
|
||||
)
|
||||
except Error as e:
|
||||
print(e)
|
57
hc/logs/admin.py
Normal file
57
hc/logs/admin.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.http import HttpRequest
|
||||
from django.utils.html import format_html
|
||||
|
||||
from hc.logs.models import Record
|
||||
|
||||
|
||||
@admin.register(Record)
|
||||
class RecordsAdmin(ModelAdmin[Record]):
|
||||
class Media:
|
||||
css = {"all": ("css/admin/records.css",)}
|
||||
|
||||
search_fields = ["name", "message"]
|
||||
readonly_fields = ("message",)
|
||||
list_display = ("when", "logger", "message_traceback")
|
||||
list_filter = (
|
||||
"created",
|
||||
"level",
|
||||
)
|
||||
|
||||
def when(self, obj: Record) -> str:
|
||||
return obj.created.strftime("%b %-d, %H:%M")
|
||||
|
||||
def logger(self, obj: Record) -> str:
|
||||
level_name = obj.get_level_display()
|
||||
level_letter = level_name[0].upper()
|
||||
return format_html(
|
||||
"""<span class="{}">{}</span> {}""",
|
||||
level_letter,
|
||||
level_letter,
|
||||
obj.name,
|
||||
)
|
||||
|
||||
@admin.display(description="Message")
|
||||
def message_traceback(self, obj: Record) -> str:
|
||||
if not obj.traceback:
|
||||
return obj.message
|
||||
|
||||
return format_html(
|
||||
"""{}<details><summary>Show traceback</summary><pre>{}</pre></details>
|
||||
|
||||
|
||||
""",
|
||||
obj.message,
|
||||
obj.traceback,
|
||||
)
|
||||
|
||||
def has_add_permission(self, request: HttpRequest) -> bool:
|
||||
return False
|
||||
|
||||
def has_change_permission(
|
||||
self, request: HttpRequest, obj: Record | None = None
|
||||
) -> bool:
|
||||
return False
|
45
hc/logs/migrations/0001_initial.py
Normal file
45
hc/logs/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 4.2.6 on 2023-10-31 13:37
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Record",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
("name", models.CharField(max_length=100)),
|
||||
(
|
||||
"level",
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "notset"),
|
||||
(10, "debug"),
|
||||
(20, "info"),
|
||||
(30, "warning"),
|
||||
(40, "error"),
|
||||
(50, "critical"),
|
||||
]
|
||||
),
|
||||
),
|
||||
("message", models.TextField()),
|
||||
("traceback", models.TextField()),
|
||||
],
|
||||
),
|
||||
]
|
0
hc/logs/migrations/__init__.py
Normal file
0
hc/logs/migrations/__init__.py
Normal file
23
hc/logs/models.py
Normal file
23
hc/logs/models.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
|
||||
LEVELS = [
|
||||
(logging.NOTSET, "notset"),
|
||||
(logging.DEBUG, "debug"),
|
||||
(logging.INFO, "info"),
|
||||
(logging.WARNING, "warning"),
|
||||
(logging.ERROR, "error"),
|
||||
(logging.CRITICAL, "critical"),
|
||||
]
|
||||
|
||||
|
||||
class Record(models.Model):
|
||||
created = models.DateTimeField(default=now)
|
||||
name = models.CharField(max_length=100)
|
||||
level = models.PositiveSmallIntegerField(choices=LEVELS)
|
||||
message = models.TextField()
|
||||
traceback = models.TextField()
|
3
hc/logs/tests.py
Normal file
3
hc/logs/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -65,6 +65,7 @@ INSTALLED_APPS = (
|
|||
"compressor",
|
||||
"hc.api",
|
||||
"hc.front",
|
||||
"hc.logs",
|
||||
"hc.payments",
|
||||
)
|
||||
|
||||
|
@ -114,23 +115,21 @@ TEMPLATES = [
|
|||
}
|
||||
]
|
||||
|
||||
# Extend Django logging to log unhandled exceptions to console even when DEBUG=False
|
||||
# Extend Django logging to log unhandled exceptions
|
||||
# and all logs from hc.* loggers to the database.
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"filters": {
|
||||
"require_debug_false": {
|
||||
"()": "django.utils.log.RequireDebugFalse",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console_debug_false": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.StreamHandler",
|
||||
"filters": ["require_debug_false"],
|
||||
"db": {
|
||||
"level": "DEBUG",
|
||||
"class": "hc.logs.Handler",
|
||||
},
|
||||
},
|
||||
"loggers": {"django.request": {"handlers": ["console_debug_false"]}},
|
||||
"loggers": {
|
||||
"django.request": {"level": "ERROR", "handlers": ["db"]},
|
||||
"hc": {"level": "DEBUG", "handlers": ["db"]},
|
||||
},
|
||||
}
|
||||
|
||||
WSGI_APPLICATION = "hc.wsgi.application"
|
||||
|
|
42
static/css/admin/records.css
Normal file
42
static/css/admin/records.css
Normal file
|
@ -0,0 +1,42 @@
|
|||
.field-when {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.field-logger {
|
||||
white-space: nowrap;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.field-logger span.N,
|
||||
.field-logger span.D,
|
||||
.field-logger span.I,
|
||||
.field-logger span.W,
|
||||
.field-logger span.E,
|
||||
.field-logger span.C {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
margin-right: 2px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.field-logger span.N { background: #e0e0e0; }
|
||||
.field-logger span.D { background: #b0bec5; }
|
||||
.field-logger span.I { background: #90caf9; }
|
||||
.field-logger span.W { background: #ffd54f; }
|
||||
.field-logger span.E { background: #d32f2f; color: #FFF; }
|
||||
.field-logger span.C { background: #333; color: #FFF; }
|
||||
|
||||
.field-message_traceback {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-message_traceback pre {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.field-traceback .readonly {
|
||||
font-family: monospace;
|
||||
}
|
Loading…
Add table
Reference in a new issue