Commit 84ee02f1 authored by Ibrahim Ahmed's avatar Ibrahim Ahmed
Browse files

controller: Refactored make_logger() to utils

parent 1e7a123a
Loading
Loading
Loading
Loading
+2 −65
Changes for src/controller.py: 2 added lines, 65 removed lines.
Original line number Diff line number Diff line
@@ -11,7 +11,6 @@ import threading as th
import logging
import csv
from datetime import datetime, timedelta
from urllib.parse import urlparse

# Issue on Windows where python does not catch keyboard interrupt b/c
# scipy/sklearn (using intel MLK installed via anaconda) imports do their own
@@ -25,7 +24,7 @@ import pytz
from sklearn.base import BaseEstimator
import bdx

from utils.logging import EmailHandler, RemoteHandler
from utils.logging import EmailHandler, RemoteHandler, get_logger, make_logger


SOURCECODE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -59,7 +58,7 @@ def make_arguments() -> ArgumentParser:
                        help='Location of settings file.', default=DEFAULTS['settings'])
    parser.add_argument('-l', '--logs', type=str, required=False, default=None,
                        help='Location of file to write logs to.')
    parser.add_argument('-r', '--logs-server-destination', type=str, required=False, default=None,
    parser.add_argument('-r', '--logs-server', type=str, required=False, default=None,
                        help='http://host[:port][/path] of remote server to POST logs to.')
    parser.add_argument('-v', '--verbosity', type=str, required=False, default=None,
                        help='Verbosity level.',
@@ -115,68 +114,6 @@ def get_settings(parsed_args) -> dict:



def get_logger():
    return logging.getLogger(__name__)



def make_logger(**settings) -> logging.Logger:
    logging.captureWarnings(True)
    logger = get_logger()
    logger.setLevel(settings['verbosity'])
    formatter = logging.Formatter('%(asctime)s, %(levelname)s, %(message)s',
                                  datefmt='%Y-%m-%d %H:%M:%S')

    # File logging
    handler_file = logging.FileHandler(filename=settings['logs'], mode='a')
    handler_file.setFormatter(formatter)
    handler_file.setLevel(settings['logs_file_verbosity'])
    logger.addHandler(handler_file)

    # Stream logging
    handler_stream = logging.StreamHandler(stream=sys.stderr)
    handler_stream.setFormatter(formatter)
    handler_stream.setLevel(settings['logs_stream_verbosity'])
    logger.addHandler(handler_stream)

    # HTTP Logging
    remote_log = settings.get('logs_server_destination')
    remote_verbosity = settings.get('logs_server_verbosity')
    if remote_log not in ('', None) and remote_verbosity not in ('', None):
        parsed = urlparse(remote_log)
        handler_remote = RemoteHandler(host=parsed.netloc, url=parsed.path, method='POST')
        handler_remote.setLevel(remote_verbosity)
        logger.addHandler(handler_remote)

    # Email logging
    mailhost = settings.get('logs_email_smtp_server')
    fromaddr = settings.get('logs_email_from')
    toaddrs = settings.get('logs_email_to', '')
    username = settings.get('logs_email_username')
    password = settings.get('logs_email_password')
    email_verbosity = settings.get('logs_email_verbosity')
    name = settings.get('controller', '') + settings.get('application', '')

    if (email_verbosity not in ('', None) and \
        mailhost not in ('', None) and \
        fromaddr not in ('', None) and \
        len(toaddrs) > 0 and toaddrs[0]!='' and \
        username not in ('', None) and password not in ('', None)):
        
        logger.info('Setting up email logging.')   
        handler_email = EmailHandler(name, mailhost, fromaddr, toaddrs,
            username, password, capacity=settings['logs_email_batchsize'],
            logger=logger)
        handler_email.setLevel(email_verbosity)
        handler_email.setFormatter(formatter)
        logger.addHandler(handler_email)
    else:
        logger.info('Skipping email logging.')

    return logger



def get_controller(**settings) -> BaseEstimator:
    from baseline_control import SimpleFeedbackController
    class Controller(SimpleFeedbackController):
+0 −0

File moved.

+152 −6
Changes for src/utils/logging.py: 152 added lines, 6 removed lines.
Original line number Diff line number Diff line
"""
Various classes to be used by `logging.Logger` for sending messages:

* Formatters convert a message `logging.LogRecord` object into a string for output
* Handlers direct a message to various outputs (stream, HTTP, file)
"""
import sys
import logging
from logging.handlers import HTTPHandler, BufferingHandler, HTTPHandler
from logging.handlers import HTTPHandler, BufferingHandler
import smtplib
import email
import time
from typing import Dict
from urllib.parse import urlparse



def get_logger(name: str=None) -> logging.Logger:
    return logging.getLogger(__name__ if name is None else name)



def make_logger(enable='all', logger=None, **settings) -> logging.Logger:
    logging.captureWarnings(True)
    logger = get_logger() if logger is None else logger
    logger.setLevel(settings['verbosity'])
    formatter = logging.Formatter('%(asctime)s, %(levelname)s, %(message)s',
                                  datefmt='%Y-%m-%d %H:%M:%S')

    # File logging
    if enable=='all' or 'file' in enable:
        handler_file = logging.FileHandler(filename=settings['logs'], mode='a')
        handler_file.setFormatter(formatter)
        handler_file.setLevel(settings['logs_file_verbosity'])
        logger.addHandler(handler_file)

    # Stream logging
    if enable=='all' or 'stream' in enable:
        handler_stream = logging.StreamHandler(stream=sys.stderr)
        handler_stream.setFormatter(formatter)
        handler_stream.setLevel(settings['logs_stream_verbosity'])
        logger.addHandler(handler_stream)

    # HTTP Logging
    if enable=='all' or 'http' in enable:
        remote_log = settings.get('logs_server')
        remote_verbosity = settings.get('logs_server_verbosity')
        if remote_log not in ('', None) and remote_verbosity not in ('', None):
            parsed = urlparse(remote_log)
            handler_remote = RemoteHandler(host=parsed.netloc, url=parsed.path, method='POST')
            handler_remote.setLevel(remote_verbosity)
            logger.addHandler(handler_remote)

    # Email logging
    if enable=='all' or 'email' in enable:
        mailhost = settings.get('logs_email_smtp_server')
        fromaddr = settings.get('logs_email_from')
        toaddrs = settings.get('logs_email_to', '')
        username = settings.get('logs_email_username')
        password = settings.get('logs_email_password')
        email_verbosity = settings.get('logs_email_verbosity')
        name = settings.get('controller', '') + settings.get('application', '')

        if (email_verbosity not in ('', None) and \
            mailhost not in ('', None) and \
            fromaddr not in ('', None) and \
            len(toaddrs) > 0 and toaddrs[0]!='' and \
            username not in ('', None) and password not in ('', None)):
            
            logger.info('Setting up email logging.')   
            handler_email = EmailHandler(name, mailhost, fromaddr, toaddrs,
                username, password, capacity=settings['logs_email_batchsize'],
                logger=logger)
            handler_email.setLevel(email_verbosity)
            handler_email.setFormatter(formatter)
            logger.addHandler(handler_email)
        else:
            logger.info('Skipping email logging.')

    return logger



class EmailHandler(BufferingHandler):
    
    
    def __init__(self, name, mailhost, fromaddr, toaddrs, username, password,
            capacity, flushLevel=logging.CRITICAL, logger: logging.Logger=None):
    """
    Accumulates log records for some time and sends them as an email when capacity
    is reached or when a particular severity level is reached.

    Attributes
    ----------
    smtp: smtplib.SMTP
        The email server instance
    buffer: List
        A list of logging.LogRecord messages
    """
    
    
    def __init__(self, name: str, mailhost: str, fromaddr: str, toaddrs: list,
            username: str, password: str, capacity: int,
            flushLevel:str=logging.CRITICAL, logger: logging.Logger=None):
        """

        Parameters
        ----------
        name : str
            The name to use in the email subject
        mailhost : str
            The hostname of the smtp email server sending emails
        fromaddr : str
            The address to send the email from
        toaddrs : list
            A list of addresses to send the email to
        username : str
            username for logging into the email server
        password : str
            Password for logging into the email server
        capacity : int
            Maximum number of messages to buffer before sending them in one email
        flushLevel : str, optional
            Severity of message which triggers email, by default logging.CRITICAL
        logger : logging.Logger, optional
            A logger to log setup of this instance to, by default None
        """
        super().__init__(capacity)
        self.name = name
        self.fromaddr = fromaddr
@@ -24,6 +133,9 @@ class EmailHandler(BufferingHandler):
        

    def flush(self):
        """
        Compose and send the email message.
        """
        maxlevel = max(self.buffer, key=lambda r: r.levelno).levelname
        
        msg = email.message.EmailMessage()
@@ -38,19 +150,53 @@ class EmailHandler(BufferingHandler):
        super().flush()


    def shouldFlush(self, record):
    def shouldFlush(self, record) -> bool:
        """
        Given the latest message record, determines if email should be triggered
        or not.

        Parameters
        ----------
        record : logging.LogRecord
            The latest log message

        Returns
        -------
        bool
            True if email should be triggered.
        """
        return super().shouldFlush(record) or record.levelno >= self.flushLevel



class RemoteHandler(HTTPHandler):
    """
    Logging Handler to send HTTP requests to a server.
    """


    def mapLogRecord(self, record: logging.LogRecord) -> Dict[str, str]:
        """
        Converts a logging.LogRecord message into a dictionary which is sent
        as an encoded URL (GET) or a form (POST) to the server.

        Parameters
        ----------
        record : logging.LogRecord
            The latest message log

        Returns
        -------
        Dict[str, str]
            A dictionary to encode into a HTTP request.
        """
        return super().mapLogRecord(record)



class UTCFormatter(logging.Formatter):
    """
    A formatter which uses UTC time (instead of local time) as default.
    """

    converter = time.gmtime
+0 −2
Changes for src/utils/type_operations.py: 0 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -8,8 +8,6 @@ import datetime
from typing import Any

import numpy as np
from sklearn.base import BaseEstimator
from gym import Env