Logging System
Relevant source files
The following files were used as context for generating this wiki page:The logging system in SOFL provides a robust mechanism for capturing application events, debugging information, and system details. It is designed to output logs to both the console with color-coded severity levels and to persistent log files that are rotated and compressed with each application session. This system is crucial for development, debugging user-reported issues, and providing diagnostic information through the "About" dialog.
The logging is initialized at application startup within the SOFLApplication.do_activate method, which calls setup_logging() and log_system_info().
Sources: sofl/logging/setup.py, sofl/main.py:133-138
Architecture Overview
The logging system is configured using Python's standard logging.config.dictConfig. It defines a set of formatters, handlers, and loggers to control the flow and appearance of log messages.
The following diagram illustrates the relationship between the main components of the logging system.
This diagram shows how log messages from different loggers are passed to various handlers, which then use specific formatters to produce the final output in the console or a file.
Sources: sofl/logging/setup.py:34-87
Initialization and Configuration
The entire logging system is configured by the setup_logging() function. This function is called once when the application starts.
Sources: sofl/main.py:134, sofl/logging/setup.py:27-87
setup_logging() Flow
The initialization process determines log levels, defines handlers and formatters, and applies the configuration.
This flow shows how logging levels are determined based on the application profile (development or release) and can be overridden by environment variables.
Sources: sofl/logging/setup.py:30-33
Configuration Details
The configuration is defined in a dictionary within setup_logging. Key elements are summarized in the table below.
| Component | Name | Class / Format | Description |
|---|---|---|---|
| Formatter | file_formatter | %(asctime)s - %(levelname)s: %(message)s | Formats logs for file output, including a timestamp. |
| Formatter | console_formatter | sofl.logging.color_log_formatter.ColorLogFormatter | Formats logs for console output with color. |
| Handler | file_handler | sofl.logging.session_file_handler.SessionFileHandler | Writes DEBUG level logs to sofl.log with rotation. |
| Handler | app_console_handler | logging.StreamHandler | Writes application logs to the console. Level depends on profile/env. |
| Handler | lib_console_handler | logging.StreamHandler | Writes library (e.g., PIL, urllib3) logs to the console. Level depends on profile/env. |
| Logger | PIL, urllib3 | N/A | Specific loggers for libraries to control their verbosity independently. |
| Logger | root | N/A | The root logger that captures all application-level logs. |
Sources: sofl/logging/setup.py:36-86
Log Level Control
Log verbosity can be controlled via environment variables, which override the defaults set by the application profile.
| Variable | Default (Dev) | Default (Release) | Purpose |
|---|---|---|---|
LOGLEVEL | DEBUG | INFO | Controls the log level for the main application console handler. |
LIBLOGLEVEL | INFO | WARNING | Controls the log level for third-party library console handlers. |
Sources: sofl/logging/setup.py:30-33
Log Handlers
SessionFileHandler
This custom handler manages log files for each application session. It ensures that logs from previous sessions are preserved but limited in number to save space.
Sources: sofl/logging/session_file_handler.py
Features
- Session-based Logging: Creates a new
sofl.logfile on every application start. - Log Rotation: On startup, it renames and compresses existing log files.
- Compression: Old log files are compressed into
.xzformat. - Backup Limit: Keeps a configurable number of old log files (
backup_count, defaults to 2).
The class diagram for SessionFileHandler is shown below.
The rotation process is handled by the rotate() and rotate_file() methods. When rotate() is called during initialization, it iterates through existing log files and processes each one.
# sofl/logging/session_file_handler.py:105-108
def rotate(self) -> None:
"""Rotate the numbered suffix on the log files and remove old ones"""
for path in self.get_logfiles():
self.rotate_file(path)
The rotate_file method compresses the log if it's not already compressed, increments its numbered suffix, and deletes it if it exceeds the backup_count.
Sources: sofl/logging/session_file_handler.py:83-103
Log Formatters
ColorLogFormatter
This formatter enhances console output by adding ANSI color codes based on the log message's severity level. This makes it easier to spot warnings and errors during development.
The format method applies a color based on the record.levelno.
| Level | Color |
|---|---|
DEBUG | Grey |
INFO | Default |
WARNING | Yellow |
ERROR | Red |
CRITICAL | Bold Red |
Sources: sofl/logging/color_log_formatter.py:28-40
# sofl/logging/color_log_formatter.py:28-44
class ColorLogFormatter(logging.Formatter):
"""A log formatter that adds color to the output"""
LOG_COLORS = {
logging.DEBUG: "\x1b[38;20m", # Grey
logging.INFO: "\x1b[39;20m", # Default
logging.WARNING: "\x1b[33;20m", # Yellow
logging.ERROR: "\x1b[31;20m", # Red
logging.CRITICAL: "\x1b[31;1m", # Bold Red
}
RESET_COLOR = "\x1b[0m"
def format(self, record: logging.LogRecord) -> str:
log_color = self.LOG_COLORS.get(record.levelno)
log_msg = super().format(record)
return f"{log_color}{log_msg}{self.RESET_COLOR}" if log_color else log_msg
System Information Logging
At startup, the log_system_info() function is called to record key details about the execution environment. This information is invaluable for debugging platform-specific issues.
The following information is logged at the DEBUG level:
- Application ID, Version, and Profile (
developmentorrelease) - Python version
- Flatpak version (if running in a Flatpak environment)
- Platform details from
platform.platform() unameresults on POSIX systems (OS, node, release, version, machine)
*Sources: [sofl/logging/setup.py:90-111](https://github.com/Qeatrix/steam-online-fix-launcher/blob/HEAD/38%3B20m%22%2C%20%20# Grey
logging.INFO: "\x1b[39;20m", # Default
logging.WARNING: "\x1b[33;20m", # Yellow
logging.ERROR: "\x1b[31;20m", # Red
logging.CRITICAL: "\x1b[31;1m", # Bold Red
}
RESET_COLOR = "\x1b[0m"
def format(self, record: logging.LogRecord) -> str:
log_color = self.LOG_COLORS.get(record.levelno)
log_msg = super().format(record)
return f"{log_color}{log_msg}{self.RESET_COLOR}" if log_color else log_msg
## System Information Logging
At startup, the `log_system_info()` function is called to record key details about the execution environment. This information is invaluable for debugging platform-specific issues.
The following information is logged at the `DEBUG` level:
- Application ID, Version, and Profile (`development` or `release`)
- Python version
- Flatpak version (if running in a Flatpak environment)
- Platform details from `platform.platform()`
- `uname` results on POSIX systems (OS, node, release, version, machine)
*Sources: [sofl/logging/setup.py:90-111), [sofl/main.py:136](https://github.com/Qeatrix/steam-online-fix-launcher/blob/HEAD/sofl/main.py#L136)*
## Usage in the Application
### Startup Sequence
The logging system is one of the first components to be initialized when the application starts.
This sequence shows that logging is set up before any major application logic, like loading games, occurs.
Sources: sofl/main.py:129-146
Debug Information in "About" Dialog
The log files generated by SessionFileHandler are used to populate the debug information field in the application's "About" dialog. The on_about_action method reads all current and rotated log files (including compressed .xz files) and concatenates their content.
Sources: sofl/main.py:291-303
General Logging
Throughout the application, standard Python logging is used to record events. For example, in DetailsDialog, a logger is initialized and used to report the creation of new game entries.
# sofl/details_dialog.py:53-54
# Logger setup
logger = logging.getLogger(__name__)
# sofl/details_dialog.py:220-221
if game_type_selected == 0:
source_id = "online-fix"
self.logger.info("Creating Online-Fix game")
else: # Regular Game
source_id = "imported"
self.logger.info("Creating regular imported game")
Sources: sofl/details_dialog.py:54, sofl/details_dialog.py:222-227