Skip to content

Commit 3f0e134

Browse files
Merge pull request #842 from microsoft/psl-logging-improvements
feat: improve Application Insights logging and telemetry handling
2 parents fb00119 + c9254ef commit 3f0e134

8 files changed

Lines changed: 294 additions & 137 deletions

File tree

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ omit =
1111
*/env/*
1212
*/.pytest_cache/*
1313
*/node_modules/*
14+
src/backend/v4/api/router.py
1415

1516
[paths]
1617
source =

src/backend/app.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from fastapi import FastAPI, Request
1818
from fastapi.middleware.cors import CORSMiddleware
1919

20+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
21+
2022
# Local imports
2123
from middleware.health_check import HealthCheckMiddleware
2224
from v4.api.router import app_v4
@@ -51,20 +53,6 @@ async def lifespan(app: FastAPI):
5153
logger.info("👋 MACAE application shutdown complete")
5254

5355

54-
# Check if the Application Insights Instrumentation Key is set in the environment variables
55-
connection_string = config.APPLICATIONINSIGHTS_CONNECTION_STRING
56-
if connection_string:
57-
# Configure Application Insights if the Instrumentation Key is found
58-
configure_azure_monitor(connection_string=connection_string)
59-
logging.info(
60-
"Application Insights configured with the provided Instrumentation Key"
61-
)
62-
else:
63-
# Log a warning if the Instrumentation Key is not found
64-
logging.warning(
65-
"No Application Insights Instrumentation Key found. Skipping configuration"
66-
)
67-
6856
# Configure logging levels from environment variables
6957
# logging.basicConfig(level=getattr(logging, config.AZURE_BASIC_LOGGING_LEVEL.upper(), logging.INFO))
7058

@@ -80,10 +68,32 @@ async def lifespan(app: FastAPI):
8068

8169
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING)
8270

71+
# Suppress noisy Azure Monitor exporter "Transmission succeeded" logs
72+
logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel(logging.WARNING)
73+
8374
# Initialize the FastAPI app
8475
app = FastAPI(lifespan=lifespan)
8576

8677
frontend_url = config.FRONTEND_SITE_NAME
78+
# Configure Azure Monitor and instrument FastAPI for OpenTelemetry
79+
# This enables automatic request tracing, dependency tracking, and proper operation_id
80+
if config.APPLICATIONINSIGHTS_CONNECTION_STRING:
81+
# Configure Application Insights telemetry with live metrics
82+
configure_azure_monitor(
83+
connection_string=config.APPLICATIONINSIGHTS_CONNECTION_STRING,
84+
enable_live_metrics=True
85+
)
86+
87+
# Instrument FastAPI app — exclude WebSocket URLs to reduce telemetry noise
88+
FastAPIInstrumentor.instrument_app(
89+
app,
90+
excluded_urls="socket,ws"
91+
)
92+
logging.info("Application Insights configured with live metrics and WebSocket filtering")
93+
else:
94+
logging.warning(
95+
"No Application Insights connection string found. Telemetry disabled."
96+
)
8797

8898
# Add this near the top of your app.py, after initializing the app
8999
app.add_middleware(

src/backend/common/config/app_config.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
from azure.ai.projects.aio import AIProjectClient
77
from azure.cosmos import CosmosClient
88
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
9+
from azure.identity.aio import (
10+
DefaultAzureCredential as DefaultAzureCredentialAsync,
11+
ManagedIdentityCredential as ManagedIdentityCredentialAsync,
12+
)
913
from dotenv import load_dotenv
1014

1115

@@ -113,7 +117,8 @@ def get_azure_credential(self, client_id=None):
113117
"""
114118
Returns an Azure credential based on the application environment.
115119
116-
If the environment is 'dev', it uses DefaultAzureCredential.
120+
If the environment is 'dev', it uses DefaultAzureCredential with exclude_environment_credential=True
121+
to avoid EnvironmentCredential exceptions in Application Insights traces.
117122
Otherwise, it uses ManagedIdentityCredential.
118123
119124
Args:
@@ -123,10 +128,29 @@ def get_azure_credential(self, client_id=None):
123128
Credential object: Either DefaultAzureCredential or ManagedIdentityCredential.
124129
"""
125130
if self.APP_ENV == "dev":
126-
return DefaultAzureCredential() # CodeQL [SM05139]: DefaultAzureCredential is safe here
131+
return DefaultAzureCredential(exclude_environment_credential=True) # CodeQL [SM05139]: DefaultAzureCredential is safe here
127132
else:
128133
return ManagedIdentityCredential(client_id=client_id)
129134

135+
def get_azure_credential_async(self, client_id=None):
136+
"""
137+
Returns an async Azure credential based on the application environment.
138+
139+
If the environment is 'dev', it uses DefaultAzureCredential (async) with exclude_environment_credential=True
140+
to avoid EnvironmentCredential exceptions in Application Insights traces.
141+
Otherwise, it uses ManagedIdentityCredential (async).
142+
143+
Args:
144+
client_id (str, optional): The client ID for the Managed Identity Credential.
145+
146+
Returns:
147+
Async Credential object: Either DefaultAzureCredentialAsync or ManagedIdentityCredentialAsync.
148+
"""
149+
if self.APP_ENV == "dev":
150+
return DefaultAzureCredentialAsync(exclude_environment_credential=True)
151+
else:
152+
return ManagedIdentityCredentialAsync(client_id=client_id)
153+
130154
def get_azure_credentials(self):
131155
"""Retrieve Azure credentials, either from environment variables or managed identity."""
132156
if self._azure_credentials is None:

0 commit comments

Comments
 (0)