Contributing
loly is open source and welcomes contributions! Bug fixes, new policies, documentation improvements—we'll take them all. The logging apocalypse requires all hands on deck.
Quick Start
Get the code running locally in a few commands:
# Clone the repo (fork it first if you're new)
git clone https://github.com/YOUR_USERNAME/ly.git
cd ly
# Install everything loly needs to breathe
uv sync
# Run the test suite (please don't break them)
uv run pytest
# Run loly on itself (dogfooding is good medicine)
uv run loly src/
Development Setup
Prerequisites
You'll need these things to exist on your machine:
- Python 3.9+ (or 3.12+, we're not picky)
- uv (recommended, it's actually great)
- Or
pip(if you're feeling nostalgic)
Installation
Get your development environment ready:
# Install in development mode (editable installs are your friend)
uv pip install -e .
# Install dev dependencies (linting, testing, docs, etc.)
uv pip install -e ".[dev]"
Running Tests
Make sure everything works as intended:
# Run all tests (the comprehensive check)
uv run pytest
# Run with coverage (see what you're actually testing)
uv run pytest --cov=loly
# Run a specific test (for surgical debugging)
uv run pytest tests/unit/test_exception_exc_info.py
Project Structure
Here's what lives where:
ly/
├── src/loly/
│ ├── policies/ # Where the magic happens
│ │ ├── base.py # Base policy class (extend this)
│ │ ├── exception_exc_info.py # The LY001 detector
│ │ └── log_loop.py # The LY002 detector
│ ├── file_collector.py # Finds Python files (boring but necessary)
│ ├── linter.py # Orchestrates everything
│ └── cli.py # The command-line face
├── tests/
│ ├── unit/ # Tests for individual components
│ └── integration/ # Tests for the whole system
├── docs_site/ # This documentation (MkDocs)
└── battle-test/ # Real-world codebases we tested against
Adding a New Policy
So you want to add a policy? Excellent. Here's the process:
1. Create the Policy Class
Create src/loly/policies/your_policy.py. It's AST visitor time:
from pathlib import Path
from typing import List, Dict
import libcst as cst
from loly.policies.base import BasePolicy
class _YourPolicyVisitor(cst.CSTVisitor):
"""Internal visitor. The name with underscore is a convention."""
def __init__(self, levels: List[str], logger_names: List[str],
file_path: Path, wrapper: cst.MetadataWrapper):
self.levels = levels
self.logger_names = logger_names
self.file_path = file_path
self.violations = []
self._wrapper = wrapper
def visit_Call(self, node: cst.Call) -> None:
# Your detection logic here - traverse the AST
if self._should_flag(node):
self.violations.append({
"file": str(self.file_path),
"line": self._get_line_number(node),
"code": "LY00X",
"message": "Your violation message goes here",
"category": "your_policy",
"severity": "fail"
})
def _get_line_number(self, node: cst.CSTNode) -> int:
try:
pos = self._wrapper.resolve(cst.metadata.PositionProvider)[node]
return pos.start.line
except:
return 0
class YourPolicy(BasePolicy):
"""What does this policy do? Be explicit."""
@classmethod
def check(cls, code: str, file_path: Path, levels: List[str],
logger_names: List[str]) -> List[Dict]:
module = cst.parse_module(code)
wrapper = cst.MetadataWrapper(module)
visitor = _YourPolicyVisitor(levels, logger_names, file_path, wrapper)
wrapper.visit(visitor)
return visitor.violations
2. Add Comprehensive Tests
Create tests/unit/src/loly/policies/test_your_policy.py. Tests are not optional:
from pathlib import Path
from loly.policies.your_policy import YourPolicy
def test_should_pass():
"""Code that follows the rule."""
code = """
logger.info("Valid logging pattern")
"""
violations = YourPolicy.check(code, Path("test.py"), ["info"], ["logger"])
assert len(violations) == 0
def test_should_fail():
"""Code that violates the rule."""
code = """
logger.info("Invalid pattern")
"""
violations = YourPolicy.check(code, Path("test.py"), ["info"], ["logger"])
assert len(violations) == 1
assert violations[0]["code"] == "LY00X"
# Add edge cases, weird scenarios, everything you can think of
3. Register the Policy
Add it to src/loly/linter.py so it actually runs:
from loly.policies.your_policy import YourPolicy
# In the Linter class
POLICIES = {
"exception_exc_info": ExceptionExcInfoPolicy,
"log_loop": LogLoopPolicy,
"your_policy": YourPolicy, # Add your new one here
}
4. Document the Policy
Create docs_site/docs/policies/ly00x-your-policy.md. Make it entertaining. Make it clear. Follow the format of LY001 and LY002.
Code of Conduct
Be kind. Be respectful. Be constructive. We're all trying to make logging better and save each other from 3 AM incidents. That's a shared mission.
Trolling is not welcome. Helping is.
Thank you for contributing to loly! Together, we're making the logging world a better place. 🐕