A Python decorator that automatically creates GitHub issues when functions encounter errors or log critical messages
Project description
Bug Hunter ๐๐ซ
Automatic GitHub issue creation for Python errors using AI-powered descriptions
Bug Hunter is a Python library that provides a decorator-based system for automatically creating GitHub issues when functions encounter errors or log critical messages. It uses Google's Gemini AI to generate intelligent, descriptive issue content that helps developers understand and fix problems faster.
โจ Features
- ๐ Automatic Error Detection: Captures both exceptions and logger.error calls
- ๐ค AI-Powered Descriptions: Uses Gemini AI to generate detailed, helpful issue descriptions
- ๐ Smart GitHub Integration: Automatically creates well-formatted GitHub issues
- ๐ซ Duplicate Prevention: Automatically detects and prevents duplicate issues using deterministic titles
- โก Thread-Safe: Works correctly in multi-threaded applications
- ๐ก๏ธ Robust Error Handling: Includes fallback mechanisms and retry logic
- ๐ท๏ธ Configurable: Support for custom labels, assignees, and more
๐ Quick Start
Installation
pip install exc2issue
Setup
-
Get API Keys:
GitHub Authentication (Choose one):
-
Option 1: Personal Access Token (PAT) - Simple, good for personal projects
- Go to GitHub Settings โ Developer settings โ Personal access tokens
- Create token with
repoorpublic_repopermissions
-
Option 2: GitHub App (Recommended for Organizations) - More secure, fine-grained permissions
- Create a GitHub App at: https://github.com/settings/apps/new
- Set required permissions: Issues (Read & Write), Contents (Read)
- Install the app on your repository/organization
- Note your App ID, Installation ID, and download the private key
AI Provider:
- Gemini API Key: Get one from Google AI Studio
-
-
Set Environment Variables:
For Personal Access Token (PAT):
export GITHUB_TOKEN="your_github_token_here" export GEMINI_API_KEY="your_gemini_api_key_here"
For GitHub App:
export GITHUB_APP_ID="your_app_id" export GITHUB_APP_INSTALLATION_ID="your_installation_id" export GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- Your private key here -----END RSA PRIVATE KEY-----" export GEMINI_API_KEY="your_gemini_api_key_here"
Basic Usage
from exc2issue import exc2issue
@exc2issue(
labels=["bug", "auto-generated"],
repository="your-username/your-repo"
)
def risky_function(data):
# This will automatically create a GitHub issue if an error occurs
if not data:
raise ValueError("Data cannot be empty")
return process_data(data)
# If this function fails, a GitHub issue will be created automatically
risky_function(None)
Advanced Configuration
from exc2issue import exc2issue
import logging
# Configure with custom settings
@exc2issue(
labels=["bug", "critical", "production"],
assignees=["maintainer-username", "team-lead"], # Assign to multiple users
repository="your-org/your-repo",
github_token="custom_token", # Optional: override env var
gemini_api_key="custom_key" # Optional: override env var
)
def critical_function():
logger = logging.getLogger(__name__)
try:
# Some critical operation
result = perform_operation()
except Exception as e:
# This will also create an issue
logger.error("Critical operation failed: %s", str(e))
raise
return result
๐ง Configuration Options
Decorator Parameters
|| Parameter | Type | Required | Description |
||-----------|------|----------|-------------|
|| repository | str | โ
Yes | GitHub repository in format "owner/repo" |
|| labels | List[str] | โ No | Labels to apply to created issues |
|| assignees | List[str] | โ No | GitHub usernames to assign issues to |
|| assignee | str | โ No | Single GitHub username (legacy, use assignees instead) |
|| github_token | str | โ No | GitHub PAT (defaults to GITHUB_TOKEN env var) |
|| gemini_api_key | str | โ No | Gemini API key (defaults to GEMINI_API_KEY env var) |
Pydantic Configuration (Advanced)
Bug Hunter uses Pydantic for configuration management, providing strong typing and validation. You can use environment variables with the BUG_HUNTER_ prefix to configure all aspects of the library:
from exc2issue.config import get_settings
# Load settings from environment variables or .env file
settings = get_settings()
@exc2issue(repository="your-org/repo", settings=settings)
def your_function():
# Function implementation
pass
Environment Variable Configuration:
- GitHub Settings (PAT):
BUG_HUNTER_GITHUB_TOKEN,BUG_HUNTER_GITHUB_BASE_URL - GitHub Settings (App):
BUG_HUNTER_GITHUB_APP_ID,BUG_HUNTER_GITHUB_APP_PRIVATE_KEY,BUG_HUNTER_GITHUB_APP_INSTALLATION_ID - Gemini Settings:
BUG_HUNTER_GEMINI_API_KEY,BUG_HUNTER_GEMINI_MODEL_NAME,BUG_HUNTER_GEMINI_TEMPERATURE,BUG_HUNTER_GEMINI_MAX_OUTPUT_TOKENS - Vertex AI Settings:
BUG_HUNTER_VERTEXAI_PROJECT,BUG_HUNTER_VERTEXAI_LOCATION,BUG_HUNTER_VERTEXAI_MODEL_NAME - Global Settings:
BUG_HUNTER_ENABLED,BUG_HUNTER_DRY_RUN - Logging Settings:
BUG_HUNTER_LOGGING_LEVEL,BUG_HUNTER_LOGGING_FORMAT
See .env.example for a complete list of configuration options.
GitHub App Authentication (Recommended for Organizations)
GitHub App authentication provides better security, granular permissions, and auditability compared to Personal Access Tokens. Bug Hunter fully supports GitHub App authentication with automatic fallback to PAT if App authentication fails.
Benefits of GitHub App Authentication:
- โ Fine-grained permissions (only Issues and Contents access needed)
- โ Better security with short-lived tokens
- โ Improved auditability and traceability
- โ Follows GitHub's recommended best practices
- โ Works seamlessly in enterprise environments
Setting Up GitHub App Authentication:
-
Create a GitHub App:
- Go to https://github.com/settings/apps/new (or your organization settings)
- Fill in the basic information (name, description, homepage URL)
- Set permissions:
- Repository permissions โ Issues: Read & Write
- Repository permissions โ Contents: Read (optional, enables AI to access code context for richer issue descriptions)
- Disable webhook (not needed for exc2issue)
-
Install the App:
- After creating, install it on the repositories where you want to use it
- Note the Installation ID from the installation URL
-
Get Credentials:
- From the app settings page, note the App ID
- Generate and download a Private Key (PEM format)
-
Configure Environment Variables:
The private key can be provided in three ways:
Option A: File Path (Recommended)
export GITHUB_APP_ID="123456" export GITHUB_APP_INSTALLATION_ID="98765432" export GITHUB_APP_PRIVATE_KEY="/path/to/your-app-private-key.pem"
Option B: Direct PEM Content (Shell)
export GITHUB_APP_ID="123456" export GITHUB_APP_INSTALLATION_ID="98765432" export GITHUB_APP_PRIVATE_KEY="$(cat path/to/your-app-private-key.pem)"
Option C: .env File with Escaped Newlines
BUG_HUNTER_GITHUB_APP_ID=123456 BUG_HUNTER_GITHUB_APP_INSTALLATION_ID=98765432 BUG_HUNTER_GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"
Priority and Fallback Behavior:
- If both GitHub App and PAT credentials are configured, App authentication takes priority
- If App authentication fails (e.g., token expired, invalid credentials), the system automatically falls back to PAT if available
- This ensures maximum reliability and smooth migration from PAT to App authentication
AI Provider Selection
Bug Hunter supports two AI providers with automatic fallback:
-
Vertex AI (Priority 1): Enterprise-grade AI with Google Cloud authentication
- Requires:
BUG_HUNTER_VERTEXAI_PROJECTand Google Cloud authentication - Best for: Production environments, enterprise deployments
- Authentication: Uses Google Cloud Application Default Credentials (ADC)
- Requires:
-
Gemini Developer API (Priority 2): Simple API key-based authentication
- Requires:
BUG_HUNTER_GEMINI_API_KEY - Best for: Development, testing, personal projects
- Authentication: API key from Google AI Studio
- Requires:
If both are configured, Vertex AI takes priority. If neither is configured, the library falls back to generating basic issue descriptions without AI enhancement.
Setting up Vertex AI (Recommended for Production)
# Install Google Cloud SDK
# Follow instructions at: https://cloud.google.com/sdk/docs/install
# Authenticate for local development
gcloud auth application-default login
# Set your project (or use environment variable)
export BUG_HUNTER_VERTEXAI_PROJECT=your-gcp-project-id
export BUG_HUNTER_VERTEXAI_LOCATION=us-central1 # Optional, defaults to us-central1
For production deployments on Google Cloud (GCE, GKE, Cloud Run), authentication is automatic through service accounts.
๐ What Gets Captured
Bug Hunter captures comprehensive error information:
- Exception Details: Type, message, and full stack trace
- Function Context: Name and arguments passed to the function
- Timestamp: When the error occurred
- Log Messages: Critical log messages (logger.error level and above)
๐ฏ Deterministic Issue Titles
Bug Hunter uses a deterministic title format to prevent duplicate issues for the same error:
Title Format
- Exceptions:
[EXCEPTION]-[function_name]-[ExceptionType] - Log Errors:
[LOG-ERROR]-[function_name]-[MessagePattern]
Examples
[EXCEPTION]-[process_payment]-[ValueError]
[LOG-ERROR]-[database_connect]-[Connection_failed_after_{number}_seconds]
[EXCEPTION]-[send_email]-[SMTPException]
[LOG-ERROR]-[user_lookup]-[User_{email}_not_found]
Benefits
- No Duplicates: Same error type in same function always gets same title
- Highly Readable: Exception type and message patterns are immediately visible
- Informative: Shows actual
ValueError,TypeError, etc. instead of cryptic hashes - Pattern Recognition: Log messages show static patterns with
{placeholders}for variables - Searchable: Easy to find all
ValueErrorissues inprocess_paymentfunction
Pattern Recognition Benefits
- Automatically groups similar errors together
- Makes it easy to identify recurring issues
- Facilitates pattern-based debugging and analysis
Pattern Extraction for Logs
For log messages, variable values are replaced with descriptive placeholders:
- Numbers:
30 secondsโ{number} seconds - Emails:
user@example.comโ{email} - Times:
14:30:25โ{time} - Variables:
user123โ{variable} - This ensures messages like "Connection failed after 30 seconds" and "Connection failed after 120 seconds" produce the same title
๐ซ Duplicate Prevention
Bug Hunter includes intelligent duplicate detection to prevent creating multiple issues for the same error:
How It Works
- Deterministic Titles: Each error type in each function gets a consistent, deterministic title
- Open Issue Search: Before creating a new issue, Bug Hunter searches for existing open issues with the same title
- Skip Creation: If an open issue already exists, creation is skipped and an informative message is logged
- Closed Issues Ignored: Closed issues are ignored, allowing new issues to be created for recurring problems
Example Behavior
@exc2issue(repository="myorg/myapp", labels=["bug"])
def process_payment(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
# First error creates: [EXCEPTION]-[process_payment]-[ValueError]
process_payment(-10) # Creates new issue
# Second identical error is detected as duplicate
process_payment(-5) # Skips creation, logs: "Skipping duplicate issue creation for '[EXCEPTION]-[process_payment]-[ValueError]'"
Fallback Behavior
- If GitHub search fails, the issue will still be created (fail-safe approach)
- Network errors or API rate limits won't prevent issue creation
- All search failures are logged for debugging
Benefits
- Reduced Noise: Eliminates duplicate issues cluttering your repository
- Better Issue Management: One issue per unique error type and location
- Robust Design: Never prevents issue creation even if duplicate detection fails
- Informative Logging: Clear messages when duplicates are detected
๐ค AI-Generated Issue Content
The Gemini AI generates issue descriptions that include:
- Clear summary of what went wrong
- Technical details from the error
- Function arguments and context
- Stack trace analysis (for exceptions)
- Potential impact assessment
- Properly formatted markdown
๐ก๏ธ Error Handling
Bug Hunter is designed to be robust and never interfere with your application:
- Fallback Descriptions: If AI generation fails, creates basic issue descriptions
- Silent Failures: If issue creation fails, logs a warning but doesn't crash your app
- Retry Logic: Automatically retries transient failures
- Input Sanitization: Safely handles sensitive data and special characters
๐ Observability: Logging and Metrics
Bug Hunter provides comprehensive observability capabilities while respecting the separation of concerns: the library instruments the code, but you control the configuration and exposure.
Logging
Bug Hunter uses Python's standard logging module to provide insights into its operation. The library uses the logger name exc2issue.* for all internal logging.
Log Levels Used:
DEBUG: Detailed execution flow (function entry/exit, internal state)WARNING: Expected errors and graceful degradation scenariosERROR: Unexpected errors with full stack tracesCRITICAL: Fatal errors leading to aborts (e.g., SystemExit)
Enabling Logs:
import logging
# Basic setup - enable all exc2issue logs
logging.basicConfig(level=logging.INFO)
logging.getLogger('exc2issue').setLevel(logging.DEBUG)
# Or use more specific logger configuration
logger = logging.getLogger('exc2issue.core.decorator')
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
Example Log Output:
2024-01-15 10:30:45 - exc2issue.core.decorator - DEBUG - Executing decorated function: process_payment
2024-01-15 10:30:45 - exc2issue.core.decorator - ERROR - Exception in process_payment: Invalid amount
2024-01-15 10:30:46 - exc2issue.core.issue_creator - INFO - Creating GitHub issue for process_payment
Important Notes:
- Bug Hunter never calls
logging.basicConfig()or configures handlers - You maintain full control over logging configuration
- All internal operations are logged at appropriate levels
- No print() statements are used
Metrics Collection
Bug Hunter provides a callback-based metrics system that allows you to plug in your preferred metrics backend (Prometheus, StatsD, DataDog, etc.).
Metrics Collected:
- Function execution duration (in seconds)
- Error count by function and error type
- Success/failure count per function
Setting Up Metrics:
from exc2issue import exc2issue, set_metrics_collector
from prometheus_client import Counter, Histogram
# 1. Implement the MetricsCollector protocol
class PrometheusCollector:
def __init__(self):
self.duration = Histogram(
'exc2issue_duration_seconds',
'Function execution duration',
['function']
)
self.errors = Counter(
'exc2issue_errors_total',
'Error count',
['function', 'error_type']
)
self.successes = Counter(
'exc2issue_successes_total',
'Success count',
['function']
)
def record_duration(self, function_name: str, duration_seconds: float) -> None:
self.duration.labels(function=function_name).observe(duration_seconds)
def record_error(self, function_name: str, error_type: str, error: BaseException) -> None:
self.errors.labels(function=function_name, error_type=error_type).inc()
def record_success(self, function_name: str) -> None:
self.successes.labels(function=function_name).inc()
# 2. Register your collector
set_metrics_collector(PrometheusCollector())
# 3. Use the decorator as normal
@exc2issue(repository="myorg/myapp", labels=["bug"])
def my_function():
# Metrics are automatically collected
pass
StatsD Example:
from exc2issue import set_metrics_collector
import statsd
class StatsDCollector:
def __init__(self, host='localhost', port=8125):
self.client = statsd.StatsClient(host, port)
def record_duration(self, function_name: str, duration_seconds: float) -> None:
self.client.timing(f'exc2issue.{function_name}.duration', duration_seconds * 1000)
def record_error(self, function_name: str, error_type: str, error: BaseException) -> None:
self.client.incr(f'exc2issue.{function_name}.errors.{error_type}')
def record_success(self, function_name: str) -> None:
self.client.incr(f'exc2issue.{function_name}.success')
set_metrics_collector(StatsDCollector())
DataDog Example:
from exc2issue import set_metrics_collector
from datadog import statsd
class DataDogCollector:
def record_duration(self, function_name: str, duration_seconds: float) -> None:
statsd.histogram('exc2issue.duration', duration_seconds, tags=[f'function:{function_name}'])
def record_error(self, function_name: str, error_type: str, error: BaseException) -> None:
statsd.increment('exc2issue.errors', tags=[f'function:{function_name}', f'error_type:{error_type}'])
def record_success(self, function_name: str) -> None:
statsd.increment('exc2issue.success', tags=[f'function:{function_name}'])
set_metrics_collector(DataDogCollector())
Important Notes:
- Bug Hunter never starts HTTP servers or exposes endpoints
- No metrics storage or aggregation is performed by the library
- You control how and where metrics are sent
- If no collector is set, metrics collection is skipped (no errors)
- If metrics collection fails, the decorator continues normally
MetricsCollector Protocol:
from typing import Protocol
class MetricsCollector(Protocol):
"""Interface that users implement for their metrics backend"""
def record_duration(self, function_name: str, duration_seconds: float) -> None:
"""Record function execution duration"""
...
def record_error(self, function_name: str, error_type: str, error: BaseException) -> None:
"""Record error occurrence"""
...
def record_success(self, function_name: str) -> None:
"""Record successful execution"""
...
๐ง Developer Reference
Utility Functions
For advanced usage or custom integrations, Bug Hunter provides utility functions:
from exc2issue.utils import (
generate_deterministic_title,
sanitize_function_name,
validate_title_format
)
# Generate deterministic title
title = generate_deterministic_title("my_func", "ValueError", "Invalid input provided")
# Result: "[EXCEPTION]-[my_func]-[ValueError]"
# Generate log pattern title
log_title = generate_deterministic_title("connect_db", "Log", "Connection failed after 30 seconds")
# Result: "[LOG-ERROR]-[connect_db]-[Connection_failed_after_{number}_seconds]"
# Sanitize problematic function names
safe_name = sanitize_function_name("<lambda>")
# Result: "lambda"
# Validate title format
is_valid = validate_title_format("[EXCEPTION]-[my_func]-[ValueError]")
# Result: True
Title Generation Logic
- Function Name Sanitization: Removes problematic characters like
<>, handles special cases like<module> - Exception Handling: Uses actual exception type (ValueError, TypeError) for maximum clarity
- Log Pattern Extraction: Replaces variable values with descriptive placeholders (
{number},{email},{time}) - Format Assembly: Combines category, function name, and exception type or message pattern
๐ Examples
Exception Handling
@exc2issue(labels=["bug"], repository="myorg/myapp")
def divide_numbers(a, b):
return a / b # ZeroDivisionError will create an issue
divide_numbers(10, 0) # Creates: "[EXCEPTION]-[divide_numbers]-[ZeroDivisionError]"
Logger Error Monitoring
import logging
@exc2issue(labels=["error", "database"], repository="myorg/myapp")
def connect_to_database():
logger = logging.getLogger(__name__)
try:
# Database connection logic
db.connect()
except ConnectionError:
logger.error("Failed to connect to database after 3 retries")
# This creates an issue even without raising an exception
๐๏ธ Development
Local Development Setup
# Clone the repository
git clone https://github.com/your-username/exc2issue.git
cd exc2issue
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install development dependencies
uv pip install -e ".[dev]"
# Run tests
pytest
# Run linting
ruff check src/
ruff format src/
mypy src/
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=src/exc2issue --cov-report=html --cov-report=term
# Run specific test file
pytest tests/unit/test_decorator.py -v
๐ค Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Ensure all tests pass:
pytest - Run linting:
ruff check src/ && mypy src/ - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
๐ Requirements
- Python 3.12+
- GitHub Authentication (choose one):
- Personal Access Token (PAT), or
- GitHub App credentials (App ID, Installation ID, Private Key)
- Google Gemini API Key (or Vertex AI credentials)
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- Google Gemini AI for intelligent issue descriptions
- GitHub API for seamless issue creation
- The Python community for excellent testing and development tools
Happy Bug Hunting! ๐๐ซ
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exc2issue-0.5.0.tar.gz.
File metadata
- Download URL: exc2issue-0.5.0.tar.gz
- Upload date:
- Size: 57.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89a30de4a8c3b1a1bf6c3ec797f801e2174d079007308b9671e86936049c8dfe
|
|
| MD5 |
90f4ce5d30dca431d539b612a93d7821
|
|
| BLAKE2b-256 |
932edcec5a0587f20e84792483f1ce09647096e338e1d0ca181ac206fb7724eb
|
Provenance
The following attestation bundles were made for exc2issue-0.5.0.tar.gz:
Publisher:
pypi.yaml on DucretJe/exc2issue
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exc2issue-0.5.0.tar.gz -
Subject digest:
89a30de4a8c3b1a1bf6c3ec797f801e2174d079007308b9671e86936049c8dfe - Sigstore transparency entry: 630223553
- Sigstore integration time:
-
Permalink:
DucretJe/exc2issue@6269e7c6d53ba115039da96c82a39771e94e0321 -
Branch / Tag:
refs/tags/0.5.0 - Owner: https://github.com/DucretJe
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@6269e7c6d53ba115039da96c82a39771e94e0321 -
Trigger Event:
release
-
Statement type:
File details
Details for the file exc2issue-0.5.0-py3-none-any.whl.
File metadata
- Download URL: exc2issue-0.5.0-py3-none-any.whl
- Upload date:
- Size: 77.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67842d5961ff2700a7fe0109cbff7fc0668a2bcc2c0633d432d2be3842a41584
|
|
| MD5 |
fd4b2272a6a8b40f22ec080c86452378
|
|
| BLAKE2b-256 |
59e0aa01a2addb135a23283afa7c44ac93505dc90e52e27f0619f08d4dfc4466
|
Provenance
The following attestation bundles were made for exc2issue-0.5.0-py3-none-any.whl:
Publisher:
pypi.yaml on DucretJe/exc2issue
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exc2issue-0.5.0-py3-none-any.whl -
Subject digest:
67842d5961ff2700a7fe0109cbff7fc0668a2bcc2c0633d432d2be3842a41584 - Sigstore transparency entry: 630223582
- Sigstore integration time:
-
Permalink:
DucretJe/exc2issue@6269e7c6d53ba115039da96c82a39771e94e0321 -
Branch / Tag:
refs/tags/0.5.0 - Owner: https://github.com/DucretJe
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@6269e7c6d53ba115039da96c82a39771e94e0321 -
Trigger Event:
release
-
Statement type: