This package provides a stable interface for interactions between Snakemake and its logger plugins.
Plugins should implement the following skeleton to comply with this interface.
It is recommended to use Snakemake’s poetry plugin to set up this skeleton (and automated testing) within a python package, see https://github.com/snakemake/poetry-snakemake-plugin.
Overview
from snakemake_interface_logger_plugins.base import LogHandlerBase
from snakemake_interface_logger_plugins.settings import LogHandlerSettingsBase
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class LogHandlerSettings(LogHandlerSettingsBase):
myparam: Optional[int] = field(
default=None,
metadata={
"help": "Some help text",
"required": True,
},
)
class LogHandler(LogHandlerBase):
def __post_init__(self) -> None:
# Perform additional setup here
# LogHandlerSettings instance:
self.settings
# General settings:
self.common_settings
# Here you can override logging.Handler methods to customize logging behavior.
# Only an implementation of the emit() method is required.
def emit(self, record):
# Emit the record. Typically this will call self.format(record) to
# convert the record to a formatted string. The result could then be written to
# a stream or file.
...
@property
def writes_to_stream(self) -> bool:
# Whether this plugin writes to stderr/stdout.
...
@property
def writes_to_file(self) -> bool:
# Whether this plugin writes to a file.
...
@property
def has_filter(self) -> bool:
# Whether this plugin attaches its own filter.
...
@property
def has_formatter(self) -> bool:
# Whether this plugin attaches its own formatter.
...
@property
def needs_rulegraph(self) -> bool:
# Whether this plugin requires the DAG rulegraph.
...
Instructions
Assume your plugin is named “<plugin>“:
Plugin registration
In order for the plugin to be recognized by the registry, your package must be named
snakemake-logger-plugin-<plugin> with importable root module
snakemake_logger_plugin_<plugin>. The root module must contain a LogHandlerSettings
and LogHandler class (see below).
The logger can be used by passing --logger <plugin> to the snakemake command.
Settings class
Create a subclass of
snakemake_interface_logger_plugins.settings.LogHandlerSettingsBase named
LogHandlerSettings (the @dataclass decorator is required). Its fields correspond to
CLI options that can be used to configure the plugin (a field with name “<field>“
corresponds to --logger-<plugin>-<field>).
All fields must have a default value and type annotation (e.g. str, int, bool,
possibly wrapped in Optional). To additionally customize the behavior of the CLI
option, you can pass a dictionary to the metadata argument of dataclasses.field().
An incomplete list of recognized keys are:
help (str): Help text.
required (bool, default False): Whether the CLI option is required when using
the logger.
env_var (bool): Optionally request that setting is also available for
specification via an environment variable. The variable will be named automatically as
SNAKEMAKE_LOGGER_<plugin>_<field> (all upper case). This mechanism should ONLY be
used for passwords and usernames. For other items, we rather recommend to let people
use a
profile for
setting defaults.
parse_func (function): Optionally specify a function that parses the value given by
the user. This is useful to create complex types from the user input.
unparse_func (function): Function that converts the parsed value back to a string.
Required if parse_func is specified.
nargs (int or "+"): Optionally specify multiple args with "+".
This is a subclass of logging.Handler and requires an implementation of the emit() method.
LogRecords from Snakemake carry contextual information in the record’s attributes Of
particular interest is the event attribute, which indicates the type of log
information contained (see the
LogEvent
enum). For examples on parsing LogRecords, see the
snkmt
plugin.
Do not overwrite the __init__() method as this is kept in control of the base class in
order to simplify the update process. Instead, perform any additional initialization by
overriding __post_init__(). for attributes of the base class. In particular, the
LogHandlerSettings instance is accessible via self.settings. You also have access to
self.common_settings here, which are logging settings supplied by the caller in the
form of
OutputSettingsLoggerInterface.
Additionally, you will need to implement the following properties:
writes_to_stream (bool): Whether this plugin writes to stderr/stdout. This will
cause Snakemake to disable its standard logging to stderr.
writes_to_file (bool): Whether this plugin writes to a file. If it returns True,
your handler class must also have a baseFilename attribute containing the path of
the file written to. This is only used by Snakemake to report your logfile path when
the workflow is completed.
has_filter (bool): Whether this plugin attaches its own filter. Return true if
your plugin provides custom log filtering logic. If false is returned, Snakemake’s
DefaultFilter will
be attached. See Python’s
documentation for
info on how to define and attach a Filter.
has_formatter (bool): Whether this plugin attaches its own formatter. Return true
if your plugin provides custom log formatting logic. If false is returned, Snakemake’s
Defaultformatter
will be attached. See Python’s
documentation for
info on how to define and attach a Formatter.
needs_rulegraph (bool): Whether this plugin requires the DAG rulegraph. Return
true if your plugin needs access to the workflow’s directed acyclic graph for logging
purposes. This will cause Snakemake to event a RULEGRAPH log event.
Plugin setup process
Snakemake uses the following process to set up the plugin when it is activated with the
--logger option:
An instance of the plugin’s settings class is instantiated based on the remaining
CLI arguments.
The handler class is instantiated:
The settings attribute is set to the instance of the plugin’s settings class.
The common_settings attribute is set to an instance of the common settings
class.
The __post_init__() method is called.
If the has_filter property is false, attach a DefaultFilter instance.
If the has_formatter property is false, attach a DefaultFormatter instance.
Install the handler so it can start processing events from the workflow.
Migrating from --log-handler-script
To migrate a log handler script to a logger plugin, follow these steps:
1. Understand the differences
Old approach (--log-handler-script):
Single function that receives message dictionaries
Direct access to message fields like msg['level'], msg['name'], msg['output']
Manual file handling and stderr writing
New approach (Logger Plugin):
Class-based handler inheriting from LogHandlerBase
Integration with Python’s logging framework
Access to structured LogRecord objects with event context
2. Convert your script function to a plugin class
Example old script:
def log_handler(msg):
if msg['level'] == "job_error" and msg['name'] in ['rule1', 'rule2']:
logfile = msg['log'][0]
sys.stderr.write(f"Error in {msg['output'][0]}. See {logfile}\n")
with open(logfile) as f:
for line in f:
sys.stderr.write(f" {line}")
Converted to plugin:
from snakemake_interface_logger_plugins.base import LogHandlerBase
from snakemake_interface_logger_plugins.common import LogEvent
from rich.console import Console
import logging
class LogHandler(LogHandlerBase):
def __post_init__(self) -> None:
super().__post_init__()
self.console = Console()
def emit(self, record):
# Access event type from record
if hasattr(record, 'event') and record.event == LogEvent.JOB_ERROR:
# Access job information from record attributes
if hasattr(record, 'name') and record.name in ['rule1', 'rule2']:
logfile = record.log[0] if hasattr(record, 'log') else None
output = record.output[0] if hasattr(record, 'output') else "unknown"
# Use rich console for pretty printing
self.console.print(f"[red]Error in {output}. See {logfile}[/red]")
if logfile:
try:
with open(logfile) as f:
for line in f:
self.console.print(f" {line.rstrip()}", style="dim")
except FileNotFoundError:
self.console.print(f" Log file {logfile} not found", style="yellow")
@property
def writes_to_stream(self) -> bool:
return True # we're using rich in this plugin to pretty print our logs
@property
def writes_to_file(self) -> bool:
return False # we're not writing to a log file
@property
def has_filter(self) -> bool:
return True # we're doing our own log filtering
@property
def has_formatter(self) -> bool:
return True # we format our own output
@property
def needs_rulegraph(self) -> bool:
return False # we're not using the rulegraph
3. Key migration points
Message access: Replace msg['field'] with record.field or getattr(record, 'field', default)
Event filtering: Replace msg['level'] == "job_error" with record.event == LogEvent.JOB_ERROR
Output method: Replace direct stderr/stdout calls with your plugin’s output handling in the emit() method
Error handling: Add proper exception handling for file operations
Property configuration: Set the abstract properties to inform Snakemake about your handler’s behavior
Available Log Events
The LogEvent enum defines particularly important Snakemake events such as workflow starting, job submission, job failure, etc. Below are the available events and the fields you can typically expect in LogRecord objects for each event type. Note: These field lists are guidelines only and may change between versions. Always use defensive programming practices like getattr() with defaults or hasattr() checks when accessing fields.
Event Types and Typical Available Fields
LogEvent.ERROR
exception: Optional[str] - Exception type
location: Optional[str] - Location where error occurred
rule: Optional[str] - Rule name associated with error
traceback: Optional[str] - Full traceback
file: Optional[str] - File where error occurred
line: Optional[str] - Line number where error occurred
Always use getattr(record, 'field_name', default_value) or check with hasattr(record, 'field_name') before accessing fields, as not all fields may be present in every record.
Snakemake Logger Plugin Interface
This package provides a stable interface for interactions between Snakemake and its logger plugins.
Plugins should implement the following skeleton to comply with this interface. It is recommended to use Snakemake’s poetry plugin to set up this skeleton (and automated testing) within a python package, see https://github.com/snakemake/poetry-snakemake-plugin.
Overview
Instructions
Assume your plugin is named “<plugin>“:
Plugin registration
In order for the plugin to be recognized by the registry, your package must be named
snakemake-logger-plugin-<plugin>with importable root modulesnakemake_logger_plugin_<plugin>. The root module must contain aLogHandlerSettingsandLogHandlerclass (see below).The logger can be used by passing
--logger <plugin>to thesnakemakecommand.Settings class
Create a subclass of
snakemake_interface_logger_plugins.settings.LogHandlerSettingsBasenamedLogHandlerSettings(the@dataclassdecorator is required). Its fields correspond to CLI options that can be used to configure the plugin (a field with name “<field>“ corresponds to--logger-<plugin>-<field>).All fields must have a default value and type annotation (e.g.
str,int,bool, possibly wrapped inOptional). To additionally customize the behavior of the CLI option, you can pass a dictionary to themetadataargument ofdataclasses.field(). An incomplete list of recognized keys are:help(str): Help text.required(bool, defaultFalse): Whether the CLI option is required when using the logger.env_var(bool): Optionally request that setting is also available for specification via an environment variable. The variable will be named automatically asSNAKEMAKE_LOGGER_<plugin>_<field>(all upper case). This mechanism should ONLY be used for passwords and usernames. For other items, we rather recommend to let people use a profile for setting defaults.parse_func(function): Optionally specify a function that parses the value given by the user. This is useful to create complex types from the user input.unparse_func(function): Function that converts the parsed value back to a string. Required ifparse_funcis specified.nargs(intor"+"): Optionally specify multiple args with"+".Handler class
Create a subclass of
snakemake_interface_logger_plugins.base.LogHandlerBasenamedLogHandler.This is a subclass of
logging.Handlerand requires an implementation of theemit()method.LogRecords from Snakemake carry contextual information in the record’s attributes Of particular interest is theeventattribute, which indicates the type of log information contained (see the LogEvent enum). For examples on parsing LogRecords, see the snkmt plugin.Do not overwrite the
__init__()method as this is kept in control of the base class in order to simplify the update process. Instead, perform any additional initialization by overriding__post_init__(). for attributes of the base class. In particular, theLogHandlerSettingsinstance is accessible viaself.settings. You also have access toself.common_settingshere, which are logging settings supplied by the caller in the form ofOutputSettingsLoggerInterface.Additionally, you will need to implement the following properties:
writes_to_stream(bool): Whether this plugin writes to stderr/stdout. This will cause Snakemake to disable its standard logging to stderr.writes_to_file(bool): Whether this plugin writes to a file. If it returnsTrue, your handler class must also have abaseFilenameattribute containing the path of the file written to. This is only used by Snakemake to report your logfile path when the workflow is completed.has_filter(bool): Whether this plugin attaches its own filter. Return true if your plugin provides custom log filtering logic. If false is returned, Snakemake’s DefaultFilter will be attached. See Python’s documentation for info on how to define and attach aFilter.has_formatter(bool): Whether this plugin attaches its own formatter. Return true if your plugin provides custom log formatting logic. If false is returned, Snakemake’s Defaultformatter will be attached. See Python’s documentation for info on how to define and attach aFormatter.needs_rulegraph(bool): Whether this plugin requires the DAG rulegraph. Return true if your plugin needs access to the workflow’s directed acyclic graph for logging purposes. This will cause Snakemake to event aRULEGRAPHlog event.Plugin setup process
Snakemake uses the following process to set up the plugin when it is activated with the
--loggeroption:settingsattribute is set to the instance of the plugin’s settings class.common_settingsattribute is set to an instance of the common settings class.__post_init__()method is called.has_filterproperty is false, attach aDefaultFilterinstance.has_formatterproperty is false, attach aDefaultFormatterinstance.Migrating from
--log-handler-scriptTo migrate a log handler script to a logger plugin, follow these steps:
1. Understand the differences
Old approach (
--log-handler-script):msg['level'],msg['name'],msg['output']New approach (Logger Plugin):
LogHandlerBaseLogRecordobjects with event context2. Convert your script function to a plugin class
Example old script:
Converted to plugin:
3. Key migration points
Message access: Replace
msg['field']withrecord.fieldorgetattr(record, 'field', default)Event filtering: Replace
msg['level'] == "job_error"withrecord.event == LogEvent.JOB_ERROROutput method: Replace direct stderr/stdout calls with your plugin’s output handling in the
emit()methodError handling: Add proper exception handling for file operations
Property configuration: Set the abstract properties to inform Snakemake about your handler’s behavior
Available Log Events
The
LogEventenum defines particularly important Snakemake events such as workflow starting, job submission, job failure, etc. Below are the available events and the fields you can typically expect inLogRecordobjects for each event type. Note: These field lists are guidelines only and may change between versions. Always use defensive programming practices likegetattr()with defaults orhasattr()checks when accessing fields.Event Types and Typical Available Fields
LogEvent.ERRORexception: Optional[str]- Exception typelocation: Optional[str]- Location where error occurredrule: Optional[str]- Rule name associated with errortraceback: Optional[str]- Full tracebackfile: Optional[str]- File where error occurredline: Optional[str]- Line number where error occurredLogEvent.WORKFLOW_STARTEDworkflow_id: uuid.UUID- Unique workflow identifiersnakefile: Optional[str]- Path to the SnakefileLogEvent.JOB_INFOjobid: int- Job identifierrule_name: str- Name of the rulethreads: int- Number of threads allocatedinput: Optional[List[str]]- Input filesoutput: Optional[List[str]]- Output fileslog: Optional[List[str]]- Log filesbenchmark: Optional[List[str]]- Benchmark filesrule_msg: Optional[str]- Rule messagewildcards: Optional[Dict[str, Any]]- Wildcard valuesreason: Optional[str]- Reason for job executionshellcmd: Optional[str]- Shell command to executepriority: Optional[int]- Job priorityresources: Optional[Dict[str, Any]]- Resource requirementsLogEvent.JOB_STARTEDjob_ids: List[int]- List of job IDs that startedLogEvent.JOB_FINISHEDjob_id: int- ID of the finished jobLogEvent.SHELLCMDjobid: int- Job identifiershellcmd: Optional[str]- Shell command being executedrule_name: Optional[str]- Name of the ruleLogEvent.JOB_ERRORjobid: int- ID of the job that failedLogEvent.GROUP_INFOgroup_id: int- Group identifierjobs: List[Any]- Jobs in the groupLogEvent.GROUP_ERRORgroupid: int- Group identifieraux_logs: List[Any]- Auxiliary log informationjob_error_info: Dict[str, Any]- Job error detailsLogEvent.RESOURCES_INFOnodes: Optional[List[str]]- Available nodescores: Optional[int]- Available coresprovided_resources: Optional[Dict[str, Any]]- Provided resourcesLogEvent.DEBUG_DAGstatus: Optional[str]- DAG statusjob: Optional[Any]- Job informationfile: Optional[str]- Related fileexception: Optional[str]- Exception informationLogEvent.PROGRESSdone: int- Number of completed jobstotal: int- Total number of jobsLogEvent.RULEGRAPHrulegraph: Dict[str, Any]- Rule graph data structureLogEvent.RUN_INFOper_rule_job_counts: Dict[str, int]- Job count per ruletotal_job_count: int- Total number of jobsAccessing Event Fields
You can filter for specific events and access their fields in your
emit()method:Always use
getattr(record, 'field_name', default_value)or check withhasattr(record, 'field_name')before accessing fields, as not all fields may be present in every record.