Загрузка данных


# Константа для черного списка IP-адресов
BLACKLIST_IPS = {"85.234.12.1"}


def parse_log_line(line: str) -> dict | None:
    """
    Парсит строку лога и возвращает словарь с данными.
    Возвращает None, если строка повреждена.
    """
    parts = line.split()
    
    # Проверяем, что строка содержит достаточно элементов
    if len(parts) < 7:
        return None
    
    try:
        # Извлекаем IP (0-й элемент)
        ip = parts[0]
        
        # Извлекаем пользователя (2-й элемент)
        user = parts[2] if parts[2] != "-" else None
        
        # Извлекаем метод (4-й элемент, очищаем от кавычек)
        method = parts[4].strip('"')
        
        # Извлекаем путь (5-й элемент)
        path = parts[5]
        
        # Извлекаем код ответа (последний элемент)
        status_code = int(parts[-1])
        
        return {
            "ip": ip,
            "user": user,
            "method": method,
            "path": path,
            "status_code": status_code
        }
    except (ValueError, IndexError):
        # Если возникла ошибка (например, код ответа не число)
        return None


def is_suspicious(log_dict: dict, blacklist: set) -> bool:
    """
    Проверяет, является ли запись подозрительной.
    """
    if not log_dict:
        return False
    
    # Проверка IP в черном списке
    if log_dict["ip"] in blacklist:
        return True
    
    # Проверка кода ответа
    if log_dict["status_code"] in (401, 403):
        return True
    
    # Проверка пути
    if log_dict["path"].startswith("/admin"):
        return True
    
    return False


def count_failed_logins(logs_list: list) -> dict:
    """
    Подсчитывает количество неудачных попыток входа (401) по IP.
    """
    failed_logins = {}
    
    for log in logs_list:
        if log and log["status_code"] == 401:
            ip = log["ip"]
            failed_logins[ip] = failed_logins.get(ip, 0) + 1
    
    return failed_logins


def get_most_frequent_path(logs_list: list) -> str:
    """
    Определяет самый часто запрашиваемый путь.
    """
    path_counter = {}
    
    for log in logs_list:
        if log:
            path = log["path"]
            path_counter[path] = path_counter.get(path, 0) + 1
    
    if not path_counter:
        return ""
    
    # Находим путь с максимальным количеством запросов
    most_frequent = max(path_counter, key=path_counter.get)
    return most_frequent


# Основной блок программы
if __name__ == "__main__":
    # Список для всех распарсенных записей
    all_logs = []
    
    # Чтение файла лога
    try:
        with open("network_log.txt", "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if line:  # Пропускаем пустые строки
                    parsed = parse_log_line(line)
                    if parsed:  # Если парсинг успешен
                        all_logs.append(parsed)
                    # Если parsed == None, строка пропускается
    except FileNotFoundError:
        print("Ошибка: Файл network_log.txt не найден!")
        exit()
    
    # Запись отчета о подозрительных действиях
    try:
        with open("suspicious_report.txt", "w", encoding="utf-8") as report:
            for log in all_logs:
                if is_suspicious(log, BLACKLIST_IPS):
                    report.write(
                        f"[ВНИМАНИЕ] IP: {log['ip']} | "
                        f"Путь: {log['path']} | "
                        f"Код: {log['status_code']}\n"
                    )
    except IOError:
        print("Ошибка при записи в файл suspicious_report.txt")
    
    # Вывод статистики неудачных входов
    failed_stats = count_failed_logins(all_logs)
    for ip, count in failed_stats.items():
        print(f"IP {ip}: {count} неудачных попыток входа")
    
    # Вывод самого частого пути
    most_frequent = get_most_frequent_path(all_logs)
    if most_frequent:
        print(f"Самый частый путь: {most_frequent}")