From c18d2ea1ddf51278a3dbb57cfff5b1013dbc4f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= <cuu508@gmail.com> Date: Wed, 1 Nov 2023 09:34:45 +0200 Subject: [PATCH] Update logging configuration to write logs to database --- CHANGELOG.md | 5 +++ hc/logs/__init__.py | 27 ++++++++++++++ hc/logs/admin.py | 57 ++++++++++++++++++++++++++++++ hc/logs/migrations/0001_initial.py | 45 +++++++++++++++++++++++ hc/logs/migrations/__init__.py | 0 hc/logs/models.py | 23 ++++++++++++ hc/logs/tests.py | 3 ++ hc/settings.py | 21 ++++++----- static/css/admin/records.css | 42 ++++++++++++++++++++++ 9 files changed, 212 insertions(+), 11 deletions(-) create mode 100644 hc/logs/__init__.py create mode 100644 hc/logs/admin.py create mode 100644 hc/logs/migrations/0001_initial.py create mode 100644 hc/logs/migrations/__init__.py create mode 100644 hc/logs/models.py create mode 100644 hc/logs/tests.py create mode 100644 static/css/admin/records.css diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d67431..42f69a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/hc/logs/__init__.py b/hc/logs/__init__.py new file mode 100644 index 00000000..c781acb6 --- /dev/null +++ b/hc/logs/__init__.py @@ -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) diff --git a/hc/logs/admin.py b/hc/logs/admin.py new file mode 100644 index 00000000..9c414905 --- /dev/null +++ b/hc/logs/admin.py @@ -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 diff --git a/hc/logs/migrations/0001_initial.py b/hc/logs/migrations/0001_initial.py new file mode 100644 index 00000000..c09d7a36 --- /dev/null +++ b/hc/logs/migrations/0001_initial.py @@ -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()), + ], + ), + ] diff --git a/hc/logs/migrations/__init__.py b/hc/logs/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hc/logs/models.py b/hc/logs/models.py new file mode 100644 index 00000000..5bd11d69 --- /dev/null +++ b/hc/logs/models.py @@ -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() diff --git a/hc/logs/tests.py b/hc/logs/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/hc/logs/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hc/settings.py b/hc/settings.py index a1f863bf..4be05e1e 100644 --- a/hc/settings.py +++ b/hc/settings.py @@ -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" diff --git a/static/css/admin/records.css b/static/css/admin/records.css new file mode 100644 index 00000000..0256f0db --- /dev/null +++ b/static/css/admin/records.css @@ -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; +} \ No newline at end of file