diff --git a/src/agentready/assessors/code_quality.py b/src/agentready/assessors/code_quality.py index cb87e0cc..e5692abe 100644 --- a/src/agentready/assessors/code_quality.py +++ b/src/agentready/assessors/code_quality.py @@ -627,3 +627,167 @@ def data(self, temp): # Generic names ), ], ) + + +class StructuredLoggingAssessor(BaseAssessor): + """Assesses use of structured logging libraries. + + Tier 3 Important (1.5% weight) - Structured logs are machine-parseable + and enable AI to analyze logs for debugging and optimization. + """ + + @property + def attribute_id(self) -> str: + return "structured_logging" + + @property + def tier(self) -> int: + return 3 # Important + + @property + def attribute(self) -> Attribute: + return Attribute( + id=self.attribute_id, + name="Structured Logging", + category="Code Quality", + tier=self.tier, + description="Logging in structured format (JSON) with consistent fields", + criteria="Structured logging library configured (structlog, winston, zap)", + default_weight=0.015, + ) + + def is_applicable(self, repository: Repository) -> bool: + """Applicable to any code repository.""" + return len(repository.languages) > 0 + + def assess(self, repository: Repository) -> Finding: + """Check for structured logging library usage.""" + # Check Python dependencies + if "Python" in repository.languages: + return self._assess_python_logging(repository) + else: + return Finding.not_applicable( + self.attribute, + reason=f"Structured logging check not implemented for {list(repository.languages.keys())}", + ) + + def _assess_python_logging(self, repository: Repository) -> Finding: + """Check for Python structured logging libraries.""" + # Libraries to check for + structured_libs = ["structlog", "python-json-logger", "structlog-sentry"] + + # Check dependency files + dep_files = [ + repository.path / "pyproject.toml", + repository.path / "requirements.txt", + repository.path / "setup.py", + ] + + found_libs = [] + checked_files = [] + + for dep_file in dep_files: + if not dep_file.exists(): + continue + + checked_files.append(dep_file.name) + try: + content = dep_file.read_text(encoding="utf-8") + for lib in structured_libs: + if lib in content: + found_libs.append(lib) + except (OSError, UnicodeDecodeError): + continue + + if not checked_files: + return Finding.not_applicable( + self.attribute, reason="No Python dependency files found" + ) + + # Score: Binary - either has structured logging or not + if found_libs: + score = 100.0 + status = "pass" + evidence = [ + f"Structured logging library found: {', '.join(set(found_libs))}", + f"Checked files: {', '.join(checked_files)}", + ] + remediation = None + else: + score = 0.0 + status = "fail" + evidence = [ + "No structured logging library found", + f"Checked files: {', '.join(checked_files)}", + "Using built-in logging module (unstructured)", + ] + remediation = self._create_remediation() + + return Finding( + attribute=self.attribute, + status=status, + score=score, + measured_value="configured" if found_libs else "not configured", + threshold="structured logging library", + evidence=evidence, + remediation=remediation, + error_message=None, + ) + + def _create_remediation(self) -> Remediation: + """Create remediation guidance for structured logging.""" + return Remediation( + summary="Add structured logging library for machine-parseable logs", + steps=[ + "Choose structured logging library (structlog for Python, winston for Node.js)", + "Install library and configure JSON formatter", + "Add standard fields: timestamp, level, message, context", + "Include request context: request_id, user_id, session_id", + "Use consistent field naming (snake_case for Python)", + "Never log sensitive data (passwords, tokens, PII)", + "Configure different formats for dev (pretty) and prod (JSON)", + ], + tools=["structlog", "winston", "zap"], + commands=[ + "# Install structlog", + "pip install structlog", + "", + "# Configure structlog", + "# See examples for configuration", + ], + examples=[ + """# Python with structlog +import structlog + +# Configure structlog +structlog.configure( + processors=[ + structlog.stdlib.add_log_level, + structlog.processors.TimeStamper(fmt="iso"), + structlog.processors.JSONRenderer() + ] +) + +logger = structlog.get_logger() + +# Good: Structured logging +logger.info( + "user_login", + user_id="123", + email="user@example.com", + ip_address="192.168.1.1" +) + +# Bad: Unstructured logging +logger.info(f"User {user_id} logged in from {ip}") +""", + ], + citations=[ + Citation( + source="structlog", + title="structlog Documentation", + url="https://www.structlog.org/en/stable/", + relevance="Python structured logging library", + ), + ], + ) diff --git a/src/agentready/assessors/stub_assessors.py b/src/agentready/assessors/stub_assessors.py index 2b78253f..bb72701a 100644 --- a/src/agentready/assessors/stub_assessors.py +++ b/src/agentready/assessors/stub_assessors.py @@ -298,9 +298,6 @@ def create_stub_assessors(): 0.03, ), # Tier 3 Important - StubAssessor( - "structured_logging", "Structured Logging", "Error Handling", 3, 0.03 - ), StubAssessor( "openapi_specs", "OpenAPI/Swagger Specifications", diff --git a/src/agentready/cli/main.py b/src/agentready/cli/main.py index 5b2bb968..c3d54a10 100644 --- a/src/agentready/cli/main.py +++ b/src/agentready/cli/main.py @@ -15,6 +15,7 @@ from ..assessors.code_quality import ( CyclomaticComplexityAssessor, SemanticNamingAssessor, + StructuredLoggingAssessor, TypeAnnotationsAssessor, ) @@ -89,11 +90,12 @@ def create_all_assessors(): ConciseDocumentationAssessor(), InlineDocumentationAssessor(), CyclomaticComplexityAssessor(), # Actually Tier 3, but including here - # Tier 3 Important (5 implemented) + # Tier 3 Important (6 implemented) ArchitectureDecisionsAssessor(), IssuePRTemplatesAssessor(), CICDPipelineVisibilityAssessor(), SemanticNamingAssessor(), + StructuredLoggingAssessor(), ] # Add remaining stub assessors