??????????????????????????????? ??????????????????????????????? ??????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>. ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #! /usr/lib/rads/venv/bin/python3 '''Apache Log Parser - Parse Apache domain access logs''' import os import sys import logging import re import json from argparse import ArgumentParser from time import time from collections import defaultdict from platform import node as hostname import envinfo from dns import resolver, reversename, exception from rads import setup_logging, color __maintainer__ = "Daniel K" __email__ = "danielk@inmotionhosting.com" __version__ = "1.0.2" __date__ = "2016-09-16" # Location of Apache domain logs for users. # The bit at the end is for str.format() to allow users to be added there USER_DOMLOG_DIR = envinfo.get_data()['apache_domlogs'] + "/{0!s}/" # Maximum number of log files on shared servers MAX_LOGS_SHARED = 50 LOGGER = logging.getLogger(__name__) def ptr_lookup(ip_addr): """Return PTR for IP address""" try: myresolver = resolver.Resolver() myresolver.lifetime = 1.0 myresolver.timeout = 1.0 question_name = reversename.from_address(ip_addr) answers = myresolver.query(question_name, "PTR") return str(answers[0]) except resolver.NXDOMAIN: return "No Record Found" except exception.Timeout: LOGGER.debug("Query Timed out looking for %s", ip_addr) return "Query Timed Out" except resolver.NoNameservers: LOGGER.debug("No nameservers found for %s", ip_addr) return "No nameservers found" except resolver.NoAnswer: LOGGER.debug("No answer for %s", ip_addr) return "No Answer" def domlog_lines(source): '''Process log lines''' if source == "-": LOGGER.info("Processing from STDIN.") yield from sys.stdin else: filename = source LOGGER.info("Process file %s", source) if os.path.exists(filename): with open(filename, encoding='utf-8') as file_handle: try: yield from file_handle except OSError: LOGGER.error("Error reading file %s", filename) def trim_dict(dictionary, entries): '''Trim dictionary to top entries ordered by value''' trimmed_dict = {} count = 0 for item in sorted(dictionary, key=lambda x: dictionary[x], reverse=True): count = count + 1 trimmed_dict[item] = dictionary[item] if count >= entries: return trimmed_dict return trimmed_dict def parse_domlogs(source, numlines=10, add_ptr=False): '''Process log lines''' results = { 'status_codes': defaultdict(int), 'daily_hourly': defaultdict(lambda: defaultdict(int)), 'requests': defaultdict(int), 'user_agents': defaultdict(int), 'top_ips': defaultdict(int), 'linecount': 0, } # Single regex to match all log lines. # It stores each entry in named groups, even though not all groups # are used by this script. You can see the names listed below # as (?...). rx_logline = re.compile( r'^(?P(?P[0-9.]+|[a-fA-F0-9:]+)' # Could handle multiple IPs r'(,\s*[0-9.]+|[a-fA-F0-9:]+)*)\s+' r'(?P\S+)\s+(?P\S+)\s+' # Could find logged in users r'\[(?P[0-9]+/[a-zA-Z]+/[0-9]+):' r'(?P