Make logging code self-contained (#13785)

* Make logging code self-contained.

Rewrite logging code to use python's builting QueueListener, effectively
moving the logging process into a thread of the Frigate app.

Also, wrap this behaviour in a easy-to-use context manager to encourage
some consistency.

* Fixed typing errors

* Remove todo note from log filter

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

* Do not access log record's msg directly

* Clear all root handlers before starting app

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
gtsiam 2024-09-17 16:26:25 +03:00 committed by GitHub
parent f7eaace7ae
commit 1c24f0054a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 72 deletions

View File

@ -1,17 +1,28 @@
import faulthandler
import logging
import threading
from flask import cli
from frigate.app import FrigateApp
def main() -> None:
faulthandler.enable()
threading.current_thread().name = "frigate"
# Clear all existing handlers.
logging.basicConfig(
level=logging.INFO,
handlers=[],
force=True,
)
threading.current_thread().name = "frigate"
cli.show_server_banner = lambda *x: None
if __name__ == "__main__":
frigate_app = FrigateApp()
# Run the main application.
FrigateApp().start()
frigate_app.start()
if __name__ == "__main__":
main()

View File

@ -43,7 +43,7 @@ from frigate.events.audio import listen_to_audio
from frigate.events.cleanup import EventCleanup
from frigate.events.external import ExternalEventProcessor
from frigate.events.maintainer import EventProcessor
from frigate.log import log_process, root_configurer
from frigate.log import log_thread
from frigate.models import (
Event,
Export,
@ -113,15 +113,6 @@ class FrigateApp:
else:
logger.debug(f"Skipping directory: {d}")
def init_logger(self) -> None:
self.log_process = mp.Process(
target=log_process, args=(self.log_queue,), name="log_process"
)
self.log_process.daemon = True
self.log_process.start()
self.processes["logger"] = self.log_process.pid or 0
root_configurer(self.log_queue)
def init_config(self) -> None:
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
@ -667,6 +658,7 @@ class FrigateApp:
logger.info("********************************************************")
logger.info("********************************************************")
@log_thread()
def start(self) -> None:
parser = argparse.ArgumentParser(
prog="Frigate",
@ -675,7 +667,6 @@ class FrigateApp:
parser.add_argument("--validate-config", action="store_true")
args = parser.parse_args()
self.init_logger()
logger.info(f"Starting Frigate ({VERSION})")
try:
@ -702,13 +693,11 @@ class FrigateApp:
print("*************************************************************")
print("*** End Config Validation Errors ***")
print("*************************************************************")
self.log_process.terminate()
sys.exit(1)
if args.validate_config:
print("*************************************************************")
print("*** Your config file is valid. ***")
print("*************************************************************")
self.log_process.terminate()
sys.exit(0)
self.set_environment_vars()
self.set_log_levels()
@ -725,7 +714,6 @@ class FrigateApp:
self.init_dispatcher()
except Exception as e:
print(e)
self.log_process.terminate()
sys.exit(1)
self.start_detectors()
self.start_video_output_processor()
@ -848,7 +836,4 @@ class FrigateApp:
shm.close()
shm.unlink()
self.log_process.terminate()
self.log_process.join()
os._exit(os.EX_OK)

View File

@ -1,71 +1,71 @@
# adapted from https://medium.com/@jonathonbao/python3-logging-with-multiprocessing-f51f460b8778
import atexit
import logging
import multiprocessing as mp
import os
import queue
import signal
import threading
from collections import deque
from logging import handlers
from multiprocessing import Queue
from types import FrameType
from contextlib import AbstractContextManager, ContextDecorator
from logging.handlers import QueueHandler, QueueListener
from types import TracebackType
from typing import Deque, Optional
from setproctitle import setproctitle
from typing_extensions import Self
from frigate.util.builtin import clean_camera_user_pass
def listener_configurer() -> None:
root = logging.getLogger()
if root.hasHandlers():
root.handlers.clear()
console_handler = logging.StreamHandler()
formatter = logging.Formatter(
"[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s", "%Y-%m-%d %H:%M:%S"
LOG_HANDLER = logging.StreamHandler()
LOG_HANDLER.setFormatter(
logging.Formatter(
"[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s",
"%Y-%m-%d %H:%M:%S",
)
)
LOG_HANDLER.addFilter(
lambda record: not record.getMessage().startswith(
"You are using a scalar distance function"
)
)
console_handler.setFormatter(formatter)
root.addHandler(console_handler)
root.setLevel(logging.INFO)
def root_configurer(queue: Queue) -> None:
h = handlers.QueueHandler(queue)
root = logging.getLogger()
class log_thread(AbstractContextManager, ContextDecorator):
def __init__(self, *, handler: logging.Handler = LOG_HANDLER):
super().__init__()
if root.hasHandlers():
root.handlers.clear()
self._handler = handler
root.addHandler(h)
root.setLevel(logging.INFO)
log_queue: mp.Queue = mp.Queue()
self._queue_handler = QueueHandler(log_queue)
self._log_listener = QueueListener(
log_queue, self._handler, respect_handler_level=True
)
def log_process(log_queue: Queue) -> None:
threading.current_thread().name = "logger"
setproctitle("frigate.logger")
listener_configurer()
@property
def handler(self) -> logging.Handler:
return self._handler
stop_event = mp.Event()
def _stop_thread(self) -> None:
self._log_listener.stop()
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
stop_event.set()
def __enter__(self) -> Self:
logging.getLogger().addHandler(self._queue_handler)
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
atexit.register(self._stop_thread)
self._log_listener.start()
while True:
try:
record = log_queue.get(block=True, timeout=1.0)
except queue.Empty:
if stop_event.is_set():
break
continue
if record.msg.startswith("You are using a scalar distance function"):
continue
logger = logging.getLogger(record.name)
logger.handle(record)
return self
def __exit__(
self,
exc_type: Optional[type[BaseException]],
exc_info: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
logging.getLogger().removeHandler(self._queue_handler)
atexit.unregister(self._stop_thread)
self._stop_thread()
# based on https://codereview.stackexchange.com/a/17959

View File

@ -28,8 +28,7 @@ from frigate.video import ( # noqa: E402
start_or_restart_ffmpeg,
)
logging.basicConfig()
logging.root.setLevel(logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)