Commit 128ffab8 authored by w2v1's avatar w2v1
Browse files

First pass at PostgresLoggerMixin

parent 56153506
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
The intent of the class PostgresLoggerMixin is to provide a quick and easy way to log information to both the terminal and the database. The class is of type Database from the common_package, and so leverages both the database and the logger that is created by it’s parent class, Database(), from common_package.

All one needs to initiate the PostgresLogger class is a schema name, then they would call the log just like they would normally:

	from common.database import Database

    db_log = PostgresLogger(self.workspace)
    db_log.info("This is a log message", w=True)

This will log an INFO level log message and attempt to write it to the database.

Features of the this custom logging class:
Passing the -w flag will log the message to the log table of a schema in the database.
Passing the -c flag will add the log to an internal list with the intent being to dump all of the logs in the database later. For example, if you have a running loop and want to log absolutely everything, but don’t want to open and close a database connection through every iteration, simply pass the -c flag to your log method, and after your loop, run the write_log_collection_to_database(). This logs all messages in the list and clears the log collection, self.log_collection. Note that this can be leveraged without using the logger, just pass write_log_collection_to_database() a list of tuples representing log messages.

NOTE:
Note that logging provides overhead, so logging a lot of information in long running loops will slow down your functions. Just keep this in mind when deciding what you want to log.
 No newline at end of file
+124 −0
Original line number Diff line number Diff line
import time

from common.database import Database
from common.mixins.postgres import PostgresMixin

class PostgresLoggerMixin(Database, PostgresMixin):
    """
    Log Handler for easy logging.
  
    The intent of this handler is to have a common place to easily print logs
    to the terminal and/or record them to the .log table of a schema. After
    initializing the class, passing the w parameter to the log function will tell
    the function to write the log to the database, for example:
    db_logger.info("TEST MESSAGE", w=True)
  
    Attributes:
        schema (str): The name of the schema
  
    """
    def __init__(self, schema, **kwargs):
        """
        The constructor for the PostgresLoggerMixin class.

        Parameters:
            schema (str): The name of the schema
        """
        super().__init__()
        self.schema = schema
        self.log_collection = []

    def debug(self, message, timestamp=None, w=False, c=False):
        """Logs a debug message."""
        timestamp = self.check_for_timestamp(timestamp)
        if w:
            self.check_table_exists()
            self.log_to_database('DEBUG', timestamp, message)
        elif c:
            self.collect_logs('DEBUG', timestamp, message)
        self.logger.debug(message)

    def info(self, message, timestamp=None, w=False, c=False):
        """Logs an info message."""
        timestamp = self.check_for_timestamp(timestamp)
        if w:
            self.check_table_exists()
            self.log_to_database('INFO', timestamp, message)
        elif c:
            self.collect_logs('INFO', timestamp, message)
        self.logger.info(message)

    def warning(self, message, timestamp=None, w=False, c=False):
        """Logs a warning message."""
        timestamp = self.check_for_timestamp(timestamp)
        if w:
            self.check_table_exists()
            self.log_to_database('WARNING', timestamp, message)
        elif c:
            self.collect_logs('WARNING', timestamp, message)
        self.logger.warning(message)

    def error(self, message, timestamp=None, w=False, c=False):
        """Logs an error message."""
        timestamp = self.check_for_timestamp(timestamp)
        if w:
            self.check_table_exists()
            self.log_to_database('ERROR', timestamp, message)
        elif c:
            self.collect_logs('ERROR', timestamp, message)
        self.logger.error(message)

    def critical(self, message, timestamp=None, w=False, c=False):
        """Logs a critical Message"""
        timestamp = self.check_for_timestamp(timestamp)
        if w:
            self.check_table_exists()
            self.log_to_database('CRITICAL', timestamp, message)
        elif c:
            self.collect_logs('CRITICAL', timestamp, message)
        self.logger.critical(message)

    # Only call from an exception handler
    def exception(self, message, timestamp=None, w=False, c=False):
        """Logs and exception message."""
        timestamp = self.check_for_timestamp(timestamp)
        if w:
            self.check_table_exists()
            self.log_to_database('EXCEPTION', timestamp, message)
        elif c:
            self.collect_logs('EXCEPTION', timestamp, message)
        self.logger.exceptionfo(message)

    def check_table_exists(self):
        """Checks if log table exists for workspace."""
        self.cursor.execute(f"CREATE TABLE IF NOT EXISTS {self.schema}.log (levelname TEXT, ts TIMESTAMP, message TEXT);")

    def check_for_timestamp(self, timestamp):
        """Checks if timestamp is provided."""
        if not timestamp:
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
            return timestamp

    def log_to_database(self, loglevel, timestamp, message):
        "Logs log message to database."
        self.cursor.execute(f"INSERT INTO {self.schema}.log (levelname, ts, message) VALUES('{loglevel}', '{timestamp}', '{message}');")

    def collect_logs(self, level, timestamp, message):
        """Appends log messages to list"""
        log = (level, timestamp, message)
        self.log_collection.append(tuple(log))

    def write_log_collection_to_database(self, log_list=None):
        """Writes collection of logs to database and empties log list"""
        self.check_table_exists()
        # This keeps us from mucking up the internal list, even though we double
        # the code
        if log_list:
            for l in log_list:
                self.cursor.execute(f"INSERT INTO {self.schema}.log VALUES('{l[0]}', '{l[1]}', '{l[2]}');")
        elif self.log_collection:
            for l in self.log_collection:
                self.cursor.execute(f"INSERT INTO {self.schema}.log VALUES('{l[0]}', '{l[1]}', '{l[2]}');")
            self.log_collection = []
        else:
            self.warning("No logs recorded.")
+49 −0
Original line number Diff line number Diff line
@@ -5,8 +5,11 @@ import unittest
import os
import subprocess
import logging
import time
from datetime import datetime
from common.database import Database
from common.mixins.postgres import PostgresMixin
from common.mixins.postgres_logger import PostgresLoggerMixin
from common.logz import create_logger


@@ -120,6 +123,52 @@ class LogzTestCase(unittest.TestCase):
        log = create_logger()
        self.assertTrue(isinstance(log, logging.Logger))

    def test_postgresloggermixin(self):
        """
        test_postgresloggermixin:
            Method to test the postgresloggermixin class.
        """
        plm = PostgresLoggerMixin('public')
        self.assertTrue(isinstance(plm, PostgresLoggerMixin))

        # Test log message
        plm.info("Test Message")

        # test writing log message to database
        test_log = "Test log to db"
        dt = datetime.now()
        new_dt = datetime.strptime(str(dt).split(".")[0], '%Y-%m-%d %H:%M:%S')

        plm.open()
        plm.info(test_log, w=True)
        plm.close()

        with DBPG() as data_base:
            get_log = data_base.query("SELECT * FROM public.log")
        
        self.assertEqual([("INFO", new_dt, "Test log to db")], get_log)

        # Test writing a collection to database
        plm.info("test info collection", c=True)
        plm.warning("test warning collection", c=True)
        plm.critical("test critical collection", c=True)

        plm.open()
        plm.write_log_collection_to_database()
        plm.close()

        with DBPG() as data_base:
            get_logs = data_base.query("SELECT * FROM public.log")

        self.assertListEqual(
            [
                ("INFO", new_dt, "test info collection"),
                ("WARNING", new_dt, "test warning collection"),
                ("CRITICAL", new_dt, "test critical collection")
            ],
            [
                get_logs[1], get_logs[2], get_logs[3]
            ]
        )
if __name__ == "__main__":
    unittest.main(verbosity=2)