run_with_logger
Like subprocess.run, but with the ability to pipe stdout and/or stderr to a Logger or capture each stream
independently while the process is running.
What it can do
- Run local commands.
- Run remote commands over SSH.
- For each output stream (
stdoutandstderr), choose independently whether to log, capture, or discard it. - Pipe bytes or files to
stdin. - Pass environment variables to the subprocess (even via SSH if the server's
AcceptEnvsetting allows it). - Enforce configurable timeouts for local subprocesses, remote SSH commands, and SSH network operations.
- Support many of the same arguments as
subprocess.run. - Provide access to the running process and incrementally captured output via a context manager.
Examples
Log stderr and capture stdout
The main process will wait for the subprocess while:
- forwarding
stderrline by line to the logger - capturing
stdoutin aCompletedProcessobject, which is returned when the process finishes
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["foo", "bar"],
logger=logger,
stdout_action="capture",
stderr_action="log",
check=False,
)
print(completed.stdout)
Log stdout and discard stderr
The main process will wait for the subprocess while:
- forwarding
stdoutline by line to the logger - discarding
stderr
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["foo", "bar"],
logger=logger,
stdout_action="log",
stderr_action="discard",
check=False,
)
Capture both streams
Use "capture" for both streams when you want the final CompletedProcess to contain bytes from stdout and stderr.
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip()) # "out"
print(completed.stderr.decode().strip()) # "err"
Choose the log level and decoding
from logging import INFO, getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
run_with_logger(
args=["python", "-c", "print('hello')"],
logger=logger,
level=INFO,
encoding="utf-8",
stdout_action="log",
stderr_action="discard",
)
Send bytes to stdin
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import sys; print(sys.stdin.read().upper())"],
logger=logger,
stdin_data=b"hello",
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip()) # "HELLO"
Pipe a file to stdin
from logging import getLogger
from pathlib import Path
from run_with_logger import run_with_logger
logger = getLogger(__name__)
input_path = Path("input.txt")
with input_path.open("rb") as stdin:
completed = run_with_logger(
args=["python", "-c", "import sys; print(sys.stdin.read())"],
logger=logger,
stdin_io=stdin,
stdout_action="capture",
stderr_action="capture",
)
Add environment variables
extra_env is merged with the current environment.
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import os; print(os.environ['APP_MODE'])"],
logger=logger,
extra_env={"APP_MODE": "production"},
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip()) # "production"
Use a shell command
Like subprocess.Popen, pass a string and shell=True when you intentionally want shell parsing.
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args="printf 'hello from the shell\n'",
shell=True,
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
Handle non-zero exit codes
With the default check=True, non-zero exits raise CalledProcessError.
Captured streams are attached to the exception.
from logging import getLogger
from subprocess import CalledProcessError
from run_with_logger import run_with_logger
logger = getLogger(__name__)
try:
run_with_logger(
args=["python", "-c", "import sys; print('bad'); sys.exit(2)"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
except CalledProcessError as error:
print(error.returncode) # 2
print(error.output.decode()) # captured stdout
Set check=False when you want to inspect the return code yourself:
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import sys; print('bad'); sys.exit(2)"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
check=False,
)
print(completed.returncode) # 2
print(completed.stdout.decode()) # captured stdout
Inspect a running process
Use run_with_logger__cm if you need access to the Popen object while the process is still running.
Captured streams are available as BytesIO buffers.
import time
from io import BytesIO
from logging import getLogger
from run_with_logger import run_with_logger__cm
logger = getLogger(__name__)
with run_with_logger__cm(
args=["python", "-uc", "import time; print('one'); time.sleep(1); print('two')"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
) as info:
stdout_buffer = info["stdout_buffer"]
assert isinstance(stdout_buffer, BytesIO)
while info["process"].poll() is None:
partial_stdout = stdout_buffer.getvalue()
# Do something with partial_stdout while the process runs.
time.sleep(0.1)
completed = info["completed"]
Run a remote command over SSH
Install SSH support first:
python -m pip install "run_with_logger[ssh]"
Then pass either a connected paramiko.SSHClient or a fabric.Connection.
from contextlib import closing
from logging import getLogger
from paramiko import SSHClient
from run_with_logger import run_with_logger__ssh
logger = getLogger(__name__)
with closing(SSHClient()) as client:
# Configure and connect the client as appropriate for your environment.
completed = run_with_logger__ssh(
client=client,
command="whoami",
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip())
run_with_logger__ssh__cm provides the same context-manager pattern for SSH channels.
Metadata
Release files for run-with-logger 1.5.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 | |
|---|---|---|---|
| run_with_logger-1.5.0.tar.gz | 22.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| run_with_logger-1.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.5 kB
Release files / run_with_logger-1.5.0.tar.gz
| Download URL | run_with_logger-1.5.0.tar.gz |
|---|---|
| Size | 22.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c345970272ae81ded9da6d824315e41f17f50b500211b1dccaa80036366d094
|
|
BLAKE2b-256 checksum How to use checksums |
5ecef8c4edcc8cc663aeda34d8f32a9ba9d89c14148af7f5ae5b5ee8ae0220b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jun 5, 2026.
Transparency logRelease files / run_with_logger-1.5.0-py3-none-any.whl
| Download URL | run_with_logger-1.5.0-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
56470d71cc0d2abd84fade5084960d7a39f7f49c3a957c49cdd9ad3f81e82839
|
|
BLAKE2b-256 checksum How to use checksums |
36ef35338c3117a74a49d8b9411f4e60eb3b44529e843624a08d0df7eb6779af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jun 5, 2026.
Transparency log