ictr¶
🖋️ Ictr is a system for logging and debug printing in Python applications. It provides a clean, type-safe API for emitting diagnostic messages with minimal boilerplate, featuring standard message flavors, hierarchical trace levels for debugging depth, automatic exception tracebacks, and optional rich rendering with colors and styling.
Designed as an alternative to traditional print debugging and verbose logging
setup, ictr can install a global dispatcher into the Python builtins,
similar to the well-known icecream
package, for effortless access throughout your application — no import
statements needed in every module. Perfect for both quick debugging sessions
and production logging with per-module configuration.
Key Features ⭐¶
🎨 Standard Message Flavors: Pre-configured note, monition,
error, abort, future, success, and advice flavors with
semantic labels and optional emoji/color styling.
🔢 Hierarchical Trace Levels: Ten trace levels (0-9) with automatic indentation for visualizing call depth and execution flow.
💥 Automatic Exception Tracebacks: errorx and abortx flavors
capture and format active exceptions with full stack traces.
🌳 Module Hierarchy: Global and per-module configs with inheritance for precise control over active flavors, trace levels, output formatting, etc….
🚀 Zero-Import Access: Global dispatcher available in builtins after initial setup — no import statements needed in every module.
🖨️ Printer Factory: Dynamically associate output functions with reporters
based on module name, flavor, etc…. Swap in customized print,
logging, or other sinks as desired.
📚 Library-Friendly: Non-intrusive registration for libraries without stepping on application debugger/logging configuration.
Installation 📦¶
Method: Install Python Package¶
Install via uv pip
command:
uv pip install ictr
Or, install via pip:
pip install ictr
Examples 💡¶
For more detailed examples, please see the examples documentation.
Basic Usage¶
Install an ictr dispatcher as a Python builtin (default alias, ictr)
and then use it anywhere in your codebase:
import ictr
ictr.install( )
# Emit messages with different flavors
ictr( 'note' )( 'Application started.' ) # NOTE| Application started.
ictr( 'error' )( 'Connection failed.' ) # ERROR| Connection failed.
ictr( 'success' )( 'Task completed.' ) # SUCCESS| Task completed.
Trace Levels¶
Use numeric trace levels (0-9) for hierarchical debugging output:
import ictr
ictr.install( trace_levels = { None: 3 } ) # Enable levels 0-3 globally
ictr( 0 )( 'Top-level operation' ) # TRACE0| Top-level operation
ictr( 1 )( 'Sub-operation details' ) # TRACE1| Sub-operation details
ictr( 2 )( 'More detail' ) # TRACE2| More detail
ictr( 4 )( 'Too verbose' ) # (suppressed - level > 3)
Exception Handling¶
Use errorx or abortx flavors to capture active exceptions with
tracebacks:
import ictr
ictr.install( )
try:
result = 1 / 0
except ZeroDivisionError:
ictr( 'errorx' )( 'Calculation failed.' )
# ERROR| Calculation failed.
#
# [ZeroDivisionError] division by zero
# File 'example.py', line 8, in <module>
# result = 1 / 0
Library Integration¶
Libraries can register their own configurations without overriding those of the application:
# mylibrary/__init__.py
import ictr
ictr.register_address( 'mylibrary' )
When install is called by the application, any addresses that were
previously registered via register_address are incorporated into the
installed dispatcher. This allows applications to setup output after libraries
have registered their configurations.
Motivation 🚚¶
Why ictr?
There is nothing wrong with the icecream or logging packages. However,
there are times that the author of ictr (and its predecessor,
icecream-truck) has wanted, for various reasons, more than these packages
inherently offer:
Coexistence: Application and libraries can coexist without configuration clashes.
Library developers are strongly advised not to create custom levels in
logging.Library developers are advised on how to avoid polluting stderr in
logging, when an application has not supplied a configuration.Loggers propagate upwards by default in
logging. This means that libraries must explicitly opt-out of propagation if their authors want to be good citizens and not contribute to noise pollution / signal obfuscation.
Granularity: Control of debug output by depth threshold and subsystem.
Only one default debugging level (
DEBUG) withlogging. Libraries cannot safely extend this. (See point about coexistence).No concept of debugging level with
icbuiltin. Need to orchestrate multipleicecream.IceCreamDebuggerinstances to support this. (In fact, this is whaticecream-truckdoes.)While logger hierarchies in
loggingdo support the notion of software subsystems, hierarchies are not always the most convenient or abbreviated way of representing subsystems which span parts or entireties of modules.
Signal: Prevention of undesirable library chatter.
The
loggingroot logger will log all messages, at its current log level or higher, which propagate up to it. Many Python libraries have opt-out rather than opt-in logging, so you see all of theirDEBUGandINFOspam unless you surgically manipulate their loggers or squelch the overall log level.Use of the
icbuiltin is only recommended for temporary debugging. It cannot be left in production code without spamming. While theenabledflag on theicbuiltin can be set to false, it is easy to forget and also applies to every place whereicis used in the code. (See point about granularity.)
Extensibility: More natural integration with packages like
richvia robust recipes.While it is not difficult to change the
argToStringFunctiononicto berich.pretty.pretty_repr, there is some repetitive code involved in each project which wants to do this. And, from a safety perspective, there should be a fallback ifrichfails to import.Similarly, one can add a
rich.logging.RichHandlerinstance to a logger instance with minimal effort. However, depending on the the target output stream, one may also need to build arich.console.Consolefirst and pass that to the handler. This handler will also compete with whatever handler has been set on the root logger. So, some care must be taken to prevent propagation. Again, this is repetitive code across projects and there are import safety fallbacks to consider.
About the Name 📝¶
The name ictr has multiple origins and interpretations:
🍦 Shortened from icecream-truck: The package from which
ictris derived and redesigned. The abbreviation maintains the connection to its predecessor while establishing its own identity.🎯 Short and memorable: Four letters that are easy to type and remember. The Python package distribution name and import name are the same, reducing cognitive overhead.
📊 Backronym interpretations: While
ictrworks perfectly well as just a name, several backronyms capture different aspects of its purpose:Inspection-Capable Trace Reporting (emphasizes diagnostic capabilities)
Intelligent Configurable Trace Reporter (emphasizes smart behavior)
I See Textual Reports (playful take on the phonetic sound)
Pronunciation? You can spell it out. But, if that is too many syllables, then maybe “eyes-tra” but probably not “ick-ter”, because it is not that revulsive.
Contribution 🤝¶
Contribution to this project is welcome! However, it must follow the code of conduct for the project.
Please file bug reports and feature requests in the issue tracker or submit pull requests to improve the source code or documentation.
For development guidance and standards, please see the development guide.