Logging Processes in the Toolbox
Where are the logs
- Per-run logs — pre-processing and the optimizers write their own log files to
derivatives/ti-toolbox/logs/sub-<id>/in your project. - Job consoles — each job’s full console is
code/ti-toolbox/jobs/<id>/stdout.log; the job’s Raw log tab shows all of it. The folder button beside a console opens the job’s results folder (Open output folder), or the job’s own folder when it has no results folder (Open job folder). Finished job records older than 30 days, or beyond the newest 200, are deleted when the server starts (see Jobs). - The desktop app’s own log —
main.log: on macOS~/Library/Logs/TI-Toolbox/main.log, on Windows%APPDATA%\ti-toolbox-desktop\logs\main.log, on Linux${XDG_CONFIG_HOME:-~/.config}/ti-toolbox-desktop/logs/main.log.
The logging API
The TI-Toolbox logging system (tit/logger.py) is intentionally minimal. On import, tit/__init__.py auto-configures the tit logger hierarchy with a stdout handler at INFO level — no explicit setup is needed. File logging is opt-in via add_file_handler().
Architecture
The main scripting helpers are:
| Function | Purpose |
|---|---|
setup_logging(level) |
Set log level; attaches a JSON event handler when the server supplies TIT_EVENTS_FILE (called automatically on import) |
add_stream_handler(logger_name, level) |
Attach a stdout StreamHandler (called on import for tit at INFO) |
add_file_handler(log_file) |
Attach a FileHandler to a named logger |
get_file_only_logger(name, log_file) |
Return a standalone logger that writes only to a file |
setup_logging(level)
Called automatically by tit/__init__.py on import. You only need to call it explicitly if you want to change the log level:
from tit.logger import setup_logging
setup_logging("DEBUG") # override the default INFO level
This does three things:
- Clears any existing handlers on the
titlogger - Sets the log level (defaults to
INFO) - Sets
propagate = Falseso messages never bubble to the root logger
Third-party loggers (matplotlib, PIL) are silenced to ERROR level. When TIT_EVENTS_FILE is set by the job runner, setup also attaches a JSON event handler to tit and simnibs; log records share the job’s ordered event stream with stage, progress, artifact and exit events.
add_file_handler(log_file, level, logger_name)
from tit.logger import add_file_handler
fh = add_file_handler("/path/to/run.log", level="DEBUG", logger_name="tit")
- Creates the parent directory if needed
- Opens the file in append mode
- Returns the
FileHandlerso callers can remove it when finished
get_file_only_logger(name, log_file)
from tit.logger import get_file_only_logger
logger = get_file_only_logger("tit.analyzer.roi", "/path/to/roi.log")
Returns a logger with propagate = False that writes exclusively to the given file. Useful for per-ROI or per-subject log isolation.
Log Format
All file handlers use the same format:
2025-06-05 23:10:26 | INFO | tit.analyzer | Mesh analyzer initialized successfully
Fields: timestamp, level, logger name, message.
Log Levels
- DEBUG: Detailed diagnostic information (file handlers default to this)
- INFO: General progress information
- WARNING: Potentially problematic situations
- ERROR: Serious problems
- CRITICAL: Fatal errors
How Each Entry Point Uses Logging
Desktop application
The desktop app streams job logs over the HTTP API (tit.server) rather than through an in-process logging handler; each job’s output is written to its log file and relayed to the run page’s terminal.
__main__.py Subprocess Entry Points
Modules invoked as subprocesses (simnibs_python -m tit.analyzer config.json) write logs and progress from inside the container. The job runner captures stdout and stderr to the job log; structured events and logging records are streamed to the UI through the server.
Library Usage
When using tit as a library, logging is auto-configured on import — no setup call needed. To add file logging, attach a handler:
from tit import add_file_handler
fh = add_file_handler("my_analysis.log")
# ... run analysis ...
# Clean up when done
import logging
logging.getLogger("tit").removeHandler(fh)
Best Practices
- Logging auto-initializes on import — no explicit
setup_logging()call needed - Use
add_file_handler()to direct logs to a file when you need a record - Use
print()in__main__.pymodules where output must reach subprocess capture - Use
logging.getLogger("tit.your_module")in library modules – the hierarchy propagates to whatever handlers are attached to thetitlogger - Clean up handlers when a run completes to avoid leaking file descriptors