-
Notifications
You must be signed in to change notification settings - Fork 382
Expand file tree
/
Copy pathapp.py
More file actions
218 lines (168 loc) · 8.58 KB
/
app.py
File metadata and controls
218 lines (168 loc) · 8.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
"""Create and configure the FastAPI application."""
import logging
import os
from contextlib import asynccontextmanager
from api.api_routes import router as backend_router
from azure.monitor.opentelemetry.exporter import AzureMonitorLogExporter, AzureMonitorTraceExporter
from common.config.config import app_config
from common.logger.app_logger import AppLogger
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from helper.azure_credential_utils import get_azure_credential
from opentelemetry import trace
from opentelemetry._logs import set_logger_provider
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent # pylint: disable=E0611
from sql_agents.agent_manager import clear_sql_agents, set_sql_agents
from sql_agents.agents.agent_config import AgentBaseConfig
from sql_agents.helpers.agents_manager import SqlAgents
import uvicorn
# from agent_services.agents_routes import router as agents_router
# Load environment variables
load_dotenv()
# Configure logging
# Basic application logging (default: INFO level)
AZURE_BASIC_LOGGING_LEVEL = os.getenv("AZURE_BASIC_LOGGING_LEVEL", "INFO").upper()
# Azure package logging (default: WARNING level to suppress INFO)
AZURE_PACKAGE_LOGGING_LEVEL = os.getenv("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING").upper()
# Azure logging packages (default: empty list)
azure_logging_packages_env = os.getenv("AZURE_LOGGING_PACKAGES")
AZURE_LOGGING_PACKAGES = azure_logging_packages_env.split(",") if azure_logging_packages_env else []
# Basic config: logging.basicConfig(level=logging.INFO)
logging.basicConfig(
level=getattr(logging, AZURE_BASIC_LOGGING_LEVEL, logging.INFO),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Package config: Azure loggers set to WARNING to suppress INFO
for logger_name in AZURE_LOGGING_PACKAGES:
logging.getLogger(logger_name).setLevel(getattr(logging, AZURE_PACKAGE_LOGGING_LEVEL, logging.WARNING))
# Suppress noisy OpenTelemetry and Azure Monitor logs
# logging.getLogger("opentelemetry.sdk").setLevel(logging.ERROR)
# logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING)
# logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel(logging.WARNING)
logger = AppLogger("app")
# Global variables for agents
sql_agents: SqlAgents = None
azure_client = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage application lifespan - startup and shutdown."""
global sql_agents, azure_client
# Startup
try:
logger.logger.info("Initializing SQL agents...")
# Create Azure credentials and client
creds = get_azure_credential(app_config.azure_client_id)
azure_client = AzureAIAgent.create_client(
credential=creds,
endpoint=app_config.ai_project_endpoint
)
# Setup agent configuration with default conversion settings
agent_config = AgentBaseConfig(
project_client=azure_client,
sql_from="informix", # Default source dialect
sql_to="tsql" # Default target dialect
)
# Create SQL agents
sql_agents = await SqlAgents.create(agent_config)
# Set the global agents instance
set_sql_agents(sql_agents)
logger.logger.info("SQL agents initialized successfully.")
except Exception as exc:
logger.logger.error("Failed to initialize SQL agents: %s", exc)
# Don't raise the exception to allow the app to start even if agents fail
yield # Application runs here
# Shutdown
try:
if sql_agents:
logger.logger.info("Application shutting down - cleaning up SQL agents...")
await sql_agents.delete_agents()
logger.logger.info("SQL agents cleaned up successfully.")
# Clear the global agents instance
await clear_sql_agents()
if azure_client:
await azure_client.close()
except Exception as exc:
logger.logger.error("Error during agent cleanup: %s", exc)
def create_app() -> FastAPI:
"""Create and return the FastAPI application instance."""
app = FastAPI(title="Code Gen Accelerator", version="1.0.0", lifespan=lifespan)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configure Azure Monitor and instrument FastAPI for OpenTelemetry
# This must happen AFTER app creation but BEFORE route registration
instrumentation_key = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
if instrumentation_key:
# SOLUTION: Use manual telemetry setup instead of configure_azure_monitor
# This gives us precise control over what gets instrumented, avoiding interference
# with Semantic Kernel's async generators while still tracking Azure SDK calls
# Set up Azure Monitor exporter for traces
azure_trace_exporter = AzureMonitorTraceExporter(connection_string=instrumentation_key)
# Create a tracer provider and add the Azure Monitor exporter
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(BatchSpanProcessor(azure_trace_exporter))
# Set the global tracer provider
trace.set_tracer_provider(tracer_provider)
# Set up Azure Monitor exporter for logs (appears in traces table)
azure_log_exporter = AzureMonitorLogExporter(connection_string=instrumentation_key)
# Create a logger provider and add the Azure Monitor exporter
logger_provider = LoggerProvider()
logger_provider.add_log_record_processor(BatchLogRecordProcessor(azure_log_exporter))
set_logger_provider(logger_provider)
# Attach OpenTelemetry handler to Python's root logger
# Ensure we don't accumulate stale handlers or mismatch logger providers
root_logger = logging.getLogger()
# Remove any existing OpenTelemetry logging handlers to avoid duplicates
for handler in list(root_logger.handlers):
if isinstance(handler, LoggingHandler):
root_logger.removeHandler(handler)
# Close the handler to release any associated exporter/background resources
try:
handler.close()
except Exception as exc:
# Guard against unexpected close failures
logging.error("Error while closing logging handler %r: %s", handler, exc)
# Add a fresh handler bound to the current logger_provider
root_logger.addHandler(LoggingHandler(logger_provider=logger_provider))
# Instrument ONLY FastAPI for HTTP request/response tracing
# This is safe because it only wraps HTTP handlers, not internal async operations
FastAPIInstrumentor.instrument_app(
app,
excluded_urls="socket,ws", # Exclude WebSocket URLs to reduce noise
tracer_provider=tracer_provider
)
# Optional: Add manual spans in your code for Azure SDK operations using:
# from opentelemetry import trace
# tracer = trace.get_tracer(__name__)
# with tracer.start_as_current_span("operation_name"):
# # your Azure SDK call here
logger.logger.info("Application Insights configured with selective instrumentation")
logger.logger.info("✓ FastAPI HTTP tracing enabled")
logger.logger.info("✓ Python logging export to Application Insights enabled")
logger.logger.info("✓ Manual span support enabled for Azure SDK operations")
logger.logger.info("✓ Custom events via OpenTelemetry enabled")
logger.logger.info("✓ Semantic Kernel async generators unaffected")
else:
logger.logger.warning("No Application Insights connection string found. Telemetry disabled.")
# Include routers with /api prefix
app.include_router(backend_router, prefix="/api", tags=["backend"])
# app.include_router(agents_router, prefix="/api/agents", tags=["agents"])
@app.get("/health")
async def health_check():
"""Health check endpoint."""
return {"status": "healthy"}
return app
app = create_app()
if __name__ == "__main__":
uvicorn.run("app:app", host="127.0.0.1", port=8000, reload=True)