216 lines
No EOL
8.2 KiB
Python
216 lines
No EOL
8.2 KiB
Python
import os
|
|
os.environ["NUMEXPR_MAX_THREADS"] = "12"
|
|
|
|
from fastapi import FastAPI, HTTPException, Depends, Body, status, Response
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from contextlib import asynccontextmanager
|
|
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
from datetime import timedelta
|
|
import pathlib
|
|
|
|
from modules.shared.configuration import APP_CONFIG
|
|
|
|
def initLogging():
|
|
"""Initialize logging with configuration from APP_CONFIG"""
|
|
# Get log level from config (default to INFO if not found)
|
|
logLevelName = APP_CONFIG.get("APP_LOGGING_LOG_LEVEL", "WARNING")
|
|
logLevel = getattr(logging, logLevelName)
|
|
|
|
# Create formatters - using single line format
|
|
consoleFormatter = logging.Formatter(
|
|
fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
|
datefmt=APP_CONFIG.get("APP_LOGGING_DATE_FORMAT", "%Y-%m-%d %H:%M:%S")
|
|
)
|
|
|
|
# File formatter with more detailed error information but still single line
|
|
fileFormatter = logging.Formatter(
|
|
fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s - %(pathname)s:%(lineno)d - %(funcName)s",
|
|
datefmt=APP_CONFIG.get("APP_LOGGING_DATE_FORMAT", "%Y-%m-%d %H:%M:%S")
|
|
)
|
|
|
|
# Add filter to exclude Chrome DevTools requests
|
|
class ChromeDevToolsFilter(logging.Filter):
|
|
def filter(self, record):
|
|
return not (isinstance(record.msg, str) and
|
|
('.well-known/appspecific/com.chrome.devtools.json' in record.msg or
|
|
'Request: /index.html' in record.msg))
|
|
|
|
# Add filter to exclude all httpcore loggers (including sub-loggers)
|
|
class HttpcoreStarFilter(logging.Filter):
|
|
def filter(self, record):
|
|
return not (record.name == 'httpcore' or record.name.startswith('httpcore.'))
|
|
|
|
# Add filter to exclude HTTP debug messages
|
|
class HTTPDebugFilter(logging.Filter):
|
|
def filter(self, record):
|
|
if isinstance(record.msg, str):
|
|
# Filter out HTTP debug messages
|
|
http_debug_patterns = [
|
|
'receive_response_body.started',
|
|
'receive_response_body.complete',
|
|
'response_closed.started',
|
|
'_send_single_request',
|
|
'httpcore.http11',
|
|
'httpx._client',
|
|
'HTTP Request'
|
|
]
|
|
return not any(pattern in record.msg for pattern in http_debug_patterns)
|
|
return True
|
|
|
|
# Add filter to remove emojis from log messages to prevent Unicode encoding errors
|
|
class EmojiFilter(logging.Filter):
|
|
def filter(self, record):
|
|
if isinstance(record.msg, str):
|
|
# Remove emojis and other Unicode characters that might cause encoding issues
|
|
import re
|
|
# Remove emojis and other Unicode symbols
|
|
record.msg = re.sub(r'[^\x00-\x7F]+', '[EMOJI]', record.msg)
|
|
return True
|
|
|
|
# Configure handlers based on config
|
|
handlers = []
|
|
|
|
# Add console handler if enabled
|
|
if APP_CONFIG.get("APP_LOGGING_CONSOLE_ENABLED", True):
|
|
consoleHandler = logging.StreamHandler()
|
|
consoleHandler.setFormatter(consoleFormatter)
|
|
consoleHandler.addFilter(ChromeDevToolsFilter())
|
|
consoleHandler.addFilter(HttpcoreStarFilter())
|
|
consoleHandler.addFilter(HTTPDebugFilter())
|
|
consoleHandler.addFilter(EmojiFilter())
|
|
handlers.append(consoleHandler)
|
|
|
|
# Add file handler if enabled
|
|
if APP_CONFIG.get("APP_LOGGING_FILE_ENABLED", True):
|
|
# Get log file path and ensure it's absolute
|
|
logFile = APP_CONFIG.get("APP_LOGGING_LOG_FILE", "app.log")
|
|
if not os.path.isabs(logFile):
|
|
# If relative path, make it relative to the gateway directory
|
|
gatewayDir = os.path.dirname(os.path.abspath(__file__))
|
|
logFile = os.path.join(gatewayDir, logFile)
|
|
|
|
# Ensure log directory exists
|
|
logDir = os.path.dirname(logFile)
|
|
if logDir:
|
|
os.makedirs(logDir, exist_ok=True)
|
|
|
|
rotationSize = int(APP_CONFIG.get("APP_LOGGING_ROTATION_SIZE", 10485760)) # Default: 10MB
|
|
backupCount = int(APP_CONFIG.get("APP_LOGGING_BACKUP_COUNT", 5))
|
|
|
|
fileHandler = RotatingFileHandler(
|
|
logFile,
|
|
maxBytes=rotationSize,
|
|
backupCount=backupCount
|
|
)
|
|
fileHandler.setFormatter(fileFormatter)
|
|
fileHandler.addFilter(ChromeDevToolsFilter())
|
|
fileHandler.addFilter(HttpcoreStarFilter())
|
|
fileHandler.addFilter(HTTPDebugFilter())
|
|
fileHandler.addFilter(EmojiFilter())
|
|
handlers.append(fileHandler)
|
|
|
|
# Configure the root logger
|
|
logging.basicConfig(
|
|
level=logLevel,
|
|
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
|
datefmt=APP_CONFIG.get("APP_LOGGING_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"),
|
|
handlers=handlers,
|
|
force=True # Force reconfiguration of the root logger
|
|
)
|
|
|
|
# Silence noisy third-party libraries - use the same level as the root logger
|
|
noisyLoggers = ["httpx", "httpcore", "urllib3", "asyncio", "fastapi.security.oauth2", "msal"]
|
|
for loggerName in noisyLoggers:
|
|
logging.getLogger(loggerName).setLevel(logging.WARNING)
|
|
|
|
# Log the current logging configuration
|
|
logger = logging.getLogger(__name__)
|
|
logger.info(f"Logging initialized with level {logLevelName}")
|
|
logger.info(f"Log file: {logFile if APP_CONFIG.get('APP_LOGGING_FILE_ENABLED', True) else 'disabled'}")
|
|
logger.info(f"Console logging: {'enabled' if APP_CONFIG.get('APP_LOGGING_CONSOLE_ENABLED', True) else 'disabled'}")
|
|
|
|
# Initialize logging
|
|
initLogging()
|
|
logger = logging.getLogger(__name__)
|
|
instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
|
|
|
|
# Define lifespan context manager for application startup/shutdown events
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Startup logic
|
|
logger.info("Application is starting up")
|
|
|
|
# Initialize root interface to ensure database is properly set up
|
|
from modules.interfaces.interfaceAppObjects import getRootInterface
|
|
getRootInterface()
|
|
|
|
yield
|
|
|
|
# Shutdown logic
|
|
logger.info("Application has been shut down")
|
|
|
|
# START APP
|
|
app = FastAPI(
|
|
title="PowerOn | Data Platform API",
|
|
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instanceLabel})",
|
|
lifespan=lifespan
|
|
)
|
|
|
|
|
|
# Parse CORS origins from environment variable
|
|
def get_allowed_origins():
|
|
origins_str = APP_CONFIG.get("APP_ALLOWED_ORIGINS", "http://localhost:8080")
|
|
# Split by comma and strip whitespace
|
|
origins = [origin.strip() for origin in origins_str.split(",")]
|
|
logger.info(f"CORS allowed origins: {origins}")
|
|
return origins
|
|
|
|
# CORS configuration using environment variables
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins= get_allowed_origins(),
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
allow_headers=["*"],
|
|
expose_headers=["*"],
|
|
max_age=86400 # Increased caching for preflight requests
|
|
)
|
|
|
|
# Include all routers
|
|
from modules.routes.routeAdmin import router as generalRouter
|
|
app.include_router(generalRouter)
|
|
|
|
from modules.routes.routeAttributes import router as attributesRouter
|
|
app.include_router(attributesRouter)
|
|
|
|
from modules.routes.routeDataMandates import router as mandateRouter
|
|
app.include_router(mandateRouter)
|
|
|
|
from modules.routes.routeDataUsers import router as userRouter
|
|
app.include_router(userRouter)
|
|
|
|
from modules.routes.routeDataFiles import router as fileRouter
|
|
app.include_router(fileRouter)
|
|
|
|
from modules.routes.routeDataPrompts import router as promptRouter
|
|
app.include_router(promptRouter)
|
|
|
|
from modules.routes.routeDataConnections import router as connectionsRouter
|
|
app.include_router(connectionsRouter)
|
|
|
|
from modules.routes.routeWorkflows import router as workflowRouter
|
|
app.include_router(workflowRouter)
|
|
|
|
from modules.routes.routeSecurityLocal import router as localRouter
|
|
app.include_router(localRouter)
|
|
|
|
from modules.routes.routeSecurityMsft import router as msftRouter
|
|
app.include_router(msftRouter)
|
|
|
|
from modules.routes.routeSecurityGoogle import router as googleRouter
|
|
app.include_router(googleRouter)
|
|
|
|
from modules.routes.routeJira import router as jiraRouter
|
|
app.include_router(jiraRouter) |