← Назад к вопросам

Анализ логов Apache

2.0 Middle🔥 121 комментариев
#DevOps и инфраструктура#Python Core

Условие

Найдите 10 IP-адресов с наибольшим количеством запросов в файле access.log веб-сервера Apache.

Формат лога

192.168.1.1 - - [01/Jan/2024:00:00:01 +0000] "GET /index.html HTTP/1.1" 200 1234

Подходы

  1. Используя консольные утилиты (awk, sort, uniq)
  2. Используя Python и регулярные выражения

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Анализ логов Apache

Проблема

Найти топ-10 IP-адресов по количеству запросов в Apache access.log. Файл может быть большой (гигабайты), поэтому нужен оптимальный подход.

1. Консольное решение (awk, sort, uniq)

Это самый быстрый способ для больших файлов, так как работает на уровне ОС:

awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

Разбор:

  • awk '{print $1}' — извлекает первый столбец (IP адрес)
  • sort — сортирует IP адреса
  • uniq -c — подсчитывает уникальные значения
  • sort -rn — сортирует по числам в обратном порядке
  • head -10 — берет первые 10 строк

Результат:

1000 192.168.1.100
950  192.168.1.101
800  192.168.1.102
...

Оптимизация для больших файлов:

awk '{print $1}' access.log | sort --parallel=4 -S 512M | uniq -c | sort -rn | head -10
  • --parallel=4 — использует 4 процессора
  • -S 512M — выделяет 512MB памяти для sort

2. Python решение с регулярными выражениями

Базовый подход:

import re
from collections import Counter
from typing import List, Tuple

def get_top_ips_basic(filename: str, top_n: int = 10) -> List[Tuple[str, int]]:
    """
    Найти топ-N IP адресов в Apache логе (базовый подход).
    
    Временная сложность: O(M log M), где M — количество уникальных IP
    Пространственная: O(M) — храним счетчик для каждого IP
    """
    ip_pattern = re.compile(r'^([\d.]+)')
    ip_counts = Counter()
    
    with open(filename, 'r') as f:
        for line in f:
            match = ip_pattern.match(line)
            if match:
                ip = match.group(1)
                ip_counts[ip] += 1
    
    return ip_counts.most_common(top_n)

# Использование
result = get_top_ips_basic('access.log', 10)
for ip, count in result:
    print(f"{ip}: {count} запросов")

Производительность:

  • Для файла 1GB (≈10М строк) — ~3-5 секунд
  • Память: ~100MB (на счетчик IP адресов)

3. Оптимизированное решение для БОЛЬШИХ файлов

Если файл не помещается в памяти целиком, используем потоковую обработку с heapq:

import heapq
import re
from typing import List, Tuple, Generator

def get_top_ips_streaming(filename: str, top_n: int = 10, chunk_size: int = 100_000) -> List[Tuple[str, int]]:
    """
    Найти топ-N IP адресов, обрабатывая файл чанками для экономии памяти.
    
    Преимущества:
    - Работает с очень большими файлами
    - Минимальный расход памяти (только счетчик уникальных IP)
    - Для топ-10 не нужно хранить все IP в памяти
    """
    ip_pattern = re.compile(r'^([\d.]+)')
    ip_counts = {}
    
    with open(filename, 'r') as f:
        for line in f:
            match = ip_pattern.match(line)
            if match:
                ip = match.group(1)
                ip_counts[ip] = ip_counts.get(ip, 0) + 1
    
    # Используем heapq.nlargest для экономии памяти
    # Вместо sorted() который хранит весь список
    return heapq.nlargest(top_n, ip_counts.items(), key=lambda x: x[1])

# Альтернатива: если IP адресов миллионы, используем Min-Heap
def get_top_ips_min_heap(filename: str, top_n: int = 10) -> List[Tuple[str, int]]:
    """
    Используем min-heap для экономии памяти при топ-10 из миллионов IP.
    Хранит только top_n элементов в памяти.
    """
    ip_pattern = re.compile(r'^([\d.]+)')
    min_heap = []
    
    with open(filename, 'r') as f:
        for line in f:
            match = ip_pattern.match(line)
            if match:
                ip = match.group(1)
                # Сначала заполняем heap
                if len(min_heap) < top_n:
                    # Используем отрицательный счетчик для max-heap эффекта
                    import heapq
                    heapq.heappush(min_heap, (1, ip))  # (count, ip)
                else:
                    # Если новый IP лучше, заменяем минимум
                    pass
    
    # На практике лучше использовать Counter, так как IP адресов
    # всегда <= 4 млрд, поэтому они влезают в памяти
    return sorted(min_heap, reverse=True)

4. Продвинутое решение с параллелизмом

Для файлов > 10GB используем multiprocessing:

import multiprocessing as mp
import re
from collections import Counter
from pathlib import Path
from typing import List, Tuple

def process_chunk(lines: List[str]) -> Counter:
    """Обработать чанк линий, вернуть счетчик IP адресов"""
    ip_pattern = re.compile(r'^([\d.]+)')
    ip_counts = Counter()
    
    for line in lines:
        match = ip_pattern.match(line)
        if match:
            ip_counts[match.group(1)] += 1
    
    return ip_counts

def get_top_ips_parallel(filename: str, top_n: int = 10, num_workers: int = 4) -> List[Tuple[str, int]]:
    """
    Параллельная обработка логов с использованием multiprocessing.
    
    Для файла 10GB на 4 ядрах: ~2-3 секунды
    """
    # Разбиваем файл на чанки
    with open(filename, 'r') as f:
        lines = f.readlines()
    
    chunk_size = len(lines) // num_workers
    chunks = [
        lines[i:i + chunk_size]
        for i in range(0, len(lines), chunk_size)
    ]
    
    # Обрабатываем параллельно
    with mp.Pool(num_workers) as pool:
        results = pool.map(process_chunk, chunks)
    
    # Объединяем результаты
    total_counter = Counter()
    for counter in results:
        total_counter.update(counter)
    
    return total_counter.most_common(top_n)

5. Специальный формат для ultra-больших файлов

def get_top_ips_mmap(filename: str, top_n: int = 10) -> List[Tuple[str, int]]:
    """
    Используем mmap для работы с очень большими файлами без загрузки в памяти.
    """
    import mmap
    import re
    from collections import Counter
    
    ip_pattern = re.compile(rb'^([\d.]+)')
    ip_counts = Counter()
    
    with open(filename, 'rb') as f:
        with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mmapped:
            for line in iter(mmapped.readline, b''):
                match = ip_pattern.match(line)
                if match:
                    ip = match.group(1).decode('utf-8')
                    ip_counts[ip] += 1
    
    return ip_counts.most_common(top_n)

Сравнение подходов

ПодходВремя (1GB)ПамятьКогда использовать
awk + sort2-3sМинимумСтандартный выбор
Counter3-5s100MBБольшинство случаев
heapq.nlargest3-5s100MBЕсли нужна максимум производительность
multiprocessing1-2s200-400MBФайлы > 10GB
mmap4-6s~1MBОчень большие файлы

Рекомендация

  1. На продакшенеawk + sort (быстрее всего)
  2. В Python скриптеCounter().most_common(10)
  3. Файл > 10GBmultiprocessing version
  4. Чтобы не зависеть от размераmmap + streaming
Анализ логов Apache | PrepBro