Home > database >  How to overrider Logger class with method to select log level and output line of the log
How to overrider Logger class with method to select log level and output line of the log

Time:06-04

I need to record logs in many places of my Django application and I want to have it in a standardized way so that when people contribute they don't have to worry to much about it.

So instead of calling:

logger.info(<standardized message here>)

I would like to write a function or probably a class that inherits Logger and selects the correct .info, .warning, .error and so on and outputs the message the way we want.

So in the code I would only call

log(request, 200, "info", "Success message")

And the function I wrote looks like this:

logger = logging.getLogger(__name__)


def log(request=None, status_code=None, level=None, message=None):
    """
    Create a log stream in a standardized format
    <request_path> <request_method> <status_code>: user <user_id> <message>
    """
    method = request.method
    path = request.path

    user_id = None
    if not request.user.is_anonymous:
        user_id = request.user.id

    log = f"{path} {method} {status_code}: user {user_id} {message}"

    if level == 'info':
        logger_type = logger.info
    elif level == 'warning':
        logger_type = logger.warning
    elif level == 'error':
        logger_type = logger.error
    elif level == 'debug':
        logger_type = logger.debug

    return logger_type(log)

The problem is that our log formatter also records the line in the code where the log happened, and because the log is called in the log function, the row reflects the return logger_type(log) instead of the actual line in the code where log was called.

How can I write a class with a method to do the automatic selection of log level based on the input and still stream the log from the line where it happened?

CodePudding user response:

I want to have it in a standardized way

If you really want to do things in a standardized way, then use what the logging module gives you and don't create your own helper functions.

The logging already lets you pass in "extra" arguments for the logging message (doc):

logger.info("success message", extra={"request": request, "status_code": 200})

You can then put your logic extract parts of the request in a formatter object.

As well as following the standard, this will give you additional flexibility, as your formatter can do different things based on what extra parameters it gets.

One thing that the Python logging module is missing that other loggers have is the idea of a thread-local "context" object. You could use such and object to preserve the request at the time it's processed, so that it's available in all log messages without being passed in explicitly. You could implement this by adding a request_context dictionary to the current thread, and look for it in your formatter.

CodePudding user response:

If you would like to stick with your helper function approach, the logging methods accept kwarg stacklevel that specifies the corresponding number of stack frames to skip when computing the line number and function name ref. The default is 1, so setting it to 2 should look to the frame above you helper function. For example:

import logging

logging.basicConfig(format="%(lineno)d")
logger = logging.getLogger(__name__)


def log():
    logger.error("message", stacklevel=2)


log()
  • Related