How to Parse Apache Log Files in Python
Parse Apache common log format lines into structured dictionaries using Python's standard library.
Python code
44 linesimport re
from pathlib import Path
def parse_apache_line(line):
pattern = r'^(\S+) (\S+) (\S+) \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d{3}) (\S+)'
match = re.match(pattern, line)
if not match:
return None
ip, ident, user, timestamp, method, path, protocol, status, size = match.groups()
return {
"ip": ip,
"user": user,
"timestamp": timestamp,
"method": method,
"path": path,
"status": int(status),
"size": int(size) if size.isdigit() else 0
}
def read_apache_log(filepath):
log_path = Path(filepath)
if not log_path.exists():
raise FileNotFoundError(f"Log file not found: {filepath}")
entries = []
with log_path.open("r") as f:
for line in f:
line = line.strip()
if line:
parsed = parse_apache_line(line)
if parsed:
entries.append(parsed)
return entries
if __name__ == "__main__":
sample_log = """192.168.1.1 - - [10/Oct/2024:13:55:36 +0000] "GET /index.html HTTP/1.1" 200 2326
192.168.1.2 - john [10/Oct/2024:13:56:02 +0000] "POST /api/data HTTP/1.1" 201 128
10.0.0.5 - - [10/Oct/2024:13:56:45 +0000] "GET /images/logo.png HTTP/1.1" 404 0
"""
temp_file = Path("/tmp/sample_apache.log")
temp_file.write_text(sample_log)
results = read_apache_log(temp_file)
for entry in results:
print(entry)
print(f"Total entries: {len(results)}")
Output
{'ip': '192.168.1.1', 'user': '-', 'timestamp': '10/Oct/2024:13:55:36 +0000', 'method': 'GET', 'path': '/index.html', 'status': 200, 'size': 2326}
{'ip': '192.168.1.2', 'user': 'john', 'timestamp': '10/Oct/2024:13:56:02 +0000', 'method': 'POST', 'path': '/api/data', 'status': 201, 'size': 128}
{'ip': '10.0.0.5', 'user': '-', 'timestamp': '10/Oct/2024:13:56:45 +0000', 'method': 'GET', 'path': '/images/logo.png', 'status': 404, 'size': 0}
Total entries: 3
How it works
The regex pattern anchors each line to extract exactly the fields of Apache's common log format: IP, identity, user, timestamp, request method, path, protocol, status code, and size. Using re.match with careful escaping handles edge cases like - for missing user fields. The Path.open context manager ensures the file closes cleanly, while strip() removes trailing newlines. Converting status and size to integers makes downstream analysis (like filtering by HTTP code) straightforward.
Common mistakes
- Forgetting to strip trailing newline characters before matching
- Assuming size is always numeric — need `isdigit()` fallback for missing values
- Using `re.search` instead of `re.match` causing unexpected matches mid-line
Variations
- Use `namedtuple` or `dataclass` instead of dict for typed access
- Stream parse with a generator for large logs to avoid memory issues
Real-world use cases
- Security analysis: extract IPs and paths to detect suspicious traffic patterns in production
- Monitoring dashboards: parse frontend server logs to display real-time request rates by endpoint
- Data pipelines: ingest web server logs into analytics systems for user behavior modeling
Sponsored
More from Files & data
- Append a Line to a Log File in Python easy
- Audit File Permissions Across a Project in Python easy
- Automatically Detect Corrupted Files Using SHA-256 Checksums in Python easy
- Automatically Highlight Data Validation Errors Inside Excel Files in Python easy
- Build a Command-Line To-Do List Application with Data Persistence in Python easy
- Build a File Index by Relative Path Hash Map in Python easy
Keep learning
Related tutorials and quizzes for this topic.