Skip to content

Commit c90d7ed

Browse files
feat: Enhance logging for API routes and services to improve traceability
1 parent 03d432d commit c90d7ed

6 files changed

Lines changed: 91 additions & 20 deletions

File tree

infra/main.bicep

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,6 @@ module applicationInsights 'br/public:avm/res/insights/component:0.7.1' = if (en
331331
flowType: 'Bluefield'
332332
// WAF aligned configuration for Monitoring
333333
workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : ''
334-
diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
335334
}
336335
}
337336
// ========== Virtual Network and Networking Components ========== //

src/api/api/api_routes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
@router.get("/fetchChartData")
2424
async def fetch_chart_data():
2525
try:
26+
logger.info("GET /fetchChartData called")
2627
chart_service = ChartService()
2728
response = await chart_service.fetch_chart_data()
2829
track_event_if_configured(
@@ -76,6 +77,7 @@ async def fetch_chart_data_with_filters(chart_filters: ChartFilters):
7677
@router.get("/fetchFilterData")
7778
async def fetch_filter_data():
7879
try:
80+
logger.info("GET /fetchFilterData called")
7981
chart_service = ChartService()
8082
response = await chart_service.fetch_filter_data()
8183
track_event_if_configured(
@@ -103,6 +105,8 @@ async def conversation(request: Request):
103105
request_json = await request.json()
104106
conversation_id = request_json.get("conversation_id")
105107
query = request_json.get("query")
108+
logger.info("POST /chat called: conversation_id=%s, query_length=%d",
109+
conversation_id, len(query) if query else 0)
106110

107111
# Track chat request initiation
108112
track_event_if_configured("ChatRequestReceived", {
@@ -116,6 +120,7 @@ async def conversation(request: Request):
116120

117121
chat_service = ChatService()
118122
result = await chat_service.stream_chat_request(conversation_id, query)
123+
logger.info("Chat stream initiated successfully for conversation_id=%s", conversation_id)
119124
track_event_if_configured(
120125
"ChatStreamSuccess",
121126
{"conversation_id": conversation_id, "query": query}
@@ -141,6 +146,7 @@ async def conversation(request: Request):
141146

142147
@router.get("/layout-config")
143148
async def get_layout_config():
149+
logger.info("GET /layout-config called")
144150
layout_config_str = os.getenv("REACT_APP_LAYOUT_CONFIG", "")
145151
if layout_config_str:
146152
try:
@@ -164,6 +170,7 @@ async def get_layout_config():
164170

165171
@router.get("/display-chart-default")
166172
async def get_chart_config():
173+
logger.info("GET /display-chart-default called")
167174
chart_config = os.getenv("DISPLAY_CHART_DEFAULT", "")
168175
if chart_config:
169176
track_event_if_configured("ChartDisplayDefaultFetched", {"value": chart_config})
@@ -182,6 +189,7 @@ async def fetch_azure_search_content_endpoint(request: Request):
182189
# Parse the request JSON
183190
request_json = await request.json()
184191
url = request_json.get("url")
192+
logger.info("POST /fetch-azure-search-content called: url=%s", url)
185193

186194
if not url:
187195
return JSONResponse(content={"error": "URL is required"}, status_code=400)
@@ -207,8 +215,10 @@ def fetch_content():
207215
data = response.json()
208216
content = data.get("content", "")
209217
title = data.get("sourceurl", "")
218+
logger.info("Azure Search content fetched successfully: url=%s", url)
210219
return {"content": content, "title": title}
211220
else:
221+
logger.warning("Azure Search content fetch failed: url=%s, status=%d", url, response.status_code)
212222
return {"error": f"HTTP {response.status_code}"}
213223
except Exception:
214224
logger.exception("Exception occurred while making the HTTP request")

src/api/api/history_routes.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ async def update_conversation(request: Request):
2525
# Parse request body
2626
request_json = await request.json()
2727
conversation_id = request_json.get("conversation_id")
28+
logger.info("POST /history/update called: conversation_id=%s, user_id=%s", conversation_id, user_id)
2829

2930
if not conversation_id:
3031
raise HTTPException(status_code=400, detail="No conversation_id found")
@@ -39,6 +40,8 @@ async def update_conversation(request: Request):
3940

4041
if not update_response:
4142
raise HTTPException(status_code=500, detail="Failed to update conversation")
43+
logger.info("Conversation updated successfully: conversation_id=%s, title='%s'",
44+
conversation_id, update_response.get("title"))
4245
track_event_if_configured("ConversationUpdated", {
4346
"user_id": user_id,
4447
"conversation_id": conversation_id,
@@ -80,6 +83,7 @@ async def update_message_feedback(request: Request):
8083
request_json = await request.json()
8184
message_id = request_json.get("message_id")
8285
message_feedback = request_json.get("message_feedback")
86+
logger.info("POST /history/message_feedback called: message_id=%s, user_id=%s", message_id, user_id)
8387

8488
if not message_id:
8589
track_event_if_configured("MessageFeedbackValidationError", {
@@ -144,6 +148,7 @@ async def delete_conversation(request: Request):
144148
# Parse request body
145149
request_json = await request.json()
146150
conversation_id = request_json.get("conversation_id")
151+
logger.info("DELETE /history/delete called: conversation_id=%s, user_id=%s", conversation_id, user_id)
147152
if not conversation_id:
148153
track_event_if_configured("DeleteConversationValidationError", {
149154
"error": "conversation_id is missing",
@@ -159,6 +164,7 @@ async def delete_conversation(request: Request):
159164
# Delete conversation using HistoryService
160165
deleted = await history_service.delete_conversation(user_id, conversation_id)
161166
if deleted:
167+
logger.info("Conversation deleted successfully: conversation_id=%s, user_id=%s", conversation_id, user_id)
162168
track_event_if_configured("ConversationDeleted", {
163169
"user_id": user_id,
164170
"conversation_id": conversation_id
@@ -247,6 +253,7 @@ async def get_conversation_messages(request: Request):
247253
# Parse request body
248254
request_json = await request.json()
249255
conversation_id = request_json.get("conversation_id")
256+
logger.info("POST /history/read called: conversation_id=%s, user_id=%s", conversation_id, user_id)
250257

251258
if not conversation_id:
252259
track_event_if_configured("ReadConversationValidationError", {
@@ -271,6 +278,7 @@ async def get_conversation_messages(request: Request):
271278
status_code=404,
272279
detail=f"Conversation {conversation_id} was not found. It either does not exist or the user does not have access to it."
273280
)
281+
logger.info("Returning %d message(s) for conversation %s", len(conversationMessages), conversation_id)
274282
track_event_if_configured("ConversationRead", {
275283
"user_id": user_id,
276284
"conversation_id": conversation_id,
@@ -307,6 +315,8 @@ async def rename_conversation(request: Request):
307315
request_json = await request.json()
308316
conversation_id = request_json.get("conversation_id")
309317
title = request_json.get("title")
318+
logger.info("POST /history/rename called: conversation_id=%s, user_id=%s, new_title='%s'",
319+
conversation_id, user_id, title)
310320

311321
if not conversation_id:
312322
track_event_if_configured("RenameConversationValidationError", {
@@ -328,6 +338,7 @@ async def rename_conversation(request: Request):
328338
raise HTTPException(status_code=400, detail="title is required")
329339

330340
rename_conversation = await history_service.rename_conversation(user_id, conversation_id, title)
341+
logger.info("Conversation renamed successfully: conversation_id=%s, new_title='%s'", conversation_id, title)
331342

332343
track_event_if_configured("ConversationRenamed", {
333344
"user_id": user_id,
@@ -357,6 +368,7 @@ async def delete_all_conversations(request: Request):
357368
authenticated_user = get_authenticated_user_details(
358369
request_headers=request.headers)
359370
user_id = authenticated_user["user_principal_id"]
371+
logger.info("DELETE /history/delete_all called: user_id=%s", user_id)
360372

361373
# Get all user conversations
362374
conversations = await history_service.get_conversations(user_id, offset=0, limit=None)
@@ -368,8 +380,10 @@ async def delete_all_conversations(request: Request):
368380
detail=f"No conversations for {user_id} were found")
369381

370382
# Delete all conversations
383+
logger.info("Deleting %d conversation(s) for user %s", len(conversations), user_id)
371384
for conversation in conversations:
372385
await history_service.delete_conversation(user_id, conversation["id"])
386+
logger.info("All conversations deleted successfully for user %s", user_id)
373387

374388
track_event_if_configured("AllConversationsDeleted", {
375389
"user_id": user_id,
@@ -406,6 +420,7 @@ async def clear_messages(request: Request):
406420
# Parse request body
407421
request_json = await request.json()
408422
conversation_id = request_json.get("conversation_id")
423+
logger.info("POST /history/clear called: conversation_id=%s, user_id=%s", conversation_id, user_id)
409424

410425
if not conversation_id:
411426
track_event_if_configured("ClearMessagesValidationError", {
@@ -430,6 +445,7 @@ async def clear_messages(request: Request):
430445
raise HTTPException(
431446
status_code=404,
432447
detail="Failed to clear messages or conversation not found")
448+
logger.info("Messages cleared successfully for conversation %s, user %s", conversation_id, user_id)
433449
track_event_if_configured("MessagesCleared", {
434450
"user_id": user_id,
435451
"conversation_id": conversation_id
@@ -456,6 +472,7 @@ async def clear_messages(request: Request):
456472
@router.get("/history/ensure")
457473
async def ensure_cosmos():
458474
try:
475+
logger.info("GET /history/ensure called")
459476
success, err = await history_service.ensure_cosmos()
460477
if not success:
461478
track_event_if_configured("CosmosDBEnsureFailed", {

src/api/app.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
from api.api_routes import router as backend_router
1818
from api.history_routes import router as history_router
1919

20+
# Configure Azure Monitor and OpenTelemetry imports
21+
from azure.monitor.opentelemetry import configure_azure_monitor
22+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
23+
2024
# Load environment variables
2125
load_dotenv()
2226

@@ -40,16 +44,6 @@
4044
for logger_name in AZURE_LOGGING_PACKAGES:
4145
logging.getLogger(logger_name).setLevel(getattr(logging, AZURE_PACKAGE_LOGGING_LEVEL, logging.WARNING))
4246

43-
# Suppress noisy OpenTelemetry and Azure Monitor logs
44-
logging.getLogger("opentelemetry.sdk").setLevel(logging.ERROR)
45-
logging.getLogger("opentelemetry.instrumentation.httpx").setLevel(logging.WARNING)
46-
logging.getLogger("opentelemetry.instrumentation.aiohttp-client").setLevel(logging.WARNING)
47-
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING)
48-
logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel(logging.WARNING)
49-
50-
# Configure Azure Monitor and OpenTelemetry imports
51-
from azure.monitor.opentelemetry import configure_azure_monitor # noqa: E402
52-
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor # noqa: E402
5347

5448

5549
def build_app() -> FastAPI:
@@ -83,15 +77,24 @@ async def health_check():
8377
instrumentation_key = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
8478
if instrumentation_key:
8579
# Configure Application Insights telemetry with live metrics
86-
# Set disable_offline_storage=True to reduce logs about offline storage
8780
configure_azure_monitor(
8881
connection_string=instrumentation_key,
89-
enable_live_metrics=True,
90-
disable_offline_storage=True # Reduces "Storing events" logs
82+
enable_live_metrics=True
9183
)
9284

93-
# Instrument FastAPI app to automatically trace all requests
94-
FastAPIInstrumentor.instrument_app(fastapi_app)
85+
# Suppress noisy OpenTelemetry logs
86+
logging.getLogger("opentelemetry.sdk").setLevel(logging.ERROR)
87+
logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel(logging.WARNING)
88+
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING)
89+
logging.getLogger("azure.core.pipeline.policies._universal").setLevel(logging.WARNING)
90+
logging.getLogger("azure.core.pipeline.policies").setLevel(logging.WARNING)
91+
logging.getLogger("azure.identity").setLevel(logging.WARNING)
92+
93+
# Instrument FastAPI app — exclude health-check URL to reduce telemetry noise
94+
FastAPIInstrumentor.instrument_app(
95+
fastapi_app,
96+
excluded_urls="health"
97+
)
9598
logging.info("Application Insights configured with live metrics and FastAPI instrumentation enabled")
9699
else:
97100
logging.warning("No Application Insights connection string found. Telemetry disabled.")

src/api/services/chat_service.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ async def stream_openai_text(self, conversation_id: str, query: str) -> Streamin
120120
"""
121121
Get a streaming text response from OpenAI.
122122
"""
123+
logger.info("stream_openai_text called: conversation_id=%s, query_length=%d",
124+
conversation_id, len(query) if query else 0)
123125
async with (
124126
await get_azure_credential_async(client_id=self.azure_client_id) as credential,
125127
AIProjectClient(endpoint=self.ai_project_endpoint, credential=credential) as project_client,
@@ -139,12 +141,17 @@ async def stream_openai_text(self, conversation_id: str, query: str) -> Streamin
139141
thread_conversation_id = None
140142
cache = self.get_thread_cache()
141143
thread_conversation_id = cache.get(conversation_id, None)
144+
if thread_conversation_id:
145+
logger.info("Reusing existing thread %s for conversation %s",
146+
thread_conversation_id, conversation_id)
142147

143148
# Get agent with tools using provider
149+
logger.info("Retrieving orchestrator agent: '%s'", self.orchestrator_agent_name)
144150
agent = await provider.get_agent(
145151
name=self.orchestrator_agent_name,
146152
tools=custom_tool.get_sql_response
147153
)
154+
logger.info("Orchestrator agent retrieved successfully: '%s'", self.orchestrator_agent_name)
148155

149156
citations = []
150157
first_chunk = True
@@ -153,9 +160,11 @@ async def stream_openai_text(self, conversation_id: str, query: str) -> Streamin
153160

154161
if not thread_conversation_id:
155162
# Create a conversation using OpenAI client for conversation continuity
163+
logger.info("No existing thread found, creating new thread for conversation %s", conversation_id)
156164
openai_client = project_client.get_openai_client()
157165
conversation = await openai_client.conversations.create()
158166
thread_conversation_id = conversation.id
167+
logger.info("New thread created: %s for conversation %s", thread_conversation_id, conversation_id)
159168

160169
def replace_citation_marker(match):
161170
nonlocal citation_counter
@@ -165,6 +174,8 @@ def replace_citation_marker(match):
165174
citation_marker_map[marker] = citation_counter
166175
return f"[{citation_marker_map[marker]}]"
167176

177+
logger.info("Starting agent.run stream for conversation %s, thread %s",
178+
conversation_id, thread_conversation_id)
168179
async for chunk in agent.run(query, stream=True, conversation_id=thread_conversation_id):
169180
# Collect citations from Azure AI Search responses
170181
for content in getattr(chunk, "contents", []):
@@ -181,10 +192,13 @@ def replace_citation_marker(match):
181192
complete_response += chunk_text
182193
if first_chunk:
183194
first_chunk = False
195+
logger.info("First chunk received for conversation %s, streaming response", conversation_id)
184196
yield "{ \"answer\": " + chunk_text
185197
else:
186198
yield chunk_text
187199

200+
logger.info("Streaming complete for conversation %s: response_length=%d, citation_count=%d",
201+
conversation_id, len(complete_response), len(citations))
188202
cache[conversation_id] = thread_conversation_id
189203

190204
if citations:
@@ -255,6 +269,7 @@ async def stream_chat_request(self, conversation_id, query):
255269
"""
256270
Handles streaming chat requests.
257271
"""
272+
logger.info("stream_chat_request called: conversation_id=%s", conversation_id)
258273

259274
async def generate():
260275
try:

0 commit comments

Comments
 (0)