Search code examples
pythondockerhttprequesthttp-proxy

Log response times for all HTTP requests from a docker container


I have built a docker image that contains a pre-built application that I cannot add logging statements to. The application makes many HTTP requests as it executes, and I would like to log those requests, and in particular, the time it takes for those responses to be fulfilled. Is there something I add to my docker container, such as a proxy, that would allow me to log response times to all outgoing HTTP requests?


Solution

  • Log outgoing requests

    To log outgoing requests, you can set up Nginx as a forward proxy.

    To illustrate, I've created this Flask app which makes a request to Google when we hit the / endpoint.

    from flask import Flask
    import requests
    
    app = Flask(__name__)
    
    @app.route("/")
    def hello_world():
        r = requests.get('http://www.google.com')
        return r.text
    

    and Dockerfile

    FROM python:latest
    WORKDIR /app
    RUN pip install flask requests
    COPY hello.py .
    CMD flask --app hello run --host=0.0.0.0
    

    We then configure Nginx to listen on port 80 and pass all requests on to the host indicated in the request

    log_format upstream_time '$remote_addr - $remote_user [$time_local] '
                                 '"$request" $status $body_bytes_sent '
                                 '"$http_referer" "$http_user_agent" '
                                 '"$upstream_response_time"';
    
    server {
      listen 80;
      location / {
        resolver 8.8.8.8;
        proxy_pass http://$http_host$uri$is_args$args;
      }
      access_log /var/log/nginx/access.log upstream_time;
    }
    

    I've added a custom log format that logs the response time from the server we're calling.

    We can then orchestrate the 2 containers using Docker Compose like this. Note that we're setting the http_proxy environment variable for the application container. That causes it to pass all outgoing requests to the proxy container instead of calling directly.

    version: '3'
    
    services:
      app:
        build: .
        ports:
          - 5000:5000
        environment:
          - http_proxy=http://nginx/
      nginx:
        image: nginx:latest
        volumes:
          - ./nginx.conf:/etc/nginx/conf.d/default.conf
    

    Now we can run the containers, make a request and check the log with

    docker compose up -d
    curl http://localhost:5000/
    docker compose logs nginx
    

    At the end of the log messages, you'll see the response time from the server (in the example, it's Google). My log looks like this and shows a response time of 112ms.

    172.24.0.3 - - [10/Oct/2023:20:14:46 +0000] "GET http://www.google.com/ HTTP/1.1" 200 8264 "-" "python-requests/2.31.0" "0.112"
    

    Log incoming requests

    If you want to log incoming requests, you can set up Nginx as a reverse proxy.

    To create an app to measure, I've used this Dockerfile that creates a template C# web API application that listens on port 80 and responds to requests on /weatherforecast. I've used this since I'm familiar with it and it's easy to set up. You should use your application instead.

    FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
    WORKDIR /src
    RUN dotnet new webapi -n myapp -o . --no-https
    RUN dotnet publish -o /app
    
    FROM mcr.microsoft.com/dotnet/aspnet:7.0
    WORKDIR /app
    COPY --from=build /app /app
    CMD ["dotnet", "myapp.dll"]
    

    We'll orchestrate the application and Nginx using this docker-compose.yml file

    version: '3'
    
    services:
      myapp:
        build: .
      nginx:
        image: nginx:latest
        ports:
          - 8000:80
        volumes:
          - ./nginx.conf:/etc/nginx/conf.d/default.conf
    

    This runs the application ('myapp') and Nginx. Nginx listens on the host's port 8000 and is configured with a file called nginx.conf which looks like this:

    log_format upstream_time '$remote_addr - $remote_user [$time_local] '
                                 '"$request" $status $body_bytes_sent '
                                 '"$http_referer" "$http_user_agent" '
                                 '"$upstream_response_time"';
    server {
      listen 80;
      location / {
        proxy_pass http://myapp/;
      }
      access_log /var/log/nginx/access.log upstream_time;
    }
    

    In this file we've specified a custom log format for Nginx that includes $upstream_response_time as the last part of the log entries. Nginx is also configured to pass all requests on to the myapp application.

    Run it, make a couple of requests and look at the Nginx log using

    docker compose up -d
    curl http://localhost:8000/weatherforecast
    curl http://localhost:8000/weatherforecast
    docker compose logs nginx
    

    My output looks like this, with the response times logged as 68 ms and 3 ms for the two requests (the response times are at the end of the log entries)

    nginx_1  | 172.22.0.1 - - [07/Oct/2023:09:51:16 +0000] "GET /weatherforecast HTTP/1.1" 200 394 "-" "curl/7.81.0" "0.068"
    nginx_1  | 172.22.0.1 - - [07/Oct/2023:09:52:13 +0000] "GET /weatherforecast HTTP/1.1" 200 393 "-" "curl/7.81.0" "0.003"