Optional alternate logging method for LR monitor callback
🚀 Feature
Allow user to pass any callable to be used in addition to trainer.logger.log_metrics()
Motivation
The LR monitor callback handles many best practices, but it doesn't offer integration with a plain Python logger. As mentioned on this discussions thread, I prefer to use a plain Python logger to output progress messages during training. I demonstrated how model callbacks can be used for batch loss/accuracy, but in order to log the learning rate the same way I currently need a custom callback.
Rather than duplicate much of the official LR monitor code, it would be better to support an optional second "data destination" that the official callback can send statistics to. This is easier to understand with the example code below
Pitch
Make the following additions to pl.callbacks.lr_monitor
class LearningRateMonitor(Callback):
    def __init__(
        self,
        logging_interval: Optional[str] = None,
        log_momentum: bool = False,
        alt_logging_method: Optional[str] = None
    ):
        if logging_interval not in (None, 'step', 'epoch'):
            raise MisconfigurationException('logging_interval should be `step` or `epoch` or `None`.')
        if not callable(alt_logging_method):
            raise MisconfigurationException('alt_logging_method must be callable')

        self.logging_interval = logging_interval
        self.log_momentum = log_momentum
        self.alt_logging_method = alt_logging_method  # The key addition
        self.lrs = None
        self.lr_sch_names = []

    def on_train_batch_start(self, trainer, *args, **kwargs):
            ...
            if latest_stat:
                trainer.logger.log_metrics(latest_stat, step=trainer.global_step)
                if self.alt_logging_method is not None:
                    self.alt_logging_method(latest_stat)  # Use a second data destination if provided

    def on_train_epoch_start(self, trainer, *args, **kwargs):
        ...
        if latest_stat:
            trainer.logger.log_metrics(latest_stat, step=trainer.global_step)
            if self.alt_logging_method is not None:
                self.alt_logging_method(latest_stat)  # Use a second data destination if provided
Then our model can opt to do the following
import logging
import pytorch_lightning as pl

logger = logging.getLogger(__name__)
# Other configuration of logger handlers/formatters here


class MyModel(pl.LightningModule):
    
    def __init__(self):
        super(MyModel, self).__init__()
    
    def configure_callbacks(self):
        return [
            pl.callbacks.LearningRateMonitor(alt_logging_method=logger.info)  # Could use other logging level
        ]
Or we could wrap the destination method in another callable to do any arbitrary task (like apply formatting vs. printing the raw dict). With this modification to the existing callback, the user can accomplish whatever they want with the extracted LR statistics. It's essentially a callback for the callback
def log_formatted_lrs(latest_stat):
    stat_str = ' | '.join([f'{"-".join(k.split("-")[1:])} {v:.0e}' for k, v in latest_stat.items()])
    return logger.info(f'Optimizer LRs: {stat_str}')
Alternatives
Rather than passing logger to the LR monitor callback, we could create a custom Lightning logger that uses plain Python logging and then trainer.logger.log_metrics() would automatically connect to it. However as mentioned in the discussion thread linked above and this issue, it is rather difficult to get a plain Python logger to provide an interface for accepting dict metrics since it was designed for text messages only. That difficulty is why I ended up creating a module-level logger instance and using it in model callbacks