POC: Added FastAPI with one endpoint (get /logs/service)

This commit is contained in:
Rui Alves 2024-09-07 16:45:39 +01:00
parent 17901fcfef
commit c50283b55e
5 changed files with 120 additions and 28 deletions

View File

@ -2,7 +2,7 @@ daemon off;
user root; user root;
worker_processes auto; worker_processes auto;
error_log /dev/stdout warn; error_log /dev/stdout debug;
pid /var/run/nginx.pid; pid /var/run/nginx.pid;
events { events {
@ -222,12 +222,16 @@ http {
include proxy.conf; include proxy.conf;
} }
location ~* /api/.*\.(jpg|jpeg|png|webp|gif)$ { # FIXME: Needed to disabled this rule, otherwise it fails for endpoints that end with one of those file extensions
include auth_request.conf; # 1. with httptools it passes the auth.conf but then throws a 400 error "WARN "Invalid HTTP request received." -> https://github.com/encode/uvicorn/blob/47304d9ae76321f0f5f649ff4f73e09b17085933/uvicorn/protocols/http/httptools_impl.py#L165
rewrite ^/api/(.*)$ $1 break; # 2. With h11 it goes through the auth.conf but returns a 404 error
proxy_pass http://frigate_api; # We might need to add extra rules that will allow endpoint that end with an extension OR find a fix without creating other rules
include proxy.conf; # location ~* /api/.*\.(jpg|jpeg|png|webp|gif)$ {
} # include auth_request.conf;
# rewrite ^/api/(.*)$ $1 break;
# proxy_pass http://frigate_api;
# include proxy.conf;
# }
location /api/ { location /api/ {
include auth_request.conf; include auth_request.conf;

View File

@ -10,6 +10,9 @@ from functools import reduce
from typing import Optional from typing import Optional
import requests import requests
from fastapi import APIRouter, Path
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from flask import Blueprint, Flask, current_app, jsonify, make_response, request from flask import Blueprint, Flask, current_app, jsonify, make_response, request
from markupsafe import escape from markupsafe import escape
from peewee import operator from peewee import operator
@ -17,6 +20,7 @@ from playhouse.sqliteq import SqliteQueueDatabase
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
from frigate.api.auth import AuthBp, get_jwt_secret, limiter from frigate.api.auth import AuthBp, get_jwt_secret, limiter
from frigate.api.defs.tags import Tags
from frigate.api.event import EventBp from frigate.api.event import EventBp
from frigate.api.export import ExportBp from frigate.api.export import ExportBp
from frigate.api.media import MediaBp from frigate.api.media import MediaBp
@ -52,6 +56,8 @@ bp.register_blueprint(ReviewBp)
bp.register_blueprint(AuthBp) bp.register_blueprint(AuthBp)
bp.register_blueprint(NotificationBp) bp.register_blueprint(NotificationBp)
router = APIRouter()
def create_app( def create_app(
frigate_config, frigate_config,
@ -454,19 +460,26 @@ def vainfo():
) )
@bp.route("/logs/<service>", methods=["GET"]) @router.get("/logs/{service}", tags=[Tags.logs])
def logs(service: str): def logs(
service: str = Path(enum=["frigate", "nginx", "go2rtc", "chroma"]),
download: Optional[str] = None,
start: Optional[int] = 0,
end: Optional[int] = None,
):
"""Get logs for the requested service (frigate/nginx/go2rtc/chroma)"""
def download_logs(service_location: str): def download_logs(service_location: str):
try: try:
file = open(service_location, "r") file = open(service_location, "r")
contents = file.read() contents = file.read()
file.close() file.close()
return jsonify(contents) return JSONResponse(jsonable_encoder(contents))
except FileNotFoundError as e: except FileNotFoundError as e:
logger.error(e) logger.error(e)
return make_response( return JSONResponse(
jsonify({"success": False, "message": "Could not find log file"}), content={"success": False, "message": "Could not find log file"},
500, status_code=500,
) )
log_locations = { log_locations = {
@ -478,17 +491,14 @@ def logs(service: str):
service_location = log_locations.get(service) service_location = log_locations.get(service)
if not service_location: if not service_location:
return make_response( return JSONResponse(
jsonify({"success": False, "message": "Not a valid service"}), content={"success": False, "message": "Not a valid service"},
404, status_code=404,
) )
if request.args.get("download", type=bool, default=False): if download:
return download_logs(service_location) return download_logs(service_location)
start = request.args.get("start", type=int, default=0)
end = request.args.get("end", type=int)
try: try:
file = open(service_location, "r") file = open(service_location, "r")
contents = file.read() contents = file.read()
@ -529,15 +539,15 @@ def logs(service: str):
logLines.append(currentLine) logLines.append(currentLine)
return make_response( return JSONResponse(
jsonify({"totalLines": len(logLines), "lines": logLines[start:end]}), content={"totalLines": len(logLines), "lines": logLines[start:end]},
200, status_code=200,
) )
except FileNotFoundError as e: except FileNotFoundError as e:
logger.error(e) logger.error(e)
return make_response( return JSONResponse(
jsonify({"success": False, "message": "Could not find log file"}), content={"success": False, "message": "Could not find log file"},
500, status_code=500,
) )

7
frigate/api/defs/tags.py Normal file
View File

@ -0,0 +1,7 @@
from enum import Enum
class Tags(Enum):
preview = "Preview"
logs = "Logs"
media = "Media"

View File

@ -0,0 +1,53 @@
import logging
from fastapi import FastAPI
from frigate.api import app as main_app
from frigate.api.defs.tags import Tags
from frigate.plus import PlusApi
from frigate.ptz.onvif import OnvifController
from frigate.stats.emitter import StatsEmitter
from frigate.storage import StorageMaintainer
logger = logging.getLogger(__name__)
# https://fastapi.tiangolo.com/tutorial/metadata/#use-your-tags
tags_metadata = [
{
"name": Tags.preview,
"description": "Preview routes",
},
{
"name": Tags.logs,
"description": "Logs routes",
},
{
"name": Tags.media,
"description": "Media routes",
},
]
def create_fastapi_app(
frigate_config,
detected_frames_processor,
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
plus_api: PlusApi,
stats_emitter: StatsEmitter,
):
logger.info("Starting FastAPI app")
app = FastAPI(debug=False, tags_metadata=tags_metadata)
# Routes
app.include_router(main_app.router)
# App Properties
app.frigate_config = frigate_config
app.detected_frames_processor = detected_frames_processor
app.storage_maintainer = storage_maintainer
app.camera_error_image = None
app.onvif = onvif
app.plus_api = plus_api
app.stats_emitter = stats_emitter
return app

View File

@ -14,6 +14,8 @@ from types import FrameType
from typing import Optional from typing import Optional
import psutil import psutil
import uvicorn
from fastapi.middleware.wsgi import WSGIMiddleware
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
@ -21,6 +23,7 @@ from pydantic import ValidationError
from frigate.api.app import create_app from frigate.api.app import create_app
from frigate.api.auth import hash_password from frigate.api.auth import hash_password
from frigate.api.fastapi_app import create_fastapi_app
from frigate.comms.config_updater import ConfigPublisher from frigate.comms.config_updater import ConfigPublisher
from frigate.comms.dispatcher import Communicator, Dispatcher from frigate.comms.dispatcher import Communicator, Dispatcher
from frigate.comms.inter_process import InterProcessCommunicator from frigate.comms.inter_process import InterProcessCommunicator
@ -397,6 +400,15 @@ class FrigateApp:
self.stats_emitter, self.stats_emitter,
) )
self.fastapi_app = create_fastapi_app(
self.config,
self.detected_frames_processor,
self.storage_maintainer,
self.onvif_controller,
self.plus_api,
self.stats_emitter,
)
def init_onvif(self) -> None: def init_onvif(self) -> None:
self.onvif_controller = OnvifController(self.config, self.ptz_metrics) self.onvif_controller = OnvifController(self.config, self.ptz_metrics)
@ -754,11 +766,17 @@ class FrigateApp:
signal.signal(signal.SIGTERM, receiveSignal) signal.signal(signal.SIGTERM, receiveSignal)
try: try:
self.flask_app.run(host="127.0.0.1", port=5001, debug=False, threaded=True) # Run the flask app inside fastapi: https://fastapi.tiangolo.com/advanced/sub-applications/
self.fastapi_app.mount("", WSGIMiddleware(self.flask_app))
uvicorn.run(
self.fastapi_app,
host="127.0.0.1",
port=5001,
)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
logger.info("Flask has exited...") logger.info("FastAPI/Flask has exited...")
self.stop() self.stop()