This file provides comprehensive guidance to AI coding assistants (Claude Code, GitHub Copilot, Cursor, etc.) when working with code in this repository.
MANDATORY: Review these documents for complete project context:
docs/SPEC.md- Complete technical specification, system architecture, database schemas, and API designdocs/adr/- Architectural Decision Records for all design decisions and project context
This project follows universal best practices documented in docs/best-practices/. These are Tier 1 (universal) conventions that can be applied to any project. AGENTS.md provides Tier 2 (project-specific) implementation details.
Tier 1: Universal Best Practices (copy to other projects):
- PYTHON.md - Type hints, Pydantic, UV, generators, context managers
- DEVELOPMENT.md - Git workflow, zero warnings, ADR lifecycle
- TESTING.md - Multi-layered testing strategy, coverage philosophy
- SECURITY.md - SQL injection, path validation, archive safety
- LOGGING.md - Semantic log levels, stderr routing, test suppression
- DOCUMENTATION.md - Two-tier documentation architecture
Tier 2: Project-Specific Implementation (this document):
- How we apply Python standards to logfile-mcp-server
- FastAPI and SQLAlchemy patterns specific to this project
- MCP server implementation details
- Log analysis domain-specific patterns
Reading Strategy: Review applicable Tier 1 best practices first, then use this document for project-specific implementation details.
This is a Python FastAPI application for loading, parsing, correlating, and analyzing access logs from Nexus Repository and nginx reverse proxy. The application provides both a web interface and MCP (Model Context Protocol) server for LLM integration.
# Install uv (Python package manager) if needed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment and install dependencies
uv sync
# Install development dependencies
uv sync --dev# Create demo database with sample data
uv run python scripts/create_demo_db.py
# Process logs and start web interface
uv run python -m app.main --nexus-dir ./sample_logs/nexus --nginx-dir ./sample_logs/nginx --process-logs
# Start MCP server for VS Code Copilot integration (stdio mode)
uv run python -m app.main --db-name log_analysis.db --mcp-stdio
# Full stack: process logs, web server, and MCP server
uv run python -m app.main --nexus-dir /path/to/nexus --nginx-dir /path/to/nginx --process-logs --enable-mcp-server# Run all unit and integration tests
uv run pytest tests/unit tests/integration -v
# Run specific test file
uv run pytest tests/unit/test_nginx_processor.py -v
# Run with coverage
uv run pytest --cov=app --cov-report=html --cov-report=term
# Run E2E tests with Playwright
./run_playwright_e2e.sh
# Run tests by category
uv run pytest -m unit # Unit tests only
uv run pytest -m integration # Integration tests only
uv run pytest -m e2e # E2E tests only# Format code with black
uv run black app/ tests/
# Sort imports
uv run isort app/ tests/
# Type checking
uv run mypy app/
# Linting
uv run flake8 app/ tests/Processing Pipeline:
file_discovery/- Archive extraction and file pattern matching with support for nested archives (tar, zip, tar.gz) up to configurable depthprocessors/- Abstract base class (BaseLogProcessor) with format-specific implementations (NginxLogProcessor,NexusLogProcessor) using regex parsingprocessing/orchestrator.py- Coordinates file discovery and processing with statistics tracking
Database Layer:
database/models.py- SQLAlchemy models fornginx_logsandnexus_logstables with comprehensive indexesdatabase/operations.py- Unified database operations interface with batch insertiondatabase/connection.py- SQLite connection management with fresh database creation on startup
Web Interface:
web/routes.py- FastAPI endpoints for table previews and SQL query execution- Security: Only SELECT queries allowed, results limited to prevent memory issues
MCP Server:
mcp/server.py- Dual transport support (stdio for VS Code Copilot, network for other clients)mcp/tools.py- Three core tools:list_database_schema,execute_sql_query,get_table_sample- Single entry point through
app.main(no separate MCP scripts)
Memory-Efficient Processing:
- Chunked file reading (default: 1000 lines per chunk)
- Generator-based processing to avoid loading entire files into memory
- Batch database inserts (default: 1000 entries per batch)
- Stream processing through archives
Configuration Management:
- Pydantic-based
Settingsclass with.envfile support - CLI arguments override environment variables
- Dependency injection pattern - all components accept Settings instance
Error Handling:
- Malformed log lines are logged but processing continues
- Format:
PARSE_ERROR: {file_path}:{line_number} - {error_message} - Statistics tracking for errors and successful entries
Nested Archive Support:
- Recursive extraction with configurable depth limit (default: 3 levels)
- File source tracking through nesting chains (e.g.,
backup.zip->daily.tar.gz->access.log) - Temporary directory cleanup after extraction
nginx_logs table:
- Key fields: ip_address, timestamp, method, path, status_code, response_size, user_agent
- Indexes on: timestamp, ip_address, method, path, status_code, (method, path) composite
nexus_logs table:
- Key fields: ip_address, timestamp, method, path, status_code, response_size_1, response_size_2, user_agent, thread_info
- Similar index structure to nginx_logs for performance
Both tables include raw_log (original line) and file_source (tracking) fields.
MANDATORY: Always use UV for Python package management:
# Project setup and dependency installation
uv sync # Install all dependencies from lock file
uv add package_name # Add new dependency
uv add --dev pytest # Add development dependency
uv remove package_name # Remove dependency
# Running commands in UV environment
uv run python -m app.main # Run application
uv run pytest # Run tests
uv run python -m app.main --mcp-stdio # Run MCP server
# Environment management
uv venv # Create virtual environment (if needed)
uv pip list # List installed packagesDependency Guidelines:
- Always use
uv addinstead ofpip installfor adding dependencies - Use
uv runprefix for all Python commands to ensure correct environment - Keep
pyproject.tomlanduv.lockfiles updated and committed - Use
--devflag for development-only dependencies (pytest, black, etc.)
MANDATORY: All docstrings must use "AI:" prefix to indicate AI-generated/maintained code:
def process_log_file(file_path: Path, patterns: List[str]) -> Iterator[Dict[str, Any]]:
"""
AI: Process log file and yield parsed entries.
Args:
file_path: Path to log file
patterns: List of filename patterns to match
Yields:
Parsed log entry dictionaries
Raises:
ProcessingError: When file cannot be processed
"""
pass
class NginxLogProcessor(BaseLogProcessor):
"""AI: Processor specifically for nginx access log format."""
passType Hints Required:
from typing import List, Dict, Optional, Iterator, Tuple
from pathlib import Path
def parse_log_line(line: str, line_number: int, source_file: str) -> Optional[Dict]:
"""AI: Parse individual log line with detailed error reporting."""
try:
return self._apply_regex_parsing(line)
except LogParsingError as e:
print(f"PARSE_ERROR: {source_file}:{line_number} - {e}")
return NoneComprehensive Error Handling:
# Always use specific exception types
class LogParsingError(Exception):
"""AI: Raised when log entry cannot be parsed."""
pass
class ArchiveExtractionError(Exception):
"""AI: Raised when archive cannot be extracted."""
pass
# Error handling with context
def parse_log_line(self, line: str, line_number: int, source_file: str) -> Optional[Dict]:
"""AI: Parse individual log line with detailed error reporting."""
try:
return self._apply_regex_parsing(line)
except LogParsingError as e:
print(f"PARSE_ERROR: {source_file}:{line_number} - {e}")
return None # Continue processing
except Exception as e:
print(f"UNEXPECTED_ERROR: {source_file}:{line_number} - {e}")
return NoneAlways Use Context Managers:
# Always use context managers and handle nested archives
def process_archive_recursive(self, archive_path: Path, max_depth: int = 3) -> Iterator[Dict]:
"""AI: Process archive with nested archive support and proper resource cleanup."""
if max_depth <= 0:
print(f"WARNING: Maximum nesting depth reached for {archive_path}")
return
with tempfile.TemporaryDirectory() as temp_dir:
with tarfile.open(archive_path, 'r:gz') as archive:
for member in archive.getmembers():
if member.isfile():
# Extract to temporary location
archive.extract(member, temp_dir)
extracted_path = Path(temp_dir) / member.name
if self.matches_target_pattern(member.name):
with open(extracted_path, 'r') as file_obj:
yield from self.process_file_content(file_obj, f"{archive_path.name}->{member.name}")
elif self.is_archive_file(extracted_path):
# Recursively process nested archive
yield from self.process_archive_recursive(extracted_path, max_depth - 1)
# Temporary directory automatically cleaned upMANDATORY: All code must execute without warnings in both development and test environments:
- Test Output: All pytest runs must complete without warning messages
- Dependencies: No deprecation warnings from imported libraries
- Code Quality: No unused imports, variables, or unreachable code
- Resource Management: Proper cleanup to prevent ResourceWarnings
- Mock Configuration: Test mocks properly configured to avoid warnings
- Async Operations: Proper async handling to prevent runtime warnings
MANDATORY: All processors must accept Settings instance in constructor:
# CORRECT
class NginxLogProcessor(BaseLogProcessor):
def __init__(self, settings: Settings):
self.settings = settings
self.patterns = settings.nginx_patterns # Use from config
# INCORRECT - Never hardcode patterns
class NginxLogProcessor(BaseLogProcessor):
def __init__(self):
self.patterns = ["access.log*"] # Don't do thisNever hardcode configuration values (patterns, paths, limits) in component classes. Use settings.nexus_patterns and settings.nginx_patterns properties for pattern matching.
Perfect Parallel Structure Required:
app/processors/ app/database/
├── base.py ├── base.py
│ └── BaseLogProcessor └── BaseLogDatabase
│ └── CommonLogDatabase
├── nginx_processor.py ├── nginx_database.py
│ └── NginxLogProcessor └── NginxLogDatabase
└── nexus_processor.py └── nexus_database.py
└── NexusLogProcessor └── NexusLogDatabase
Naming Convention Rules:
File Naming:
- Base files:
base.py(notbase_operations.pyorbase_processor.py) - Format-specific files:
{format}_{module_type}.py- Examples:
nginx_processor.py,nginx_database.py
- Examples:
- Avoid verbose suffixes: Use
databasenotdatabase_operations
Class Naming:
- Base classes:
BaseLog{ModuleType}- Examples:
BaseLogProcessor,BaseLogDatabase
- Examples:
- Format-specific classes:
{Format}Log{ModuleType}- Examples:
NginxLogProcessor,NginxLogDatabase
- Examples:
- Common/shared classes:
CommonLog{ModuleType}- Examples:
CommonLogDatabase
- Examples:
File Creation Pattern:
- Processor: Create
{format}_processor.pywith{Format}LogProcessorclass - Database: Create
{format}_database.pywith{Format}LogDatabaseclass - Model: Add
{Format}LogSQLAlchemy model tomodels.py - Tests: Create corresponding test files following the same pattern
Implementation Checklist:
- Follow exact naming patterns of existing nginx/nexus implementations
- Inherit from appropriate base classes (
BaseLogProcessor,BaseLogDatabase) - Update unified interfaces (
DatabaseOperations, orchestrator) - Add configuration patterns to Settings class
- Create comprehensive tests following established patterns
- Update documentation to include new format
from fastapi import FastAPI, HTTPException, Depends
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI(
title="Log Analysis Application",
description="Nexus and nginx log correlation and analysis",
version="1.0.0"
)
# Static files and templates
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
# Dependency injection for database
def get_database():
"""AI: Dependency injection for database connections."""
return DatabaseOperations(settings.db_name)@app.post("/api/execute-query")
async def execute_query(
query_request: QueryRequest,
db: DatabaseOperations = Depends(get_database)
) -> QueryResponse:
"""
AI: Execute SQL query with security validation.
Only SELECT statements allowed for security.
Results limited to prevent memory issues.
"""
try:
# Validate query is SELECT only
if not query_request.query.strip().upper().startswith("SELECT"):
raise HTTPException(
status_code=400,
detail="Only SELECT queries are allowed"
)
results = db.execute_query(query_request.query, limit=1000)
return QueryResponse(results=results, row_count=len(results))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
class QueryRequest(BaseModel):
"""AI: Request model for SQL query execution."""
query: str = Field(..., description="SQL SELECT query to execute")
limit: Optional[int] = Field(100, description="Maximum rows to return")
class QueryResponse(BaseModel):
"""AI: Response model for query results."""
results: List[Dict[str, Any]]
row_count: int
columns: List[str]
execution_time: floatfrom sqlalchemy import Column, Integer, String, DateTime, Text, Index
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func
Base = declarative_base()
class NginxLog(Base):
"""AI: SQLAlchemy model for nginx access logs."""
__tablename__ = 'nginx_logs'
id = Column(Integer, primary_key=True, autoincrement=True)
ip_address = Column(String, nullable=False)
remote_user = Column(String)
timestamp = Column(DateTime, nullable=False)
method = Column(String, nullable=False)
path = Column(Text, nullable=False)
http_version = Column(String, nullable=False)
status_code = Column(Integer, nullable=False)
response_size = Column(Integer)
referer = Column(Text)
user_agent = Column(Text)
raw_log = Column(Text, nullable=False)
file_source = Column(String, nullable=False)
created_at = Column(DateTime, default=func.now())
# Define indexes as table arguments
__table_args__ = (
Index('idx_nginx_timestamp', 'timestamp'),
Index('idx_nginx_method', 'method'),
Index('idx_nginx_path', 'path'),
Index('idx_nginx_method_path', 'method', 'path'),
)from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from contextlib import contextmanager
class DatabaseOperations:
"""AI: Database operations with proper connection management."""
def __init__(self, db_path: str):
self.engine = create_engine(f"sqlite:///{db_path}")
self.SessionLocal = sessionmaker(bind=self.engine)
@contextmanager
def get_session(self):
"""AI: Context manager for database sessions."""
session = self.SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
def batch_insert(self, model_class, data: List[Dict]) -> int:
"""AI: Efficient batch insert with transaction management."""
with self.get_session() as session:
objects = [model_class(**item) for item in data]
session.bulk_save_objects(objects)
return len(objects)tests/
├── unit/
│ ├── test_nginx_processor.py
│ ├── test_nexus_processor.py
│ ├── test_file_handler.py
│ └── test_database_operations.py
├── integration/
│ ├── test_end_to_end_processing.py
│ └── test_api_endpoints.py
├── playwright/
│ ├── test_web_interface.py
│ └── test_sql_execution.py
├── fixtures/
│ ├── sample_logs.py
│ └── test_data.py
└── conftest.py
import pytest
from pathlib import Path
from app.processors.nginx_processor import NginxLogProcessor
class TestNginxProcessor:
"""AI: Test nginx log processing functionality."""
def setup_method(self):
"""AI: Setup test instance before each test."""
self.processor = NginxLogProcessor()
def test_parse_valid_nginx_log(self):
"""AI: Test parsing of standard nginx log entry."""
log_line = '127.0.0.1 - - [29/May/2025:00:00:09 -0400] "GET /api/test HTTP/1.1" 200 1234 "-" "test-agent"'
result = self.processor.parse_log_line(log_line, 1, "test.log")
assert result is not None
assert result['ip_address'] == '127.0.0.1'
assert result['method'] == 'GET'
assert result['path'] == '/api/test'
assert result['status_code'] == 200
def test_parse_malformed_log_returns_none(self):
"""AI: Test that malformed logs return None and log error."""
log_line = 'invalid log format'
result = self.processor.parse_log_line(log_line, 1, "test.log")
assert result is None
@pytest.mark.parametrize("log_line,expected_method,expected_path", [
('127.0.0.1 - - [29/May/2025:00:00:09 -0400] "GET /test HTTP/1.1" 200 1234', 'GET', '/test'),
('127.0.0.1 - - [29/May/2025:00:00:09 -0400] "POST /api/data HTTP/1.1" 201 567', 'POST', '/api/data'),
])
def test_parse_different_methods(self, log_line, expected_method, expected_path):
"""AI: Test parsing different HTTP methods."""
result = self.processor.parse_log_line(log_line, 1, "test.log")
assert result['method'] == expected_method
assert result['path'] == expected_pathimport pytest
import tempfile
from pathlib import Path
from app.database.operations import DatabaseOperations
from app.database.models import Base, NginxLog
class TestDatabaseIntegration:
"""AI: Test database operations with temporary database."""
@pytest.fixture
def temp_db(self):
"""AI: Create temporary database for testing."""
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
db_path = f.name
db_ops = DatabaseOperations(db_path)
Base.metadata.create_all(db_ops.engine)
yield db_ops
# Cleanup
Path(db_path).unlink(missing_ok=True)
def test_batch_insert_nginx_logs(self, temp_db):
"""AI: Test batch insertion of nginx log entries."""
test_data = [
{
'ip_address': '127.0.0.1',
'method': 'GET',
'path': '/test',
'status_code': 200,
'raw_log': 'test log line',
'file_source': 'test.log'
}
]
count = temp_db.batch_insert(NginxLog, test_data)
assert count == 1
# Verify data inserted
with temp_db.get_session() as session:
logs = session.query(NginxLog).all()
assert len(logs) == 1
assert logs[0].ip_address == '127.0.0.1'PROBLEM: Mock tests can pass while real implementations fail
# Mock test - may not catch attribute access errors
class TestMCPServerMocked:
def test_database_path_access_mocked(self):
"""AI: Mock test - may not catch attribute access errors."""
mock_db_ops = Mock()
mock_db_ops.db_path = "test.db" # Mock allows any attribute
server = MCPServer(mock_db_ops)
# This test passes but real implementation fails!
assert server.get_db_path() == "test.db"
# SOLUTION: Test with real object attribute chains
class TestMCPServerReal:
def test_database_path_access_real(self):
"""AI: Real test - catches actual attribute access errors."""
real_db_ops = DatabaseOperations("test.db")
server = MCPServer(real_db_ops)
# This would fail if attribute chain is incorrect
# Error: AttributeError: 'DatabaseOperations' object has no attribute 'db_path'
# Should be: real_db_ops.db_connection.db_path
assert server.get_db_path() == "test.db"Testing Strategy:
- Unit Tests: Use mocks for isolated component testing
- Integration Tests: Use real objects to validate actual attribute chains
- Critical Path Testing: Always test real implementations for production code paths
- Attribute Validation: Test actual object hierarchies, not just mock interfaces
- Error Discovery: Real tests reveal errors that mocks miss
import pytest
from playwright.sync_api import Page, expect
class TestWebInterface:
"""AI: Test web interface functionality using Playwright."""
def test_table_previews_display(self, page: Page):
"""AI: Test that both log table previews are displayed."""
page.goto("http://localhost:8000")
# Check nginx table preview
nginx_table = page.locator("#nginx-table")
expect(nginx_table).to_be_visible()
# Check nexus table preview
nexus_table = page.locator("#nexus-table")
expect(nexus_table).to_be_visible()
def test_sql_query_execution(self, page: Page):
"""AI: Test SQL query execution through web interface."""
page.goto("http://localhost:8000")
# Enter SQL query
query_textarea = page.locator("#sql-query")
query_textarea.fill("SELECT COUNT(*) as total FROM nginx_logs")
# Execute query
execute_button = page.locator("#execute-query")
execute_button.click()
# Verify results displayed
results_div = page.locator("#query-results")
expect(results_div).to_contain_text("total")Systematic Test Coverage Assessment:
- MANDATORY: Conduct systematic test coverage assessment after completing significant functionality
- Trigger Points: Major feature completion, release milestones, coverage below 80%, architectural changes
- Process: Run coverage analysis, identify gaps, create phase-based improvement plan, implement with proven patterns
- Target Standards:
- Overall: 85%+
- Critical modules: 90%+
- Core logic: 85%+
- Infrastructure: 80%+
- Documentation: Maintain
docs/TEST_COVERAGE_IMPROVEMENT_PLAN.mdwith current status, roadmap, and lessons learned - Mock vs Real Testing: Ensure critical paths are tested with real implementations, not just mocked components
- Attribute Access Validation: Test actual object attribute chains, as mocks may not catch incorrect attribute access patterns
def process_large_file(self, file_path: Path) -> Iterator[Dict]:
"""AI: Process large files using generators to control memory usage."""
with open(file_path, 'r') as f:
batch = []
for line_num, line in enumerate(f, 1):
parsed = self.parse_log_line(line.strip(), line_num, str(file_path))
if parsed:
batch.append(parsed)
if len(batch) >= self.batch_size:
yield batch
batch = [] # Free memory
if batch: # Final batch
yield batch
def process_all_batches(self, file_path: Path):
"""AI: Process file in batches with database commits."""
for batch in self.process_large_file(file_path):
self.db_ops.batch_insert(self.model_class, batch)
# Memory freed after each batch# Use indexes effectively
def get_logs_by_timerange(self, start_time: datetime, end_time: datetime) -> List[Dict]:
"""AI: Query logs using timestamp index for efficiency."""
query = """
SELECT method, path, COUNT(*) as hits
FROM nginx_logs
WHERE timestamp BETWEEN ? AND ?
GROUP BY method, path
ORDER BY hits DESC
LIMIT 100
"""
return self.execute_query(query, [start_time, end_time])
# Batch operations
def bulk_insert_with_transaction(self, data: List[Dict]):
"""AI: Use transactions for bulk operations."""
with self.get_session() as session:
session.execute(
insert(self.model_class),
data
)
# Single commit for entire batchimport sqlparse
from sqlparse.sql import Statement
from sqlparse.tokens import Keyword
def validate_select_only(query: str) -> bool:
"""
AI: Validate that query contains only SELECT statements.
Uses sqlparse to analyze SQL structure and reject any
non-SELECT operations for security.
"""
try:
parsed = sqlparse.parse(query)
for statement in parsed:
if statement.get_type() != 'SELECT':
return False
return True
except Exception:
return False
def sanitize_query(query: str) -> str:
"""AI: Sanitize query string and add safety limits."""
query = query.strip()
# Add LIMIT if not present
if 'LIMIT' not in query.upper():
query += ' LIMIT 1000'
return querydef validate_file_path(file_path: Path, allowed_base: Path) -> bool:
"""
AI: Validate that file path is within allowed directory.
Prevents directory traversal attacks and ensures files
are only accessed from configured log directories.
"""
try:
resolved_path = file_path.resolve()
allowed_resolved = allowed_base.resolve()
return resolved_path.is_relative_to(allowed_resolved)
except Exception:
return False
def safe_extract_archive(archive_path: Path, max_size: int = 100_000_000, max_depth: int = 3) -> bool:
"""
AI: Safely extract archive with size, path, and depth checks.
Prevents zip bombs, directory traversal, and infinite recursion
during nested archive extraction.
"""
total_size = 0
def _extract_recursive(arch_path: Path, current_depth: int = 0):
nonlocal total_size
if current_depth > max_depth:
raise SecurityError(f"Archive nesting too deep: {current_depth} levels")
with zipfile.ZipFile(arch_path, 'r') as archive:
for member in archive.infolist():
# Check for directory traversal
if os.path.isabs(member.filename) or '..' in member.filename:
raise SecurityError(f"Unsafe path in archive: {member.filename}")
# Check extracted size
total_size += member.file_size
if total_size > max_size:
raise SecurityError("Archive too large - possible zip bomb")
_extract_recursive(archive_path)
return TrueCRITICAL: Use single entry point through app.main:
# CORRECT: Use main app entry point
uv run python -m app.main --mcp-stdio
# INCORRECT: Create separate MCP server files
# DO NOT create standalone run_mcp_*.py scriptsfrom enum import Enum
class TransportMode(Enum):
"""AI: MCP server transport modes."""
STDIO = "stdio" # For VS Code Copilot
NETWORK = "network" # For network-based clients
class LogAnalysisMCPServer:
"""AI: Unified MCP server supporting multiple transports."""
def __init__(
self,
db_ops: DatabaseOperations,
transport_mode: TransportMode = TransportMode.NETWORK
):
self.db_ops = db_ops
self.transport_mode = transport_mode
self.server = Server("log-analysis")
self._register_tools()async def execute_sql_query(self, query: str, limit: int = 100) -> List[Dict[str, Any]]:
"""
AI: Execute SELECT query with comprehensive security validation.
Security measures:
- Only SELECT statements allowed
- Query parsing and validation
- Result limits enforced
- Input sanitization
"""
# Validate SELECT only
if not self._is_select_query(query):
raise SecurityError("Only SELECT queries are allowed")
# Apply result limits
safe_query = self._apply_limit(query, min(limit, 1000))
try:
return self.db_ops.execute_query(safe_query)
except Exception as e:
# Log security events
logger.warning(f"MCP query failed: {e}")
raise MCPToolError(f"Query execution failed: {str(e)}")CRITICAL: Verify actual attribute access patterns:
# CORRECT attribute chain
db_path = self.db_ops.db_connection.db_path
# INCORRECT - Will fail at runtime
db_path = self.db_ops.db_path # AttributeError!def create_stdio_server(db_ops: DatabaseOperations) -> LogAnalysisMCPServer:
"""AI: Factory for VS Code Copilot stdio integration."""
return LogAnalysisMCPServer(db_ops, TransportMode.STDIO)
def create_network_server(db_ops: DatabaseOperations, host: str = "0.0.0.0", port: int = 8001) -> LogAnalysisMCPServer:
"""AI: Factory for network-based MCP clients."""
return LogAnalysisMCPServer(db_ops, TransportMode.NETWORK, host, port)# Validate directories only when needed
if process_logs and (not nexus_dir or not nginx_dir):
print("❌ Error: --nexus-dir and --nginx-dir are required when using --process-logs")
sys.exit(1)
# Auto-assign dummy directories for MCP-only mode
if mcp_stdio and not nexus_dir:
nexus_dir = "/tmp" # Dummy directory for MCP modedocs/
├── SPEC.md # Technical specification and architecture
├── adr/ # Architectural Decision Records
│ ├── ADR_YYYYMMDD_NN_*.md
└── [future docs] # API docs, user guides, etc.
README.md # Project overview and setup instructions
AGENTS.md # This file - AI assistant instructions
- Location: All ADRs stored in
docs/adr/following industry standards - Naming Convention:
ADR_YYYYMMDD_NN_description.mdformat - Discovery: Use
find docs/adr/ -name "*.md" | sortto list all current ADRs - Usage: Reference ADRs by ID in code comments and documentation
- Updates: Create new ADR file for architectural changes, never modify existing ADRs
- Knowledge Source: Always check
docs/adr/directory for current architectural decisions - Technical Foundation: Review
docs/SPEC.mdfor comprehensive system architecture before implementing changes
- MANDATORY: Every commit must be made on a separate feature branch
- NEVER commit directly to main branch
- Branch naming:
feature/descriptionorfix/description - Each PR must include:
- Concise summary of changes
- Reference to user requirements that led to changes
- Test results confirmation
- Updated documentation if applicable
MANDATORY: Run all tests before creating PR:
- Unit/Integration:
uv run pytest tests/unit tests/integration - E2E:
./run_playwright_e2e.sh
MANDATORY: Add all new/modified files to git: git add .
MANDATORY: Clean code with zero warnings requirement:
- All pytest runs must complete without warning messages
- No deprecation warnings from dependencies
- No unused imports, variables, or unreachable code
- Proper resource cleanup to prevent ResourceWarnings
- Mock configuration validated to avoid warnings
MANDATORY: Configuration consistency check:
-
Verify
.env.examplereflects all Settings class fields -
Update
.env.exampleif new configuration options added -
Check CLI options match Settings class properties
-
Ensure documentation reflects current configuration options
-
Ensure no untracked files remain that should be committed
-
Verify all tests pass before opening PR
- Don't hardcode configuration values - Always use Settings instance
- Don't create separate MCP server files - Use unified app entry point
- Don't skip integration tests - Configuration changes need end-to-end validation
- Don't rely solely on mock tests - Critical paths need real object testing
- Don't forget nested archive support - File discovery must handle recursive extraction
- Don't modify ADRs - Create new ones for architectural changes
- Don't forget "AI:" prefix - All docstrings must include this prefix
- Don't use pip directly - Always use
uvfor dependency management - Don't ignore warnings - All pytest runs must be warning-free
- All tests pass (unit, integration, E2E, MCP)
- Code follows style guidelines
- Documentation updated
- Error handling implemented
- Security considerations addressed
- Performance optimizations applied
- All files added to git
- MCP integration follows unified architecture
- Configuration consistency verified:
-
.env.examplematches Settings class fields - CLI options align with configuration properties
- Documentation reflects current configuration options
-
- Clean code with zero warnings:
- No pytest warnings in test output
- No deprecation warnings from dependencies
- No unused imports or variables
- No runtime warnings from async operations
- Proper resource cleanup to prevent ResourceWarnings
- Mock objects properly configured to avoid warnings
- Branch created from main
- All requirements implemented
- Tests comprehensive and passing
- Documentation complete
- Security review completed
- Performance acceptable
- User requirements addressed
- No duplicate MCP server implementations
Configuration file: .vscode/mcp.json
{
"servers": {
"log-analysis": {
"command": "uv",
"args": ["run", "python", "-m", "app.main", "--db-name", "log_analysis.db", "--mcp-stdio"]
}
}
}Ask questions like:
- "Show me the nginx log table structure"
- "What are the most common HTTP methods?"
- "Find all 404 errors"
ALL code changes MUST go through pull requests. No direct commits to main.
For complete contribution guidelines, see CONTRIBUTING.md.
-
Read the documentation:
- This AGENTS.md file (you're reading it)
- CONTRIBUTING.md for PR workflow and conventions
- docs/best-practices/PULL_REQUESTS.md for detailed PR process
- Project-specific best practices in
docs/best-practices/
-
Create a feature branch:
git checkout -b feature/your-feature-name # OR git checkout -b fix/bug-description -
Make your changes:
- Follow conventions in CONTRIBUTING.md
- Use
logger.*()for all output (neverprint()) - Add type hints to all functions
- Write comprehensive tests
-
Test thoroughly:
uv run pytest # All 330 tests must pass uv run pytest --cov=app # Check coverage (>85% target)
-
Create a pull request:
git push origin feature/your-feature-name gh pr create --title "feat: Your feature" --body "..."
CRITICAL: When implementing features or fixes:
- Always use EnterPlanMode for non-trivial changes before writing code
- Always create feature branch - NEVER commit directly to main
- Always create PR - NEVER push directly to main
- Always use logger - NEVER use
print()(corrupts MCP stdio protocol) - Always review documentation - Update AGENTS.md, SPEC.md, README.md as needed
See CONTRIBUTING.md for complete AI agent guidelines.
Before marking work complete, verify:
- All 330 tests pass
- Coverage meets target (>85% for new code)
- No
print()statements in production code - Logger outputs to stderr only
- Type hints on all new functions
- Documentation updated (AGENTS.md, SPEC.md, README.md, CHANGELOG.md)
- PR created and ready for review
- All commits follow conventional commit format
See CONTRIBUTING.md for complete Definition of Done checklist.