pytest-junit-logging
A pytest plugin that captures log output during test execution and embeds it into JUnit XML reports.
Features
- 📋 Log Integration: Automatically captures all log output during test execution and embeds it into JUnit XML reports
- 🎯 Smart Scope Distribution: Session-scoped logs appear in all tests, module-scoped logs appear in module tests, function-scoped logs appear only in specific tests
- ⚡ Execution Phase Tags: Each log entry includes a
stepattribute indicating whether it occurred during setup or test execution - 🔧 Configurable Log Levels: Control verbosity with
--junit-log-leveloption - 🚨 Assertion Capture: Automatically captures assertion failure messages as ASSERT-level logs
- 📁 Relative Paths: File paths in logs are relative to project root for portability
- 🔒 Safe HTML: Automatically escapes HTML entities in log messages
Installation
pip install pytest-junit-logging
Usage
The plugin activates automatically when you use pytest's --junit-xml option:
# Basic usage - captures all logs (DEBUG level and above)
pytest --junit-xml=results.xml
# Control log verbosity
pytest --junit-xml=results.xml --junit-log-level=WARNING
# Only capture errors and critical logs
pytest --junit-xml=results.xml --junit-log-level=ERROR
Command Line Options
| Option | Choices | Default | Description |
|---|---|---|---|
--junit-log-level |
DEBUG, INFO, WARNING, ERROR, CRITICAL |
DEBUG |
Minimum log level to include in JUnit XML |
Example
Test Code
# test_example.py
import logging
import pytest
@pytest.fixture(scope="session")
def database():
logging.info("Setting up database connection")
yield "db_connection"
logging.info("Closing database connection")
@pytest.fixture
def user_data():
logging.debug("Creating test user data")
return {"id": 123, "name": "Test User"}
def test_user_creation(database, user_data):
logging.info("Starting user creation test")
logging.debug(f"Using database: {database}")
logging.debug(f"User data: {user_data}")
# Simulate test logic
assert user_data["id"] == 123
logging.info("User creation test completed successfully")
def test_user_validation(database, user_data):
logging.warning("This is a validation test")
assert user_data["name"] == "Test User"
Generated JUnit XML
pytest test_example.py --junit-xml=results.xml --junit-log-level=INFO
The resulting results.xml will include embedded logs:
<?xml version='1.0' encoding='utf-8'?>
<testsuites name="pytest tests">
<testsuite name="pytest" errors="0" failures="0" skipped="0" tests="2" time="0.01">
<testcase classname="test_example" name="test_user_creation" time="0.001">
<logs>
<log step="setup" ts="2025-11-27T10:00:00.000000+00:00" level="INFO" src="test_example.py:8">Setting up database connection</log>
<log step="test" ts="2025-11-27T10:00:00.100000+00:00" level="INFO" src="test_example.py:15">Starting user creation test</log>
<log step="test" ts="2025-11-27T10:00:00.200000+00:00" level="INFO" src="test_example.py:22">User creation test completed successfully</log>
</logs>
</testcase>
<testcase classname="test_example" name="test_user_validation" time="0.001">
<logs>
<log step="setup" ts="2025-11-27T10:00:01.000000+00:00" level="INFO" src="test_example.py:8">Setting up database connection</log>
<log step="test" ts="2025-11-27T10:00:01.100000+00:00" level="WARNING" src="test_example.py:25">This is a validation test</log>
<log step="test" ts="2025-11-27T10:00:01.200000+00:00" level="INFO" src="test_example.py:10">Closing database connection</log>
</logs>
</testcase>
</testsuite>
</testsuites>
Log Scope Distribution
The plugin intelligently distributes logs based on fixture scopes:
- Session-scoped fixture logs: Appear in all test cases
- Module-scoped fixture logs: Appear in all tests within the same module
- Function-scoped fixture logs: Appear only in the specific test that uses the fixture
- Test execution logs: Appear only in their respective test case
Execution Phase Tags
Each log entry includes a step attribute that indicates during which execution phase the log was generated:
step="setup": Logs from fixture setup (session, module, function fixture initialization)step="test": Logs from test execution and fixture teardown
This makes it easy to distinguish between infrastructure logs (fixture setup) and actual test logic:
<logs>
<log step="setup" ts="..." level="INFO" src="conftest.py:10">Database connection established</log>
<log step="setup" ts="..." level="INFO" src="conftest.py:25">User fixtures initialized</log>
<log step="test" ts="..." level="DEBUG" src="test_user.py:15">Starting user validation test</log>
<log step="test" ts="..." level="INFO" src="test_user.py:20">User validation completed</log>
<log step="test" ts="..." level="INFO" src="conftest.py:30">Cleaning up user fixtures</log>
</logs>
Post-processing: To flatten the structure and remove step attributes for legacy compatibility, simply strip the step="..." attributes from log elements.
Log Level Filtering
Control the verbosity of logs in your JUnit XML:
# Capture everything (default)
pytest --junit-xml=results.xml --junit-log-level=DEBUG
# Only warnings and above
pytest --junit-xml=results.xml --junit-log-level=WARNING
# Only errors and critical
pytest --junit-xml=results.xml --junit-log-level=ERROR
Note: ASSERT level logs (from assertion failures) are always included regardless of the log level filter.
Integration with CI/CD
The plugin works seamlessly with CI/CD systems that consume JUnit XML reports:
- GitHub Actions: Use with
dorny/test-reporter - Jenkins: Native JUnit plugin support
- GitLab CI: Built-in JUnit report processing
- Azure DevOps: Test result publishing
Requirements
- Python 3.10+
- pytest 6.0+
Development
Setup Development Environment
# Clone the repository
git clone <repository-url>
cd pytest-junit-logging
# Install in development mode with dev dependencies
pip install -e ".[dev]"
Running Tests
Local Testing
# Run tests for current Python version
pytest
# Run tests across all supported Python/pytest combinations (requires all Python versions)
tox
# Run specific tox environment
tox -e py312-pytest8
Docker-based Testing (Recommended)
# Run all tests across all Python versions in Docker
./scripts/test-all.sh
# Test specific Python version
./scripts/test-single.sh 3.11 8 # Python 3.11 with pytest 8.x
# Test individual components with docker-compose
docker-compose -f docker-compose.test.yml run --rm test-py310 # Python 3.10 tests
docker-compose -f docker-compose.test.yml run --rm test-quality # Linting & coverage
docker-compose -f docker-compose.test.yml run --rm test-mindeps # Minimum dependencies
Tox Test Matrix
The project tests against these combinations:
| Python | pytest 6.x | pytest 7.x | pytest 8.x |
|---|---|---|---|
| 3.10 | ✅ | ✅ | ✅ |
| 3.11 | - | ✅ | ✅ |
| 3.12 | - | - | ✅ |
| 3.13 | - | - | ✅ |
Code Quality
Manual Code Quality Checks
# Format code
black src/ tests/
# Lint code
ruff check src/ tests/
# Type checking
mypy src/
Pre-commit Hooks (Recommended)
Install pre-commit to automatically run quality checks before commits:
# Install pre-commit
pip install pre-commit
# Install the git hook scripts
pre-commit install
# (Optional) Run against all files
pre-commit run --all-files
Once installed, pre-commit will automatically run:
- Black (code formatting)
- Ruff (linting with auto-fixes)
- MyPy (type checking)
- Bandit (security scanning)
- Basic file checks (trailing whitespace, YAML validation, etc.)
This ensures all committed code meets quality standards and matches the CI pipeline checks.
CI/CD and Releases
Automated Testing
The project uses GitHub Actions for comprehensive CI/CD:
- Test Matrix: Runs all tests across Python 3.10-3.13 and pytest 6.x-8.x combinations
- Code Quality: Automated linting (ruff), formatting (black), and type checking (mypy)
- Integration Tests: End-to-end testing with real pytest runs and XML validation
- Security Scanning: Automated security scans with bandit
- Coverage Reporting: Code coverage tracking and reporting
All checks must pass before code can be merged to main.
Releases
Releases are fully automated:
- Create a git tag with version format
vX.Y.Z(e.g.,v1.0.0,v2.1.3) - Push the tag to trigger the release workflow:
git tag v1.0.0 git push origin v1.0.0
- Automated pipeline runs:
- Full test suite across all supported versions
- Code quality checks
- Package building and validation
- PyPI publishing (using trusted publishing)
- GitHub release creation with changelog
Version Management
The project uses setuptools-scm for automatic version management:
- Version is automatically derived from git tags
- No manual version bumping required
- Supports semantic versioning and pre-releases (e.g.,
v1.0.0-beta1)
License
MIT License - see LICENSE file for details.
Metadata
Release files for pytest-junit-logging 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_junit_logging-0.1.0.tar.gz | 34.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_junit_logging-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.0 kB
Release files / pytest_junit_logging-0.1.0.tar.gz
| Download URL | pytest_junit_logging-0.1.0.tar.gz |
|---|---|
| Size | 34.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
03b22edd822d0c010df65def56e3a577314be3c2af8b8b93c11e9612f55acf0c
|
|
BLAKE2b-256 checksum How to use checksums |
8b1d437ab81482d43a98151d5eae820cb896d440d162a56b9fb0187ad56391c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 27, 2025.
Transparency logRelease files / pytest_junit_logging-0.1.0-py3-none-any.whl
| Download URL | pytest_junit_logging-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1ece2266ba1d2491da4c155c25410fcc9d5c8979426b9439e1ec7cc5ca0ab1dc
|
|
BLAKE2b-256 checksum How to use checksums |
c9332dae619380a9dd28f6504c6e1ee1969a5509e75fd80e994e9296961ba72f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 27, 2025.
Transparency log