Skip to main content
added 395 characters in body
Source Link
janos
  • 113.1k
  • 15
  • 154
  • 396

Inspecting the stack on every logger call

Although fetchOrCreateLogger caches the loggers to avoid creating them repeatedly, inspect.stack() is called for every logger method call (every log.debug, log.info, and so on). Note that inspecting the stack is usually not cheap.

You could replace log = Bouncer() with a factory method:

# in LoggingHandler
def callsite_path_logger():
    filepath = inspect.stack()[1].filename

    # ...

    return logger

And then use it in files that want this kind of logger as:

from LoggingHandler import callsite_path_logger


log = callsite_path_logger()

Notice that with this approach you don't need the cache anymore.

One caveat is that you must remember to not pass this logger to code in other files, because the logging calls will use filepath that was determined at construction time.

If you want a logger that will always report the correct path no matter from where it is called, then indeed you must inspect the call stack non every logging call, as in the original code. I don't recall such use case in practice, most of time loggers are created per file, using a factory method.

Transparent caching

The posted code manages the cache of loggers. You could benefit from functools.cache to do that for you, getting rid of the loggers cache:

@functools.cache
def logger_for(filepath):
    logger = logging.getLogger(filepath)

    # ...

    return logger


class Bouncer:
    def __getattr__(self, item):
        return getattr(logger_for(inspect.stack()[1].filename), item)

Minor issues

Instead of path in loggers.keys() you can write simply path in loggers.

Instead of this:

if (filepath := inspect.stack()[2].filename) not in loggers:

I think this is easier to read, with one statement per line:

filepath = inspect.stack()[2].filename
if filepath not in loggers:

I would rename loggr to the natural spelling logger.

Inspecting the stack on every logger call

Although fetchOrCreateLogger caches the loggers to avoid creating them repeatedly, inspect.stack() is called for every logger method call (every log.debug, log.info, and so on). Note that inspecting the stack is usually not cheap.

You could replace log = Bouncer() with a factory method:

# in LoggingHandler
def callsite_path_logger():
    filepath = inspect.stack()[1].filename

    # ...

    return logger

And then use it in files that want this kind of logger as:

from LoggingHandler import callsite_path_logger


log = callsite_path_logger()

Notice that with this approach you don't need the cache anymore.

One caveat is that you must remember to not pass this logger to code in other files, because the logging calls will use filepath that was determined at construction time.

If you want a logger that will always report the correct path no matter from where it is called, then indeed you must inspect the call stack non every logging call, as in the original code. I don't recall such use case in practice, most of time loggers are created per file, using a factory method.

Transparent caching

The posted code manages the cache of loggers. You could benefit from functools.cache to do that for you, getting rid of the loggers cache:

@functools.cache
def logger_for(filepath):
    logger = logging.getLogger(filepath)

    # ...

    return logger


class Bouncer:
    def __getattr__(self, item):
        return getattr(logger_for(inspect.stack()[1].filename), item)

Inspecting the stack on every logger call

Although fetchOrCreateLogger caches the loggers to avoid creating them repeatedly, inspect.stack() is called for every logger method call (every log.debug, log.info, and so on). Note that inspecting the stack is usually not cheap.

You could replace log = Bouncer() with a factory method:

# in LoggingHandler
def callsite_path_logger():
    filepath = inspect.stack()[1].filename

    # ...

    return logger

And then use it in files that want this kind of logger as:

from LoggingHandler import callsite_path_logger


log = callsite_path_logger()

Notice that with this approach you don't need the cache anymore.

One caveat is that you must remember to not pass this logger to code in other files, because the logging calls will use filepath that was determined at construction time.

If you want a logger that will always report the correct path no matter from where it is called, then indeed you must inspect the call stack non every logging call, as in the original code. I don't recall such use case in practice, most of time loggers are created per file, using a factory method.

Transparent caching

The posted code manages the cache of loggers. You could benefit from functools.cache to do that for you, getting rid of the loggers cache:

@functools.cache
def logger_for(filepath):
    logger = logging.getLogger(filepath)

    # ...

    return logger


class Bouncer:
    def __getattr__(self, item):
        return getattr(logger_for(inspect.stack()[1].filename), item)

Minor issues

Instead of path in loggers.keys() you can write simply path in loggers.

Instead of this:

if (filepath := inspect.stack()[2].filename) not in loggers:

I think this is easier to read, with one statement per line:

filepath = inspect.stack()[2].filename
if filepath not in loggers:

I would rename loggr to the natural spelling logger.

Source Link
janos
  • 113.1k
  • 15
  • 154
  • 396

Inspecting the stack on every logger call

Although fetchOrCreateLogger caches the loggers to avoid creating them repeatedly, inspect.stack() is called for every logger method call (every log.debug, log.info, and so on). Note that inspecting the stack is usually not cheap.

You could replace log = Bouncer() with a factory method:

# in LoggingHandler
def callsite_path_logger():
    filepath = inspect.stack()[1].filename

    # ...

    return logger

And then use it in files that want this kind of logger as:

from LoggingHandler import callsite_path_logger


log = callsite_path_logger()

Notice that with this approach you don't need the cache anymore.

One caveat is that you must remember to not pass this logger to code in other files, because the logging calls will use filepath that was determined at construction time.

If you want a logger that will always report the correct path no matter from where it is called, then indeed you must inspect the call stack non every logging call, as in the original code. I don't recall such use case in practice, most of time loggers are created per file, using a factory method.

Transparent caching

The posted code manages the cache of loggers. You could benefit from functools.cache to do that for you, getting rid of the loggers cache:

@functools.cache
def logger_for(filepath):
    logger = logging.getLogger(filepath)

    # ...

    return logger


class Bouncer:
    def __getattr__(self, item):
        return getattr(logger_for(inspect.stack()[1].filename), item)