Logging Processes in the Toolbox

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:

  1. Clears any existing handlers on the tit logger
  2. Sets the log level (defaults to INFO)
  3. Sets propagate = False so 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 FileHandler so 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. The removed v2 BaseProcessThread and Qt console are no longer involved.

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)

What Was Removed

Previous versions (~v2.2.3 and earlier) had ~750 lines of custom logging infrastructure including:

  • get_logger() factory function (deleted)
  • configure_external_loggers() for SimNIBS integration
  • File output by default (files are now opt-in via add_file_handler)
  • TI_LOG_FILE, PROJECT_DIR, SUBJECT_ID environment variables
  • Debug toggles in the GUI

The old GUI logging infrastructure is removed. Current server jobs use TIT_EVENTS_FILE for their structured event sink; ordinary scripts use the helper functions above.

Best Practices

  1. Logging auto-initializes on import — no explicit setup_logging() call needed
  2. Use add_file_handler() to direct logs to a file when you need a record
  3. Use print() in __main__.py modules where output must reach subprocess capture
  4. Use logging.getLogger("tit.your_module") in library modules – the hierarchy propagates to whatever handlers are attached to the tit logger
  5. Clean up handlers when a run completes to avoid leaking file descriptors