0
0
Fork 0
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:
Pēteris Caune 2023-11-01 09:34:45 +02:00
parent f08ac87888
commit c18d2ea1dd
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
9 changed files with 212 additions and 11 deletions

View file

@ -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
View 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
View 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

View 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()),
],
),
]

View file

23
hc/logs/models.py Normal file
View 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
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -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"

View 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;
}