Logging Guide¶
XPCS Viewer provides a comprehensive logging system with session correlation, rate limiting, method timing, and path sanitization. This guide covers setup and usage patterns.
Quick Start¶
Get a configured logger in any module:
from xpcsviewer.utils.logging_config import get_logger
logger = get_logger(__name__)
logger.info("Processing started")
The logging system initializes automatically on first use. Configuration is read from environment variables (see Configuration Reference).
Log Output¶
Logs are written to two destinations simultaneously:
- Console (stdout):
Colored output using
ColoredConsoleFormatter. Includes timestamp, level, logger name, and message.- Rotating file (
~/.xpcsviewer/logs/xpcsviewer.log): Uses
RotatingFileHandlerwith configurable maximum size (default 10 MB) and backup count (default 5). File format is either structured text or JSON depending onPYXPCS_LOG_FORMAT.
Session Context (LoggingContext)¶
Use LoggingContext to correlate related log entries with a session ID.
This is valuable for tracing operations across modules in a complex analysis
workflow.
from xpcsviewer.utils.log_utils import LoggingContext
with LoggingContext(operation="batch_analysis") as ctx:
logger.info("Starting batch") # Includes session_id in log record
for file_path in file_list:
ctx.update_file(file_path)
process_file(file_path)
ctx.update_operation("fitting")
run_fits()
The LoggingContext sets three contextvars that are thread-safe:
session_id: Auto-generated UUID4 prefix (8 chars) or custom string
operation: Current high-level operation name (max 100 chars)
current_file: Currently loaded file path (auto-sanitized)
The SessionContextFilter (added automatically to handlers when
PYXPCS_LOG_SESSION_ID=1) enriches each log record with these fields.
Retrieving current context:
from xpcsviewer.utils.log_utils import get_session_context
ctx = get_session_context()
# {'session_id': 'a1b2c3d4', 'operation': 'fitting', 'current_file': '~/data/...'}
Rate-Limited Logging (RateLimitedLogger)¶
For high-frequency events (mouse moves, progress updates, per-pixel
operations), use RateLimitedLogger to prevent log flooding:
from xpcsviewer.utils.log_utils import RateLimitedLogger
rate_limited = RateLimitedLogger(logger, rate_per_second=5.0)
def on_mouse_move(event):
rate_limited.debug(f"Mouse at ({event.x}, {event.y})") # Max 5/sec
The rate limiter uses a token bucket algorithm:
Each unique message template (first 50 characters) gets its own bucket.
Tokens refill at
rate_per_second.Burst size defaults to
rate_per_second(allows short bursts).
Available methods: debug(), info(), warning(), error(),
critical(). Each returns True if the message was logged, False
if suppressed.
Checking suppressed messages:
count = rate_limited.get_suppressed_count() # Total suppressed
count = rate_limited.get_suppressed_count("Mouse") # For specific prefix
rate_limited.reset() # Clear all state
Method Timing (log_timing)¶
Use the @log_timing decorator to measure and log method execution time:
from xpcsviewer.utils.log_utils import log_timing
@log_timing()
def process_data(data):
... # Logs: "process_data completed in 234.56ms" at DEBUG level
@log_timing(threshold_ms=5000)
def long_operation():
... # Logs at WARNING level if execution exceeds 5 seconds
@log_timing(threshold_ms=1000, include_args=True)
def fit_q_bin(q_idx, model_name):
... # Logs: "fit_q_bin(0, 'single_exp') completed in 1234.56ms"
Parameters:
logger: Logger to use (default: module logger of decorated function)
level: Normal log level (default:
DEBUG)threshold_ms: Time threshold for elevated logging (optional)
threshold_level: Level when threshold exceeded (default:
WARNING)include_args: Include function arguments in log message (default:
False)
If the decorated function raises an exception, the timing is still logged
at ERROR level with the traceback.
Path Sanitization¶
Always sanitize file paths before including them in log messages to protect user privacy:
from xpcsviewer.utils.log_utils import sanitize_path
logger.info(f"Loaded: {sanitize_path(file_path)}")
Sanitization modes (configured via PYXPCS_LOG_SANITIZE_PATHS):
Mode |
Input |
Output |
|---|---|---|
|
|
|
|
|
|
|
|
|
The home mode replaces the user’s home directory with ~. The hash
mode additionally replaces the filename with an 8-character SHA-256 hash
while preserving the extension.
Array Logging Guards¶
For expensive debug operations involving array metadata, use the
isEnabledFor guard to avoid computing values when debug logging is
disabled:
import logging
if logger.isEnabledFor(logging.DEBUG):
shape = getattr(array, "shape", "N/A")
dtype = getattr(array, "dtype", "N/A")
has_nan = bool(np.any(np.isnan(array)))
logger.debug(f"Array: shape={shape}, dtype={dtype}, has_nan={has_nan}")
This pattern is used throughout the codebase (backends/io_adapter.py,
backends/_device.py, etc.) to avoid unnecessary computation when logging
at INFO or higher levels.
JSON Structured Logging¶
Set PYXPCS_LOG_FORMAT=JSON to output structured JSON log records. Each
line is a valid JSON object:
{"timestamp": "2026-02-17T10:30:00.123", "level": "INFO",
"logger": "xpcsviewer.fitting.nlsq", "message": "nlsq_optimize completed in 45.67ms",
"session_id": "a1b2c3d4", "operation": "fitting", "app_name": "XPCS Viewer"}
JSON format is suitable for log aggregation tools (ELK stack, Datadog, etc.) in multi-user beamline environments.
Programmatic Configuration¶
The logging system can also be configured programmatically:
from xpcsviewer.utils.logging_config import (
get_logging_config,
set_log_level,
get_log_file_path,
setup_exception_logging,
log_system_info,
)
# Change log level at runtime
set_log_level("DEBUG")
# Get current log file path
log_path = get_log_file_path()
# Enable uncaught exception logging
setup_exception_logging()
# Log system information (Python version, NumPy version, etc.)
log_system_info()
# Get full configuration
config = get_logging_config()
info = config.get_logger_info()
# {'log_level': 'DEBUG', 'log_file': '...', 'format': 'TEXT', ...}