Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a Custom Log Inspection Tool with Django and Nginx

Tech 1

Implementing a centralized log viewer allows for efficient diagnostics without the overhead of complex database-driven analysis platforms. This approach utilizes Nginx paired with uWSGI to serve a Django application, bypassing the configuration difficulties often encountered with Apache. The system reads flat log files directly from the filesystem based on user-defined dates and keywords.

Project Configuration

The Django configuration is adjusted to serve static assets and define the search application module. Static file handling is explicitly mapped to the project directory.

# settings.py
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = 'change_this_to_a_random_string'

DEBUG = False
ALLOWED_HOSTS = ['*']

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'log_viewer',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'log_project.urls'
WSGI_APPLICATION = 'log_project.wsgi.application'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

URL Routing

The routing structure handles authentication, the search dashboard, and logout actions.

# urls.py
from django.conf.urls import url
from django.contrib import admin
from log_viewer import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.auth_view, name='login'),
    url(r'^dashboard/', views.search_view, name='dashboard'),
    url(r'^signout/', views.logout_view, name='logout'),
]

View Logic

The core logic involves authenticating users and executing system commands to search specific log directories. The application constructs a file path based on the selected date and uses grep to filter content.

# views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django import forms
from django.contrib.admin.widgets import AdminDateWidget
import subprocess
import os
from datetime import datetime

class AuthForm(forms.Form):
    user_id = forms.CharField(max_length=100)
    pass_key = forms.CharField(widget=forms.PasswordInput)

class LogSearchForm(forms.Form):
    query = forms.CharField(label="Search Query", min_length=1)
    log_date = forms.DateTimeField(label='Log Date', widget=AdminDateWidget())

def auth_view(request):
    if request.user.is_authenticated:
        return redirect('/dashboard/')
    
    msg = ""
    if request.method == 'POST':
        form = AuthForm(request.POST)
        if form.is_valid():
            uid = form.cleaned_data['user_id']
            pwd = form.cleaned_data['pass_key']
            user = authenticate(username=uid, password=pwd)
            if user and user.is_active:
                login(request, user)
                return redirect('/dashboard/')
            else:
                msg = "Invalid credentials"
    else:
        form = AuthForm()
    return render(request, 'login.html', {'form': form, 'msg': msg})

def search_view(request):
    if not request.user.is_authenticated:
        return redirect('/')
    
    context = {'form': LogSearchForm(initial={'log_date': datetime.now()})}
    
    if request.method == 'POST':
        form = LogSearchForm(request.POST)
        if form.is_valid():
            keyword = form.cleaned_data['query']
            target_date = form.cleaned_data['log_date']
            
            # Prevent command injection characters
            if '|' in keyword:
                context['error'] = "Illegal character '|' detected."
                return render(request, 'dashboard.html', context)

            # Construct path: /var/logs/YYYY/MM/DD
            date_path = os.path.join(
                '/var/logs/', 
                str(target_date.year), 
                f"{target_date.month:02d}", 
                f"{target_date.day:02d}"
            )

            if not os.path.exists(date_path):
                context['error'] = "No logs found for this date."
                return render(request, 'dashboard.html', context)

            # Execute grep command
            file_pattern = os.path.join(date_path, '*')
            cmd = f"sudo grep '{keyword}' {file_pattern}"
            
            process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout, stderr = process.communicate()
            
            results = stdout.decode('utf-8').split('\n')
            # Filter empty lines
            clean_results = [line for line in results if line.strip()]
            
            context['results'] = clean_results
            context['form'] = form

    return render(request, 'dashboard.html', context)

def logout_view(request):
    logout(request)
    return render(request, 'login.html')

HTML Templates

The interface relies on Bootstrap for layout.

Base Template html {% load staticfiles %}

Login Template html {% extends "base.html" %} {% block content %}

Dashboard Template html {% extends "base.html" %} {% block content %}

        {% if error %}
            <div class="alert alert-warning" style="margin-top: 20px;">{{ error }}</div>
        {% endif %}

        {% if results %}
        <div style="margin-top: 20px;">
            <table class="table table-striped table-bordered">
                <thead>
                    <tr>
                        <th width="5%">#</th>
                        <th>Log Entry</th>
                    </tr>
                </thead>
                <tbody>
                    {% for line in results %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td><code>{{ line }}</code></td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
        {% endif %}
    </div>
</div>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.