Note: I'm migrating from gonzalo123.com to here. When I finish I'll swap the DNS to here. The "official" blog will be always gonzalo123.com

      Monitoring Django applications with Grafana and Kibana using Prometheus and Elasticsearch

      When we’ve one application we need to monitor the logs in one way or another. Not only the server’s logs (500 errors, response times and things like that). Sometimes the user complains about the application. Without logs we cannot do anything. We can save logs within files and let grep and tail do the magic. This’s assumable with a single on-premise server, but nowadays with clouds and docker this’s a nightmare. We need a central log collector to collect all the logs of the application and use this collector to create alerts, and complex searches of our application logs.

      I normally work with AWS. In AWS we’ve CloudWatch. It’s pretty straightforward to connect our application logs to CloudWatch when we’re using AWS. When we aren’t using AWS we can use the ELK stack. In this example we’re going to send our Django application logs to a Elasticsearch database. Let’s start:

      The idea is not to send the logs directly. The idea save the logs to log files. We can use this LOGGING configuration to do that:

      LOGGING = {
          'version': 1,
          'disable_existing_loggers': False,
          'formatters': {
              "json": {
                  '()': CustomisedJSONFormatter,
              },
          },
          'handlers': {
              'console': {
                  'class': 'logging.StreamHandler',
              },
              'app_log_file': {
                  'level': LOG_LEVEL,
                  'class': 'logging.handlers.RotatingFileHandler',
                  'filename': os.path.join(LOG_PATH, 'app.log.json'),
                  'maxBytes': 1024 * 1024 * 15,  # 15MB
                  'backupCount': 10,
                  'formatter': 'json',
              },
          },
          'root': {
              'handlers': ['console', 'app_log_file'],
              'level': LOG_LEVEL,
          },
      }
      

      Here I’m using a custom JSON formatter:

      import json_log_formatter
      import logging
      from django.utils.timezone import now
      
      
      class CustomisedJSONFormatter(json_log_formatter.JSONFormatter):
          def json_record(self, message: str, extra: dict, record: logging.LogRecord):
              extra['name'] = record.name
              extra['filename'] = record.filename
              extra['funcName'] = record.funcName
              extra['msecs'] = record.msecs
              if record.exc_info:
                  extra['exc_info'] = self.formatException(record.exc_info)
      
              return {
                  'message': message,
                  'timestamp': now(),
                  'level': record.levelname,
                  'context': extra
              }
      

      With this configuration our logs are going to be something like that:

      {"message": "Hello from log", "timestamp": "2020-04-26T19:35:59.427098+00:00", "level": "INFO", "app_id": "Logs", "context": {"random": 68, "name": "app.views", "filename": "views.py", "funcName": "index", "msecs": 426.8479347229004}}
      

      Now we’re going to use logstash as data shipper to send the logs to elastic search. We need to create a pipeline:

      input {
          file {
              path => "/logs/*"
              start_position => "beginning"
              codec => "json"
          }
      }
      
      output {
        elasticsearch {
              index => "app_logs"
              hosts => ["elasticsearch:9200"]
          }
      }
      

      We’re going to use Docker to build our stack, so our logstash and our django containers will share the logs volumes.

      Now we need to visualize the logs. Kibana is perfect for this task. We can set up a Kibana server connected to the Elasticsearch and visualize the logs:

      Also we can monitor our server performance. Prometheus is the de facto standard for doing that. In fact it’s very simple to connect our Django application to Prometheus. We only need to add django-prometheus dependency, install the application and set up two middlewares:

      INSTALLED_APPS = [
         ...
         'django_prometheus',
         ...
      ]
      
      MIDDLEWARE = [
          'django_prometheus.middleware.PrometheusBeforeMiddleware', # <-- this one
          'app.middleware.RequestLogMiddleware',
          'django.middleware.security.SecurityMiddleware',
          'django.contrib.sessions.middleware.SessionMiddleware',
          'django.middleware.common.CommonMiddleware',
          'django.middleware.csrf.CsrfViewMiddleware',
          'django.contrib.auth.middleware.AuthenticationMiddleware',
          'django.contrib.messages.middleware.MessageMiddleware',
          'django.middleware.clickjacking.XFrameOptionsMiddleware',
          'django_prometheus.middleware.PrometheusAfterMiddleware', # <-- this one
      ]
      

      also we need to set up some application routes

      from django.contrib import admin
      from django.urls import path, include
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          path('p/', include('django_prometheus.urls')), # <-- prometheus routes
          path('', include('app.urls'))
      ]
      

      The easiest way to visualize the data stored in prometheus is using Grafana. In Grafana we need to create a datasource with Prometheus and build our custom dashboard. We can import pre-built dashboards. For example this one: https://grafana.com/grafana/dashboards/9528

      Here the docker-compose file with all the project:

      version: '3'
      services:
        web:
          image: web:latest
          restart: always
          command: /bin/bash ./docker-entrypoint.sh
          volumes:
            - static_volume:/src/staticfiles
            - logs_volume:/src/logs
          environment:
            DEBUG: 'False'
            LOG_LEVEL: DEBUG
      
        nginx:
          image: nginx:latest
          restart: always
          volumes:
            - static_volume:/src/staticfiles
          ports:
            - 80:80
          depends_on:
            - web
            - grafana
      
        prometheus:
          image: prometheus:latest
          restart: always
          build:
            context: .docker/prometheus
            dockerfile: Dockerfile
      
        grafana:
          image: grafana:latest
          restart: always
          depends_on:
            - prometheus
          environment:
            - GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER}
            - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
            - GF_USERS_DEFAULT_THEME=${GF_USERS_DEFAULT_THEME}
            - GF_USERS_ALLOW_SIGN_UP=${GF_USERS_ALLOW_SIGN_UP}
            - GF_USERS_ALLOW_ORG_CREATE=${GF_USERS_ALLOW_ORG_CREATE}
            - GF_AUTH_ANONYMOUS_ENABLED=${GF_AUTH_ANONYMOUS_ENABLED}
      
        logstash:
          image: logstash:latest
          restart: always
          depends_on:
            - elasticsearch
          volumes:
            - logs_volume:/logs:ro
      
        elasticsearch:
          image: docker.elastic.co/elasticsearch/elasticsearch:7.5.2
          restart: always
          environment:
            - discovery.type=single-node
            - http.host=0.0.0.0
            - xpack.security.enabled=false
            - ES_JAVA_OPTS=-Xms750m -Xmx750m
          volumes:
            - elasticsearch_volume:/usr/share/elasticsearch/data
      
        kibana:
          image: kibana:latest
          restart: always
          ports:
            - 5601:5601
          depends_on:
            - elasticsearch
      volumes:
        elasticsearch_volume:
        static_volume:
        logs_volume:
        grafana_data:
      

      And that’s all. Our Django application up and running fully monitored.

      Source code in my github

      comments powered by Disqus