Santhosh
Santhosh

Reputation: 11798

python: logging: can we added multiple filters to the logger and which one is considered

I am trying to understand how multiple Filters (one defined in config and other in the code) in Python logging work.

I am working on a Django project and below is my logger config in settings.py

My goal is to switch on and switch off the logger whenever i want. So using filters i am trying to switch off the logger by returning False (0)

1) Switch off the logger in the starting

class StartFilter(object):
    def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Is the specified record to be logged? Returns 0 for no, nonzero for
        yes. If deemed appropriate, the record may be modified in-place.
        """
        return 0

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(funcName)s() %(pathname)s[:%(lineno)s] %(name)s \n%(message)s'
        }
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'formatter': 'verbose',
            'class': 'logging.StreamHandler',
        },
    },
    'filters': {
        'myfilter': {
            '()': StartFilter,
        }
    },
    'loggers': {
        'log_testing': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': False,
            'filters': ['myfilter']
        },
    }
}

I have added the filter to the logger. 'filters': ['myfilter']

2) Switch on and off the logger in the views.py file where i want the logging to be seen

# to switch on logger
class LoggerGateStart(object):
    def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Is the specified record to be logged? Returns 0 for no, nonzero for
        yes. If deemed appropriate, the record may be modified in-place.
        """
        return 1

# to switch off logger
class LoggerGateStop(object):
    def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Is the specified record to be logged? Returns 0 for no, nonzero for
        yes. If deemed appropriate, the record may be modified in-place.
        """
        return 0

import logging
logger = logging.getLogger("log_testing")


...
logging.debug("Some text Before)  # i dont want this to be logged
...



gatestart = LoggerGateStart()
logger_database.addFilter(gatestart)

...
logging.debug("Some text)  # i want this to be logged
...

gatestop = LoggerGateStop()
logger_database.addFilter(gatestop)

...
logging.debug("Some text after")  # i dont want this to be logged even 
if it exist
...

I found its not working this way. It considers the StartFilter only and not consider LoggerGateStart or LoggerGateStop and does not print any log to the console

How can i do this

ANSWER I USED BASED ON THE ANSWER OF Gabriel C

My goal was to log sql using django django.db.backends. But the problem with it is that it will log all the sqls. I want to log only sqls in a particular section of a code or whereever i want to see the sqls. So the following way i could do it.

logging config inside settings.py:

# Filter class to stop or start logging for "django.db.backends"
class LoggerGate:
    def __init__(self, state='closed'):
        # We found that the settings.py runs twice and the filters are created twice. So we have to keep only one. So we delete all the previous filters before we create the new one
        import logging
        logger_database = logging.getLogger("django.db.backends")
        try:
            for filter in logger_database.filters:
                logger_database.removeFilter(filter)
        except Exception as e:
            pass
        self.state = state

    def open(self):
        self.state = 'open'

    def close(self):
        self.state = 'closed'

    def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Is the specified record to be logged? Returns 0/False for no, nonzero/True for
        yes. If deemed appropriate, the record may be modified in-place.
        """
        return self.state == 'open'

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'sql': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
        }
    },
    'filters': {
        'myfilter': {
            '()': LoggerGate,
        }
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['sql'],
            'level': 'DEBUG',
            'propagate': False,
            'filters': ['myfilter']
        }
    }
}

Then in the views.py

import logging
logger = logging.getLogger(__name__)
logger_database = logging.getLogger("django.db.backends")

def test1(request):


    logger_database.filters[0].open()
    #Will allow priting of sql satatements from here

    from django import db
    user_set = User.objects.all()

    for user in user_set: # Here sql is executed and is printed to console
        pass
    #Will stop priting of sql satatements after this
    logger_database.filters[0].close()

    from django import db
    user_set = User.objects.all()

    for user in user_set:  # Here sql is executed and is not printed to console
        pass

    now = datetime.datetime.now()
    html = "<html><body>Internal purpose</body></html>"
    return HttpResponse(html)

If one wants to print the sql in formatted and colorful way use this in the settings.py

# SQL formatter to be used for the handler used in logging "django.db.backends"
class SQLFormatter(logging.Formatter):
    def format(self, record):

        # Check if Pygments is available for coloring 
        try:
            import pygments
            from pygments.lexers import SqlLexer
            from pygments.formatters import TerminalTrueColorFormatter
        except ImportError:
            pygments = None

        # Check if sqlparse is available for indentation
        try:
            import sqlparse
        except ImportError:
            sqlparse = None

        # Remove leading and trailing whitespaces
        sql = record.sql.strip()

        if sqlparse:
            # Indent the SQL query
            sql = sqlparse.format(sql, reindent=True)

        if pygments:
            # Highlight the SQL query
            sql = pygments.highlight(
                sql,
                SqlLexer(),
                #TerminalTrueColorFormatter(style='monokai')
                TerminalTrueColorFormatter()
            )

        # Set the record's statement to the formatted query
        record.statement = sql
        return super(SQLFormatter, self).format(record)




# Filter class to stop or start logging for "django.db.backends"
class LoggerGate:
    def __init__(self, state='closed'):
        # We found that the settings.py runs twice and the filters are created twice. So we have to keep only one. So we delete all the previous filters before we create the new one
        import logging
        logger_database = logging.getLogger("django.db.backends")
        try:
            for filter in logger_database.filters:
                logger_database.removeFilter(filter)
        except Exception as e:
            pass
        self.state = state

    def open(self):
        self.state = 'open'

    def close(self):
        self.state = 'closed'

    def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Is the specified record to be logged? Returns 0/False for no, nonzero/True for
        yes. If deemed appropriate, the record may be modified in-place.
        """
        return self.state == 'open'

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'sql': {
            '()': SQLFormatter,
            'format': '[%(duration).3f] %(statement)s',
        }
    },
    'handlers': {
        'sql': {
            'class': 'logging.StreamHandler',
            'formatter': 'sql',
            'level': 'DEBUG',
        }
    },
    'filters': {
        'myfilter': {
            '()': LoggerGate,
        }
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['sql'],
            'level': 'DEBUG',
            'propagate': False,
            'filters': ['myfilter']
        }
    }
}

Upvotes: 2

Views: 1830

Answers (2)

Sraw
Sraw

Reputation: 20224

Gabriel C gives a great solution. And to explain more, filter works in sequence, which means the record is passed to each filter one by one. And will stop at the one returns a zero. So as your StartFilter returns 0, it will directly drop all records.

Upvotes: 1

Gabriel Cappelli
Gabriel Cappelli

Reputation: 4170

Create only one filter and use it's instance to control if logs should be accepted or not.

from logging import getLogger

class LoggerGate(object):

    def __init__(self):
        self.started = False

    def start(self):
        self.started = True

    def stop(self):
        self.started = False

    def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Returns True is this LoggerGate is started, False otherwise.
        """
        return self.started


logger_database = getLogger("log_testing")
logger_gate = LoggerGate()
logger_database.addFilter(logger_gate)

logger_database.critical('this is not logged')
logger_gate.start()
logger_database.critical('this is logged')
logger_gate.stop()
logger_database.critical('this is not logged')

Upvotes: 1

Related Questions