How Do I Parse and Filter Log Files with Python for Quick Debugging?
The Problem
I was debugging a production issue at 2 AM. The auth service was throwing intermittent errors, and I needed to find the relevant logs fast. I typed:
grep "auth-service" /var/log/app.log | grep "ERROR" | grep "2026-03-14"Scrolling through hundreds of lines, I realized I was spending more time crafting grep commands than actually debugging. There had to be a better way.
What I Tried First
My initial approach was pure grep with pipes:
grep "ERROR" /var/log/app.log | grep "auth" | tail -100This worked, but had problems:
- No time filtering - I got logs from weeks ago mixed with today’s errors
- No formatting - Everything was the same color, hard to scan
- No summary - I had to count errors manually
- Repetitive typing - Every debugging session required re-learning the grep patterns
I also tried opening logs in my text editor:
code /var/log/app.logBut with a 500MB log file, the editor choked. Spreadsheets were worse - CSV export took forever and I lost the original formatting.
The Solution: A Dedicated Log Parser
I decided to build a reusable Python script that I could run as:
parselog auth-service 12This would show me the last 12 hours of auth-service logs, color-coded, with a summary at the end.
Step 1: Define the Log Pattern
First, I needed to understand my log format:
2026-03-14 02:15:33 ERROR [auth-service] Failed to validate token: expiredThis matched a predictable pattern, so I built a regex:
import re
LOG_PATTERN = re.compile( r'(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})' r'\s+(?P<level>ERROR|WARN|INFO|DEBUG)' r'\s+\[(?P<component>[^\]]+)\]' r'\s+(?P<message>.*)')Step 2: Parse Individual Lines
I wrote a function to convert each log line into a dictionary:
def parse_log_line(line: str) -> dict | None: """Parse a single log line into structured data.""" match = LOG_PATTERN.match(line.strip()) if match: return match.groupdict() return NoneStep 3: Filter by Time
Next, I needed to filter logs within a time window:
from datetime import datetime, timedelta
def filter_by_time(entry: dict, hours: int) -> bool: """Filter entries within the last N hours.""" try: log_time = datetime.strptime(entry['timestamp'], '%Y-%m-%d %H:%M:%S') cutoff = datetime.now() - timedelta(hours=hours) return log_time >= cutoff except (ValueError, KeyError): return True # Include if timestamp parsing failsStep 4: Add Color Output
For quick scanning, I added terminal colors:
def format_output(entry: dict) -> str: """Format log entry with color for readability.""" level_color = { 'ERROR': '\033[91m', # Red 'WARN': '\033[93m', # Yellow 'INFO': '\033[92m', # Green 'DEBUG': '\033[90m', # Gray } reset = '\033[0m' color = level_color.get(entry.get('level', ''), '')
return f"{color}{entry['timestamp']} [{entry['level']}] {entry['component']}: {entry['message']}{reset}"Step 5: Put It All Together
The complete script:
#!/usr/bin/env python3"""Quick log parser for debugging.Usage: parselog <component> [hours] [log_file]Example: parselog auth-service 12 /var/log/app.log"""import reimport sysfrom datetime import datetime, timedeltafrom pathlib import Pathfrom collections import Counter
LOG_PATTERN = re.compile( r'(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})' r'\s+(?P<level>ERROR|WARN|INFO|DEBUG)' r'\s+\[(?P<component>[^\]]+)\]' r'\s+(?P<message>.*)')
def parse_log_line(line: str) -> dict | None: match = LOG_PATTERN.match(line.strip()) return match.groupdict() if match else None
def filter_by_time(entry: dict, hours: int) -> bool: try: log_time = datetime.strptime(entry['timestamp'], '%Y-%m-%d %H:%M:%S') return log_time >= datetime.now() - timedelta(hours=hours) except (ValueError, KeyError): return True
def format_output(entry: dict) -> str: level_color = {'ERROR': '\033[91m', 'WARN': '\033[93m', 'INFO': '\033[92m', 'DEBUG': '\033[90m'} reset = '\033[0m' color = level_color.get(entry.get('level', ''), '') return f"{color}{entry['timestamp']} [{entry['level']}] {entry['component']}: {entry['message']}{reset}"
def main(): if len(sys.argv) < 2: print("Usage: parselog <component> [hours] [log_file]") sys.exit(1)
component = sys.argv[1] hours = int(sys.argv[2]) if len(sys.argv) > 2 else 24 log_file = Path(sys.argv[3]) if len(sys.argv) > 3 else Path('/var/log/app.log')
level_counts = Counter() matched = 0
print(f"\n=== Logs for '{component}' (last {hours}h) ===\n")
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f: for line in f: entry = parse_log_line(line) if not entry: continue
if component.lower() in entry['component'].lower(): if filter_by_time(entry, hours): print(format_output(entry)) level_counts[entry['level']] += 1 matched += 1
print(f"\n=== Summary: {matched} entries ===") for level, count in sorted(level_counts.items()): print(f" {level}: {count}")
if __name__ == '__main__': main()Handling Multi-Line Stack Traces
My initial script failed when logs contained stack traces. The continuation lines weren’t being captured.
Here’s how I fixed it:
def parse_log_stream(file_handle, component: str, hours: int): """Parse logs handling multi-line stack traces.""" current_entry = None current_trace = []
for line in file_handle: entry = parse_log_line(line)
if entry: # Yield previous entry if it matches if current_entry: if component.lower() in current_entry['component'].lower(): if filter_by_time(current_entry, hours): yield current_entry, '\n'.join(current_trace)
current_entry = entry current_trace = [] elif current_entry: # This is a continuation line (stack trace) current_trace.append(line.rstrip())
# Yield last entry if current_entry and component.lower() in current_entry['component'].lower(): if filter_by_time(current_entry, hours): yield current_entry, '\n'.join(current_trace)Now stack traces stay attached to their parent log entry.
Making It a Terminal Alias
To run this from anywhere, I added an alias:
alias parselog='python3 ~/bin/parselog'Now I just type:
parselog auth-service 12And get instant, formatted results:
=== Logs for 'auth-service' (last 12h) ===
2026-03-14 02:15:33 [ERROR] auth-service: Failed to validate token: expired2026-03-14 02:16:01 [ERROR] auth-service: Database connection timeout2026-03-14 02:17:45 [WARN] auth-service: Rate limit approaching for IP 10.0.0.52026-03-14 03:01:22 [ERROR] auth-service: Invalid credentials for user [email protected]
=== Summary: 47 entries === ERROR: 38 WARN: 9Common Mistakes I Made
1. Loading Entire Log File Into Memory
My first version did this:
with open(log_file) as f: lines = f.readlines() # Loads 500MB into memory! for line in lines: ...This crashed on large files. The fix was streaming:
with open(log_file) as f: for line in f: # Streams one line at a time ...2. Ignoring Encoding Issues
Binary data in logs caused crashes:
with open(log_file) as f: # Fails on binary dataThe fix:
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:3. Not Handling Timezone Differences
My production servers use UTC, but I was filtering with local time. Logs from “today” were being filtered out.
The fix was either:
- Convert server timestamps to local time before filtering
- Or run the script on the server where timezones match
Quick Reference: Script Comparison
| Approach | Pros | Cons |
|---|---|---|
| grep pipes | No setup, available everywhere | Complex patterns, no formatting, no summary |
| Text editor | Familiar interface | Crashes on large files, slow |
| Python script | Reusable, formatted, summarized | Initial setup required |
| ELK stack | Powerful, visual | Overkill for quick debugging |
When to Use Each Tool
- grep: Quick one-off searches on small files
- Python parser: Repetitive debugging on known log formats
- ELK/Splunk: Production monitoring, dashboards, alerting
Summary
I built a Python log parser that transformed my debugging workflow from typing complex grep commands to running a single memorable alias. The key features:
- Time-based filtering - Only see relevant recent logs
- Color output - Errors stand out visually
- Summary statistics - Quick error counts without manual counting
- Stack trace handling - Multi-line entries stay together
- Memory efficient - Streams large files without loading into memory
The next time you’re debugging at 2 AM, spend 30 minutes building a tool that saves you hours. Your future self will thank you.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Python re module documentation
- 👨💻 Python datetime documentation
- 👨💻 Python argparse documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments