{
  "steward": "python-otl-steward",
  "project": "Azure-AI-RAG-CSharp-Semantic-Kernel-Functions",
  "runDate": "2026-03-22",
  "runId": "2026-03-22T00-00-00",
  "findings": [
    {
      "id": "PYOTL-ERR-001",
      "title": "Blob delete outside try/except causes NameError masking original failure",
      "severity": "critical",
      "category": "ERR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 99,
      "description": "Lines 99-101 execute unconditionally outside the main try/except block. If an exception is caught on the main processing path, blob_service_client and container_name are undefined, causing a NameError that propagates to the Functions runtime and masks the original failure in Application Insights telemetry.",
      "recommendation": "Move blob cleanup into a finally block or guard with explicit existence checks. Use try/finally to ensure cleanup always runs but does not mask the original exception.",
      "status": "open"
    },
    {
      "id": "PYOTL-MON-001",
      "title": "APPINSIGHTS_INSTRUMENTATIONKEY used instead of APPLICATIONINSIGHTS_CONNECTION_STRING",
      "severity": "notable",
      "category": "MON",
      "file": "infra/app/loader-function.bicep",
      "line": 75,
      "description": "The function app is configured with APPINSIGHTS_INSTRUMENTATIONKEY (legacy). Microsoft recommends APPLICATIONINSIGHTS_CONNECTION_STRING for all new workloads. The connection string supports sovereign clouds, future transport changes, and is already produced as an output by applicationinsights.bicep but not consumed here.",
      "recommendation": "Replace APPINSIGHTS_INSTRUMENTATIONKEY with APPLICATIONINSIGHTS_CONNECTION_STRING in loader-function.bicep and wire in the connectionString output from applicationinsights.bicep.",
      "status": "open"
    },
    {
      "id": "PYOTL-LOG-001",
      "title": "Root logger used instead of named module logger",
      "severity": "notable",
      "category": "LOG",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 35,
      "description": "All log calls use the root logger (logging.info, logging.error) rather than a named logger (logging.getLogger(__name__)). This prevents per-module log level filtering, makes log source identification in Application Insights ambiguous, and is not idiomatic for Azure Functions Python apps.",
      "recommendation": "Add logger = logging.getLogger(__name__) at module level and replace all root-logger calls with logger.info(...), logger.error(...), etc.",
      "status": "open"
    },
    {
      "id": "PYOTL-ERR-002",
      "title": "Exceptions logged as two separate records instead of using logging.exception()",
      "severity": "notable",
      "category": "ERR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 93,
      "description": "The except blocks log the error message and the traceback as two separate log.error() calls using traceback.format_exc(). Application Insights receives two unlinked log records rather than a single exception telemetry item with an attached stack trace, degrading exception search and alerting.",
      "recommendation": "Replace the two-call pattern with logging.exception('message') or logging.error('message', exc_info=True) to produce a single correlated exception telemetry item.",
      "status": "open"
    },
    {
      "id": "PYOTL-LOG-002",
      "title": "Full document content logged at INFO — potential PII exposure in Application Insights",
      "severity": "notable",
      "category": "LOG",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 68,
      "description": "logging.info(f'Blob content as JSON: {json_data}') logs the entire parsed document including title, description, resolution steps, reference code, and product ID. If documents contain PII (customer names, account identifiers), this data is written to Application Insights and retained in Log Analytics.",
      "recommendation": "Replace the full-document log with a safe summary: log only the reference_code and title fields, never the full content.",
      "status": "open"
    },
    {
      "id": "PYOTL-TRC-001",
      "title": "Blob name absent from error log messages — hinders cross-document error correlation",
      "severity": "notable",
      "category": "TRC",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 93,
      "description": "Error log messages (lines 93, 96, 184) do not include myblob.name. When multiple blobs fail concurrently or in sequence, it is impossible to correlate error telemetry to a specific document in Application Insights without joining on invocation ID, which is fragile.",
      "recommendation": "Include blob_name in all error messages. Consider using a logging.LoggerAdapter with {'blob_name': myblob.name} as extra context so blob identity appears in every log record from the function invocation.",
      "status": "open"
    },
    {
      "id": "PYOTL-LOG-003",
      "title": "F-string formatting in log calls instead of lazy % formatting",
      "severity": "minor",
      "category": "LOG",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 35,
      "description": "Most log calls use f-strings (e.g., logging.info(f'Name: {myblob.name}')). F-strings evaluate unconditionally at the call site regardless of the active log level. This causes unnecessary string interpolation when log output is suppressed and can raise exceptions if the formatted value is an uninitialized variable.",
      "recommendation": "Use lazy % formatting: logging.info('Name: %s size: %d', myblob.name, myblob.length). This is the recommended style per the Python logging HOWTO.",
      "status": "open"
    },
    {
      "id": "PYOTL-ERR-003",
      "title": "AI Search error log missing exc_info — no stack trace attached to log record",
      "severity": "minor",
      "category": "ERR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 184,
      "description": "self.logger.error('Error in AI Search: %s', ex) does not pass exc_info=True. The log record carries only the exception message string, not the full stack trace. Application Insights will not associate this log entry with an exception telemetry item.",
      "recommendation": "Change to self.logger.error('Error in AI Search: %s', ex, exc_info=True) or self.logger.exception('Error in AI Search: %s', ex).",
      "status": "open"
    },
    {
      "id": "PYOTL-LOG-004",
      "title": "Index-not-found logged at INFO instead of WARNING",
      "severity": "minor",
      "category": "LOG",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 128,
      "description": "self.logger.info('AI Search index not found, creating index...') uses INFO level. The absence of the index is an unexpected initial state that triggers index creation. WARNING is more appropriate to signal that a prerequisite was missing and remediated automatically.",
      "recommendation": "Change to self.logger.warning('AI Search index not found; creating index. index_name=%s', self.index_name) for better alerting granularity.",
      "status": "open"
    },
    {
      "id": "PYOTL-MON-002",
      "title": "Application Insights and Log Analytics are provisioned and diagnostic settings are configured",
      "severity": "info",
      "category": "MON",
      "description": "The Bicep infra provisions an Application Insights instance backed by a Log Analytics workspace. Diagnostic settings on the function app forward FunctionAppLogs and AllMetrics to Log Analytics. This provides a solid infrastructure baseline for observability.",
      "recommendation": "No action required. Upgrade the instrumentation key to a connection string (see PYOTL-MON-001) to complete the modernisation.",
      "status": "open"
    },
    {
      "id": "PYOTL-MON-003",
      "title": "No print() statements used — logging module is used throughout",
      "severity": "info",
      "category": "MON",
      "description": "The function_app.py file uses the Python logging module exclusively. No print() statements were found. This is the correct approach for Azure Functions Python apps.",
      "recommendation": "No action required.",
      "status": "open"
    }
  ],
  "summary": {
    "critical": 1,
    "notable": 5,
    "minor": 3,
    "info": 2,
    "total": 11
  }
}
