Skip to content

Index

log_panel.handlers

BufferedDatabaseHandler()

Bases: DatabaseHandler

A DatabaseHandler that accumulates records in memory and flushes them in batches.

Flushes when any of the following conditions are met:

  • The buffer reaches LOG_PANEL['BUFFER_SIZE'] records.
  • A record at or above LOG_PANEL['BUFFER_FLUSH_LEVEL'] is emitted.
  • A later record arrives after LOG_PANEL['BUFFER_FLUSH_INTERVAL'] seconds.
  • flush() or close() is called explicitly.

Batches are written with a single bulk_create call. Threshold signals fire per-record after each flush, so alert semantics are preserved.

Records not yet flushed at process exit are lost only on abrupt termination (SIGKILL, OOM). Normal shutdown calls close(), which drains the buffer first.

Source code in log_panel/handlers/sql.py
def __init__(self) -> None:
    super().__init__()
    self._buffer: list[LogRecord] = []
    self._buffer_lock = threading.Lock()
    self._closed = False
    self._owner_pid = os.getpid()
    self._last_flush_at = time.monotonic()

DatabaseHandler()

Bases: Handler

Persist log records.

The target database alias is resolved via LOG_PANEL['DATABASE_ALIAS'].

Source code in log_panel/handlers/sql.py
def __init__(self) -> None:
    super().__init__()
    self._executor = ThreadPoolExecutor(
        max_workers=1,
        thread_name_prefix="log-panel-sql",
    )

emit(record)

Format and insert a log record into the configured SQL database.

A thread-local guard prevents infinite recursion when a database execute wrapper itself emits log records while this handler is mid-write.

In ASGI contexts, Django protects the sync ORM from running inside an active event loop. Those records are persisted through a dedicated sync worker thread while preserving logging's synchronous delivery semantics.

Records emitted before the log_panel table exists (i.e. during migrate) are silently discarded so they cannot poison an in-progress migration transaction.

Source code in log_panel/handlers/sql.py
def emit(self, record: LogRecord) -> None:
    """
    Format and insert a log record into the configured SQL database.

    A thread-local guard prevents infinite recursion when a database execute wrapper itself emits log records
    while this handler is mid-write.

    In ASGI contexts, Django protects the sync ORM from running inside an active event loop. Those records are
    persisted through a dedicated sync worker thread while preserving logging's synchronous delivery semantics.

    Records emitted before the ``log_panel`` table exists (i.e. during ``migrate``) are silently discarded so they
    cannot poison an in-progress migration transaction.
    """
    if getattr(self._local, "emitting", False):
        return
    if self._is_migration_command():
        return
    if self._is_ignored_logger(logger_name=record.name):
        return
    if self._is_ignored_message(record=record):
        return
    storage_error_classes = self._storage_error_classes()
    try:
        if self._in_async_context():
            self._wait_for_worker(record=record)
            return

        self._emit_guarded(record=record, manage_connections=False)
    except storage_error_classes:
        pass
    except Exception:
        self.handleError(record)