|
16 | 16 |
|
17 | 17 | from quart import Quart, request, jsonify, Response |
18 | 18 | from quart_cors import cors |
| 19 | +from opentelemetry import trace |
19 | 20 |
|
20 | 21 | from settings import app_settings |
21 | 22 | from models import CreativeBrief, Product |
|
24 | 25 | from services.blob_service import get_blob_service |
25 | 26 | from services.title_service import get_title_service |
26 | 27 | from api.admin import admin_bp |
| 28 | +from azure.core.settings import settings as azure_settings |
27 | 29 | from azure.monitor.opentelemetry import configure_azure_monitor |
| 30 | +from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware |
28 | 31 |
|
29 | 32 | # In-memory task storage for generation tasks |
30 | 33 | # In production, this should be replaced with Redis or similar |
|
35 | 38 | level=logging.INFO, |
36 | 39 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
37 | 40 | ) |
38 | | -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) |
39 | 41 | logger = logging.getLogger(__name__) |
40 | 42 |
|
| 43 | +# Create Quart app |
| 44 | +app = Quart(__name__) |
| 45 | +app = cors(app, allow_origin="*") |
| 46 | + |
41 | 47 | # Check if the Application Insights connection string is set in the environment variables |
42 | 48 | appinsights_connection_string = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING") |
43 | 49 | if appinsights_connection_string: |
44 | 50 | # Configure Application Insights if the connection string is found |
45 | | - configure_azure_monitor(connection_string=appinsights_connection_string) |
| 51 | + # logging_level=WARNING sends only WARNING/ERROR/CRITICAL to App Insights |
| 52 | + # (INFO traces like "Loaded product", "Uploaded image", workflow steps stay in container logs only) |
| 53 | + configure_azure_monitor( |
| 54 | + connection_string=appinsights_connection_string, |
| 55 | + enable_live_metrics=False, |
| 56 | + logging_level=logging.WARNING, |
| 57 | + ) |
| 58 | + # Disable Azure SDK native span creation (ContainerProxy.*, BlobClient.* InProc spans) |
| 59 | + azure_settings.tracing_implementation = None |
| 60 | + # Apply ASGI middleware for request tracing (Quart is not auto-instrumented by configure_azure_monitor) |
| 61 | + app.asgi_app = OpenTelemetryMiddleware( |
| 62 | + app.asgi_app, |
| 63 | + exclude_spans=["receive", "send"], |
| 64 | + excluded_urls="api/generate/status", |
| 65 | + ) |
46 | 66 | logger.info("Application Insights configured with the provided connection string") |
47 | 67 | else: |
48 | 68 | # Log a warning if the connection string is not found |
49 | 69 | logger.warning("No Application Insights connection string found. Skipping configuration") |
50 | 70 |
|
51 | | -# Create Quart app |
52 | | -app = Quart(__name__) |
53 | | -app = cors(app, allow_origin="*") |
54 | | - |
55 | 71 | # Register blueprints |
56 | 72 | app.register_blueprint(admin_bp) |
57 | 73 |
|
58 | 74 |
|
| 75 | +@app.before_request |
| 76 | +async def set_conversation_context(): |
| 77 | + """Attach conversation_id and user_id to the current OTel span for App Insights.""" |
| 78 | + conversation_id = "" |
| 79 | + user_id = "" |
| 80 | + |
| 81 | + # 1. Extract from JSON body (POST requests) |
| 82 | + if request.content_type and "json" in request.content_type: |
| 83 | + try: |
| 84 | + data = await request.get_json() |
| 85 | + if data and isinstance(data, dict): |
| 86 | + conversation_id = data.get("conversation_id", "") |
| 87 | + user_id = data.get("user_id", "") |
| 88 | + except Exception: |
| 89 | + pass |
| 90 | + |
| 91 | + # 2. Extract from URL path parameters (e.g. /api/conversations/<conversation_id>) |
| 92 | + if not conversation_id and request.view_args: |
| 93 | + conversation_id = request.view_args.get("conversation_id", "") |
| 94 | + |
| 95 | + # 3. Extract from query parameters (e.g. ?conversation_id=xxx) |
| 96 | + if not conversation_id: |
| 97 | + conversation_id = request.args.get("conversation_id", "") |
| 98 | + |
| 99 | + if not user_id: |
| 100 | + user_id = request.args.get("user_id", "") or request.headers.get("X-Ms-Client-Principal-Id", "anonymous") |
| 101 | + |
| 102 | + span = trace.get_current_span() |
| 103 | + if span.is_recording(): |
| 104 | + span.set_attribute("conversation_id", conversation_id) |
| 105 | + span.set_attribute("user_id", user_id) |
| 106 | + |
| 107 | + |
59 | 108 | # ==================== Authentication Helper ==================== |
60 | 109 |
|
61 | 110 | def get_authenticated_user(): |
|
0 commit comments