From c08a647d45d4850a7cac94211b854ce1d94e2de7 Mon Sep 17 00:00:00 2001 From: gapcomputer Date: Sat, 5 Jul 2025 11:07:16 +0000 Subject: [PATCH 1/5] Start draft PR From 4c8b3dc864b0296687078591dd80e4c6a432a979 Mon Sep 17 00:00:00 2001 From: gapcomputer Date: Sat, 5 Jul 2025 11:07:37 +0000 Subject: [PATCH 2/5] Add comprehensive .gitignore for Python project --- .gitignore | 54 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 02eac69..aae708b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,34 @@ -### AL ### -#Template for AL projects for Dynamics 365 Business Central -#launch.json folder +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so + +# Virtual Environments +venv/ +env/ +.env/ + +# Distribution / packaging +dist/ +build/ +*.egg-info/ + +# Logs +*.log +logs/ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# IDEs and editors .vscode/ -#Cache folder -.alcache/ -#Symbols folder -.alpackages/ -#Snapshots folder -.snapshots/ -#Testing Output folder -.output/ -#Extension App-file -*.app -#Rapid Application Development File -rad.json -#Translation Base-file -*.g.xlf -#License-file -*.flf -#Test results file -TestResults.xml \ No newline at end of file +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +Thumbs.db \ No newline at end of file From 788ec49f98760e222d8f79b25c3e3be304617888 Mon Sep 17 00:00:00 2001 From: gapcomputer Date: Sat, 5 Jul 2025 11:07:55 +0000 Subject: [PATCH 3/5] Implement LogParser for log analysis and reporting --- src/log_analysis/log_parser.py | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/log_analysis/log_parser.py diff --git a/src/log_analysis/log_parser.py b/src/log_analysis/log_parser.py new file mode 100644 index 0000000..cfb3559 --- /dev/null +++ b/src/log_analysis/log_parser.py @@ -0,0 +1,123 @@ +from typing import List, Dict, Any +import json +import os +from datetime import datetime + +class LogParser: + """ + A class responsible for parsing and analyzing iteration logs. + + This class provides methods to: + - Read log files + - Parse log entries + - Analyze performance metrics + - Generate summary reports + """ + + def __init__(self, log_directory: str = 'logs'): + """ + Initialize the LogParser with a specific log directory. + + Args: + log_directory (str): Directory containing log files. Defaults to 'logs'. + """ + self.log_directory = log_directory + + # Ensure log directory exists + os.makedirs(log_directory, exist_ok=True) + + def get_log_files(self) -> List[str]: + """ + Retrieve all log files in the specified directory. + + Returns: + List[str]: List of log file paths + """ + return [ + os.path.join(self.log_directory, f) + for f in os.listdir(self.log_directory) + if f.endswith('.json') + ] + + def parse_log_file(self, file_path: str) -> List[Dict[str, Any]]: + """ + Parse a single log file and return its contents. + + Args: + file_path (str): Path to the log file + + Returns: + List[Dict[str, Any]]: Parsed log entries + + Raises: + FileNotFoundError: If the log file doesn't exist + json.JSONDecodeError: If the log file is not valid JSON + """ + try: + with open(file_path, 'r') as log_file: + return json.load(log_file) + except FileNotFoundError: + raise FileNotFoundError(f"Log file not found: {file_path}") + except json.JSONDecodeError: + raise ValueError(f"Invalid JSON in log file: {file_path}") + + def analyze_performance(self, log_entries: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Analyze performance metrics from log entries. + + Args: + log_entries (List[Dict[str, Any]]): List of log entries to analyze + + Returns: + Dict[str, Any]: Performance summary metrics + """ + if not log_entries: + return {} + + # Extract performance-related metrics + performance_metrics = { + 'total_iterations': len(log_entries), + 'start_time': log_entries[0].get('timestamp'), + 'end_time': log_entries[-1].get('timestamp'), + 'error_rate': sum(1 for entry in log_entries if entry.get('status') == 'error') / len(log_entries), + 'performance_scores': [entry.get('performance_score', 0) for entry in log_entries] + } + + # Calculate additional metrics + if performance_metrics['performance_scores']: + performance_metrics.update({ + 'avg_performance': sum(performance_metrics['performance_scores']) / len(performance_metrics['performance_scores']), + 'max_performance': max(performance_metrics['performance_scores']), + 'min_performance': min(performance_metrics['performance_scores']) + }) + + return performance_metrics + + def generate_report(self) -> Dict[str, Any]: + """ + Generate a comprehensive report by analyzing all log files. + + Returns: + Dict[str, Any]: Comprehensive log analysis report + """ + log_files = self.get_log_files() + report = { + 'total_log_files': len(log_files), + 'file_analyses': [] + } + + for log_file in log_files: + try: + log_entries = self.parse_log_file(log_file) + file_report = { + 'file_name': os.path.basename(log_file), + 'performance': self.analyze_performance(log_entries) + } + report['file_analyses'].append(file_report) + except Exception as e: + report['file_analyses'].append({ + 'file_name': os.path.basename(log_file), + 'error': str(e) + }) + + return report \ No newline at end of file From 5d59491bf8dbe53d01a7813f9855f7ff1f0a126d Mon Sep 17 00:00:00 2001 From: gapcomputer Date: Sat, 5 Jul 2025 11:08:00 +0000 Subject: [PATCH 4/5] Add __init__.py for log_analysis package --- src/log_analysis/__init__.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/log_analysis/__init__.py diff --git a/src/log_analysis/__init__.py b/src/log_analysis/__init__.py new file mode 100644 index 0000000..701e24e --- /dev/null +++ b/src/log_analysis/__init__.py @@ -0,0 +1,3 @@ +from .log_parser import LogParser + +__all__ = ['LogParser'] \ No newline at end of file From b44eb123d8342853b28b3225e9a8e40b24a7aab9 Mon Sep 17 00:00:00 2001 From: gapcomputer Date: Sat, 5 Jul 2025 11:08:17 +0000 Subject: [PATCH 5/5] Add comprehensive tests for LogParser --- tests/log_analysis/test_log_parser.py | 110 ++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tests/log_analysis/test_log_parser.py diff --git a/tests/log_analysis/test_log_parser.py b/tests/log_analysis/test_log_parser.py new file mode 100644 index 0000000..da41fdf --- /dev/null +++ b/tests/log_analysis/test_log_parser.py @@ -0,0 +1,110 @@ +import os +import json +import pytest +from typing import List, Dict, Any +from src.log_analysis.log_parser import LogParser + +@pytest.fixture +def sample_log_data() -> List[Dict[str, Any]]: + """Generate sample log data for testing.""" + return [ + { + 'timestamp': '2023-01-01T00:00:00', + 'iteration': 1, + 'performance_score': 0.75, + 'status': 'success' + }, + { + 'timestamp': '2023-01-01T00:01:00', + 'iteration': 2, + 'performance_score': 0.85, + 'status': 'success' + }, + { + 'timestamp': '2023-01-01T00:02:00', + 'iteration': 3, + 'performance_score': 0.5, + 'status': 'error' + } + ] + +@pytest.fixture +def log_directory(tmp_path, sample_log_data): + """Create a temporary log directory with sample log files.""" + log_dir = tmp_path / "logs" + log_dir.mkdir() + + # Create multiple log files + for i in range(3): + log_file = log_dir / f"log_{i}.json" + with open(log_file, 'w') as f: + json.dump(sample_log_data, f) + + return str(log_dir) + +def test_log_parser_initialization(log_directory): + """Test LogParser initialization.""" + parser = LogParser(log_directory) + assert parser.log_directory == log_directory + assert os.path.exists(log_directory) + +def test_get_log_files(log_directory): + """Test retrieving log files.""" + parser = LogParser(log_directory) + log_files = parser.get_log_files() + + assert len(log_files) == 3 + assert all(f.endswith('.json') for f in log_files) + +def test_parse_log_file(log_directory, sample_log_data): + """Test parsing a single log file.""" + parser = LogParser(log_directory) + log_files = parser.get_log_files() + parsed_logs = parser.parse_log_file(log_files[0]) + + assert parsed_logs == sample_log_data + assert len(parsed_logs) == 3 + +def test_analyze_performance(log_directory, sample_log_data): + """Test performance analysis of log entries.""" + parser = LogParser(log_directory) + performance_metrics = parser.analyze_performance(sample_log_data) + + assert performance_metrics['total_iterations'] == 3 + assert performance_metrics['error_rate'] == pytest.approx(1/3) + assert performance_metrics['avg_performance'] == pytest.approx(0.7) + assert performance_metrics['max_performance'] == 0.85 + assert performance_metrics['min_performance'] == 0.5 + +def test_generate_report(log_directory): + """Test generating a comprehensive log report.""" + parser = LogParser(log_directory) + report = parser.generate_report() + + assert report['total_log_files'] == 3 + assert len(report['file_analyses']) == 3 + + for file_analysis in report['file_analyses']: + assert 'file_name' in file_analysis + assert 'performance' in file_analysis + +def test_invalid_log_file(tmp_path): + """Test handling of invalid log files.""" + invalid_log_dir = tmp_path / "invalid_logs" + invalid_log_dir.mkdir() + + # Create an invalid JSON file + with open(invalid_log_dir / "invalid.json", 'w') as f: + f.write("Not a valid JSON") + + parser = LogParser(str(invalid_log_dir)) + + with pytest.raises(ValueError): + parser.parse_log_file(str(invalid_log_dir / "invalid.json")) + +def test_nonexistent_log_file(): + """Test handling of nonexistent log files.""" + parser = LogParser('/nonexistent/path') + + with pytest.raises(FileNotFoundError): + parser.parse_log_file('/nonexistent/path/log.json') \ No newline at end of file