API Development FastAPI Logging Performance Azure

Optimizing FastAPI Audit Logging: Balancing Performance and Compliance

A journey from blocking middleware to high-performance audit logging while maintaining security requirements.

Challenge

Lack of audit logging

Must integrate with Azure Application Insights
Constraint: unchanged endpoint performance

Solution

Async logging with Azure Application Insights

<1ms
Final Response Time

Impact

Full audit compliance with minimal overhead

100%
Audit Coverage

Introduction

When building enterprise APIs, we often face competing requirements: comprehensive audit logging for security and compliance, while maintaining high-performance responses for end-users. This was exactly the challenge I faced when implementing a FastAPI-based data exchange API that needed to log detailed audit information to Azure Application Insights.

In this post, I'll walk you through my journey from a basic blocking implementation to a high-performance solution that maintains full audit capabilities without compromising user experience.

Setting Up Basic Audit Logging

Initial Requirements

The audit logging system needed to capture:

  • Request details (method, path, query parameters)
  • User information from Azure AD tokens
  • Request body content for POST/PUT operations
  • Response status and timing
  • Geographic location and client IP

First Implementation

Our initial approach used FastAPI's middleware system with a basic logging setup:


class AuditLoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Basic logging implementation
        start_time = time()
        response = await call_next(request)
        duration = time() - start_time
        
        logging.info(f"Request: {request.method} {request.url.path}")
        return response
                                    

While this worked, it lacked structured logging and proper integration with Azure Application Insights.

Integrating with Azure Application Insights

The next step was creating a custom handler to properly format our logs for Azure Application Insights:


class ApplicationInsightsHandler(AzureLogHandler):
    def __init__(self, connection_string):
        super().__init__(connection_string=connection_string)
        
    def emit(self, record):
        if hasattr(record, "custom_dimensions"):
            self.add_telemetry_processor(
                self._add_custom_dimensions(record.custom_dimensions)
            )
        super().emit(record)
                                

This allowed us to send structured data with custom dimensions, making our logs more queryable and valuable for analysis.

Addressing Performance Bottlenecks

Our initial implementation had a significant issue: it was blocking the request while gathering all the audit information. With complex token decoding and body parsing, some requests were taking over 200ms just for logging!

The solution came in three parts:

  1. Parallel processing of the audit information
  2. Asynchronous logging
  3. Careful request body handling

The Optimized Solution

Our final implementation achieved both goals: comprehensive audit logging and minimal impact on response times. Key improvements included:

  • Immediate request forwarding to reduce latency
  • Asynchronous token processing
  • Efficient body handling
  • Structured logging with custom dimensions

The results were impressive: audit logging overhead dropped from 200+ms to less than 1ms per request.

Key Takeaways

This journey taught several valuable lessons:

  • Always measure performance impact of middleware
  • Use async operations whenever possible
  • Structure your logs for easy querying
  • Consider the end-user experience in every decision