Logduo
Easy logging and output management for Python scripts and interactive sessions.
Simple by default, configurable for advanced logging workflows.
Platforms: Tested through GitHub Actions on macOS, Windows, and Ubuntu.
Features
- No setup required — safe defaults applied
- Manages output directories and log files automatically
- Supports console-only logging without creating output directories or files
- Provides
help()documentation and actionable error messages - Validates arguments to all Logduo methods and functions
- Safely prunes old run directories containing a Logduo marker file
- Generates optional session artifacts:
config_table.txt,config.json - Emits ANSI-styled and Rich output to console while preserving plain-text logs
- Creates dedicated log files via
new_logger() - Creates advanced pass-through Loguru sinks via
new_loguru_sink() - Creates custom logging levels via
new_level() - Supports nested scripts via
run()andlog.join() - Captures JSONL event streams
- Reports log-generated files in console and log footers
Quick interactive session
>>> from logduo import log, run
>>> log("hello world")
Logging started: 2026-07-31 17:28:27
| INFO | hello world
>>> log.info("hello world again; INFO is the default logging level.")
| INFO | hello world again; INFO is the default logging level.
>>> log.warning("The logging level 'WARNING' is displayed in orange on the console.")
| WARNING | The logging level 'WARNING' is displayed in orange on the console.
>>> help(log.configure)
>>> help(run)
>>> log.close()
───────────────────────────────────────────────────────
Logging ended: 2026-07-31 17:29:12 (duration 45 sec)
Output directory:
/Users/my_name/my_project/logs/session/run_2026_07_31__17_28_27
Log-generated files in output directory:
config_table.txt
session.log
- If a log statement, such as
log("hello world"), is called beforelog.configure(), Logduo applies configuration settings from[tool.logduo]inpyproject.toml. - If [tool.logduo] settings are not provided, Logduo applies its own configuration defaults.
help()output appears on the console only and is not written to log files.- Use focused calls such as
help(log.configure)orhelp(log.new_logger). help(log)displays the complete logger API and is therefore lengthy.
- Use focused calls such as
log.close()is required to end logging in interactive sessions
Key Logduo default configuration settings
console_theme = "dark"console_wrap_width = 120andlog_wrap_width = "off"console_verbosity = 3andlog_verbosity = 3- verbosity = 3, all logging levels emitted (including DEBUG and TRACE)
- verbosity = 2, only CRITICAL, ERROR, WARNING, INFO, SUCCESS are emitted
- verbosity = 1, only CRITICAL, ERROR, WARNING are emitted
- verbosity = 0, output to that destination is suppressed
console_prefix = "level"andlog_prefix = "timestamp"- Prefix options layer cumulatively:
"off","level","timestamp","source" - Example prefix =
"source": 16:30:40.371 | WARNING | example_2.py:382
- Prefix options layer cumulatively:
log_dir_path = "auto""auto"→- If pyproject.toml is not detected:
log_dir_path= current working directory / "logs" - If pyproject.toml is detected:
log_dir_path= parent directory of pyproject.toml / "logs"
- If pyproject.toml is not detected:
- Other option: provide an explicit absolute log-directory path.
log_file_mode = "write""write"→ Existing log files are overwritten.- Other options:
"append","timestamped"(adds timestamp to log file name before extension).
log_file_name = "auto""auto"→- If Logduo is initialized inside a script,
log_file_name= <script_stem>.log - If Logduo is initialized in an interactive session,
log_file_name= session.log
- If Logduo is initialized inside a script,
- Other option: provide an explicit file name (".log" will be appended if no extension is given)
log_file_layout = "run""run":log_dir_path/script_stem/run_yyyy_mm_dd__hh_mm_ss/log_file_name"script":log_dir_path/script_stem/log_file_name"flat":log_dir_path/log_file_name- If Logduo is initialized in an interactive session, script_stem = "session"
log_file_path = "auto""auto"→ the log file name and location are determined bylog_dir_path,log_file_name, andlog_file_layout.- Other option: provide an explicit log file path (overrides
log_dir_path,log_file_name, andlog_file_layout)
keep = "off"- Previous run directories are not pruned automatically.
- If
keepis set to a positive integern, the newestnLogduo-marked run directories are kept and older run directories are pruned.
write_config_table = Trueconfig_table.txtwritten to output directory (useful reference for all configuration settings).
log.configure(<argument>=None)- Explicit
Noneresets that configuration argument to its built-in Logduo default. - This is useful when a script or package uses Logduo and needs to ensure the default value is used, even if there is a project-level TOML setting.
- Explicit
Quick script with log.configure()
from pathlib import Path
from logduo import log, run
my_log_dir = Path.cwd() / "logs"
log.configure(log_dir_path=my_log_dir, keep=3, console_theme="light")
log("hello world")
log(f"output directory path = {log.output_dir_path}")
log(f"main log file path = {log.main_log_file_path}")
log.export_logduo_docs()
var = 3 * 3
log.debug(f"made it here: var = {var}")
log(
'Demo of logging line options: no_prefix=True, console_style="italic blue", '
'left_indent=26, and log_wrap_width=80. '
'Console wrapping is controlled separately by console_wrap_width '
f'(current session value: {log.session_config.console_wrap_width}).',
no_prefix=True,
console_style="italic blue",
left_indent=26,
log_wrap_width=80,
)
output_dir_path = log.output_dir_path
assert isinstance(output_dir_path, Path) # Satisfy static type checkers.
myplot_output_path = output_dir_path / "myplot.png"
log.close()
-
If
log.configure()is called after logging has started or after a previouslog.configure()call, a warning is issued and the new configuration is ignored. -
If Logduo is initialized inside a script,
log.debug()includes the source (calling file name and line number) at the start of each debug message unless disabled with:`log.configure(show_debug_source=False)`. -
A logging session must be closed, and a new session started, to change Logduo settings.
-
Logging sessions in scripts close automatically during normal interpreter shutdown using best-effort cleanup.
-
While not required, explicit
log.close()is supported in scripts and is useful when subsequent code needs the completed log files immediately.
Export Logduo docs: log.export_logduo_docs()
Exports bundled documentation and example scripts to a local logduo_docs/ directory.
Exported files include examples of advanced workflows:
README.txtexamples/first_script.pyexamples/console_rendering.pyexamples/data_analysis.pyexamples/math_report_notation.pyexamples/script_parent.pyexamples/script_child.py
Logduo Methods, Functions, and Properties
-
Manage session:
log.configure()log.close()
-
Logging levels:
log()orlog.info()log.trace()log.debug()log.success()log.warning()log.error()log.critical()log.exception()# Error message and traceback
-
Create custom logging level:
log.new_level()# Maps a custom display label to an existing severity level; default = "INFO"
-
Create additional output:
log.new_logger()# Logduo-managed extra log filelog.new_loguru_sink()# Advanced Loguru sinklog.export_logduo_docs()
-
Execute a nested script or importable module:
- Inside script_parent.py or interactive session:
run(<path to script_child.py>) - Inside script_child.py:
log = log.join()
- Inside script_parent.py or interactive session:
-
Access output paths.
-
log.output_dir_path
-
log.main_log_file_path
These properties are
Noneuntil Logduo is initialized bylog.configure()or a logging call such aslog("message").Resolving a path does not necessarily create the corresponding directory or file. Logduo creates output directories only when file output is actually needed.
-
Per-call line options
Example: log("hello", no_prefix=True, left_indent=4)
no_prefix=Truesuppresses the prefix for that logging call.left_indent=naddsnspaces before displayed message text. Wrapped continuation lines retain the requested indentation.log_wrap_width=noverrides log-file wrapping for that call. It does not change console wrapping.console_style="..."applies a Rich style to console output for that call. It does not affect plain-text log output.
Message Rendering
-
Simple strings and text-like Python objects:
- Plain-text strings without
\n, and Python objects with a useful text representation (such as dictionaries, lists, numbers, and custom objects), are displayed on the same line as the prefix in both the console and log. - Long text wraps beneath the start of the message.
- Plain-text strings without
-
Multiline strings (strings containing
\n):- Displayed below the prefix in both the console and log, preserving the user-supplied line layout and maximum available line width.
- Long lines may wrap in the console to fit the console width.
- In log files, the user-supplied line breaks are preserved.
-
Rich Text objects
- Displayed flush left below the prefix in the console,
unless
left-indentis applied or Rich's Padding() is used. - Written as plain text in log files.
- Displayed flush left below the prefix in the console,
unless
-
Other Rich objects, such as
Panel, andTable:- Displayed flush left below the prefix in the console, unless Rich's
Padding() is used (not eligible for
left-indent). - Represented by placeholders in log files.
- Displayed flush left below the prefix in the console, unless Rich's
Padding() is used (not eligible for
-
Other objects, such as images and figures:
- Displayed using placeholder or text representations in the console and log.
-
For more examples, use
log.export_logduo_docs()and seeconsole_rendering.py.
Loguru Integration
- Logduo uses Loguru as its underlying file-sink engine.
- Logduo performs message formatting, wrapping, routing, session management, and Rich integration before messages reach Loguru.
- The following Loguru sink options can be passed through
log.configure():rotation: start a new log file when a size/time rule is met. Example:rotation="10 MB"orrotation="1 week". Userotation="off"for no rotation (default ="off").retention: remove older rotated log files when a retention rule is met. Example:retention="14 days"orretention=5. This applies to rotated files, not to Logduo run-directory pruning (default ="off").compression: compress rotated log files. Example:compression="zip". This applies to rotated files, not the active log file (default ="off").enqueue: write logs through a background queue. Useful for thread/process safety (default =True).catch: catch logging errors instead of letting them crash the program (default =True).backtrace: show extended traceback context for exceptions (default =False).diagnose: include extra variable/context information in exception tracebacks (default =False).
- Use
log.new_logger()when you want a normal Logduo-managed extra log file. - Use
log.new_loguru_sink()when you want direct Loguru control, such as:- using custom Loguru filters
- adding extra Loguru sinks
- sending selected events to separate destinations
- passing options directly to
logger.add()
- Sinks added with
log.new_loguru_sink()are advanced pass-through sinks. Logduo manages their creation and session lifecycle, but messages sent directly through Loguru bypass normal Logduo formatting, wrapping, routing, headers, and footers.
Console compatibility
- Logduo supports modern Unicode-capable terminals on Windows, macOS, and Linux.
- Some older or restricted terminals may not display every Rich character correctly.
- Log files are always written as UTF-8.
Release files for logduo 0.2.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 | |
|---|---|---|---|
| logduo-0.2.0.tar.gz | 119.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| logduo-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 266.2 kB
Release files / logduo-0.2.0.tar.gz
| Download URL | logduo-0.2.0.tar.gz |
|---|---|
| Size | 119.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1cf133d8df556918bdfdbc5769a42c30006009e3d8637fc7190af2ef9fc3dc41
|
|
BLAKE2b-256 checksum How to use checksums |
bce96b19ee345058923cbd6040b4ae636cbc9d067dbeab0c49a7bb36aa474c0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / logduo-0.2.0-py3-none-any.whl
| Download URL | logduo-0.2.0-py3-none-any.whl |
|---|---|
| Size | 146.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9662b7fd5068bc74641b1ad385aad420c42a62f381bf5e30a4b460a7eca98c87
|
|
BLAKE2b-256 checksum How to use checksums |
9d90dbdddb7e67fc1a30290d2143fb3a080aa54ed5bef2dd2442e4864647227d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|