loguru icon indicating copy to clipboard operation
loguru copied to clipboard

Is there a way to disable message formatting ?

Open gjeusel opened this issue 3 years ago • 20 comments

Hi Delgan !

I was wondering if there was a way to disable the message formatting with the args/kwargs ?

from loguru import logger

kwargs = {"a": 1, "b": 2}
logger.info(f"Is this going to be alright ? {kwargs}", additional_infos=kwargs)

Traceback:

Traceback (most recent call last):
  File "/tmp/test.py", line 4, in <module>
    logger.info(f"Is this going to be alright ? {kwargs}", additional_infos=kwargs)
  File "/Users/gjeusel/miniconda3/envs/greenpandas/lib/python3.6/site-packages/loguru/_logger.py", line 1971, in info
    __self._log("INFO", None, False, __self._options, __message, args, kwargs)
  File "/Users/gjeusel/miniconda3/envs/greenpandas/lib/python3.6/site-packages/loguru/_logger.py", line 1948, in _log
    log_record["message"] = message.format(*args, **kwargs)
KeyError: "'a'"

Is it a feature you would be inclined in adding ? Maybe parametrized by an environment variable or via logger.configure ?

Cordially !

gjeusel avatar Sep 08 '20 08:09 gjeusel

Hi!

Well, you can disable formatting by not providing any argument. If you want some arguments to be added to the extra dict, you can use bind() instead:

logger.bind(additional_infos=kwargs).info(f"Is this going to be alright ? {kwargs}")

Another possiblity is simply to not use f-strings:

logger.info("Is it going to be alright? {additional_infos}", additional_infos=kwargs)

I agree that this may not be the most convenient, since it implies always having to think about that particular case. Especially as auto-formatting is not very useful anymore now that f-strings are widely used.

I'm not a big fan of adding more and more configuration options because it makes the code and the api more complex. But maybe I need to reconsider my position.

Delgan avatar Sep 08 '20 21:09 Delgan

Thx for your answer ! Indeed, using the bind() to fill up extras might be a solution.

I understand your aversion of adding more configuration. Let's wait and see if this feature is of interest to some more people ?

gjeusel avatar Sep 15 '20 05:09 gjeusel

I may have some ideas to allow more freedom in configuring the message formatting. I think this feature might be of interest to some people, in your case and others. But I'm not sure of anything, we'll see. I'll keep this issue open in the meantime. :+1:

Delgan avatar Sep 16 '20 20:09 Delgan

Hi,

I just ran into a similar issue when using an intercept handler to collect logs from pika/pika. The library logs a dictionary at some point, which throws a KeyError exception when I add a keyword argument to the same logging function call.

Your suggestion to use bind() works, but I figured I'd add my use case here anyways.

Thanks for your hard work on Loguru!

kace avatar Nov 06 '20 01:11 kace

Thanks @kace for the additional use case, it's very helpful to have this kind of example while thinking about a solution.

I plan to propose a way to configure formatting, but I still haven't decided which one. Adding a parameter like logger.configure(autoformat=False) is obviously the easiest, but I'm afraid it opens the door to too many additional parameters. Also, I would like it to be flexible enough to implement customized alternatives like #359. Ideally, one could just patch() the logger using the existing method. But this one is currently called after formatting so it wouldn't work.

Anyway, I am still thinking about this issue. ;)

Delgan avatar Nov 08 '20 12:11 Delgan

In my case, this happened to me when I combined captureWarnings() with modifying my InterceptHandler to include extra=.

import logging
import loguru
import warnings

logger = loguru.logger

class InterceptHandler(logging.Handler):
    def emit(self, record):
        # Get corresponding Loguru level if it exists
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # Find caller from where originated the logged message
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(
            level, record.getMessage(), extra={'more info': 'something else'}  # this line changed from usual to include extra=
        )

logging.captureWarnings(True)
logging.getLogger('py.warnings').handlers = [InterceptHandler()]

if __name__ == '__main__':
    id_ = 23
    warnings.warn(f'problem with something of id: {id_}')

(The above results in an unexpected KeyError because warnings are automatically formatted by Python to include the raw line of code from which they were thrown).

My workaround right now is to use contextualize() instead of adding the extra() manually:

       with logger.contextualize(**{'more info': 'something else'}):  # adds the context data to logged messages here
            logger.opt(depth=depth, exception=record.exc_info).log(
                level, record.getMessage()
            )

soundstripe avatar Mar 05 '21 16:03 soundstripe

@soundstripe This is very similar to my use case. You can see my workaround using bind in my comment on another issue. This is especially useful if more info in your code doesn't change between logging calls.

If I remember correctly my original implementation passed the id parameter as a keyword argument to the logging call. I think the documentation mentions that any additional kwargs in this method call are automatically added to the extra's dict.

kace avatar Mar 05 '21 22:03 kace

Using the standard logger and intercept handler, I get an error with standard_logger.error("hello {fred}"):

Traceback (most recent call last):
  File "/usr/lib/python3.10/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  File "/usr/lib/python3.10/logging/__init__.py", line 1506, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/lib/python3.10/logging/__init__.py", line 1624, in _log
    self.handle(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 1634, in handle
    self.callHandlers(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 1696, in callHandlers
    hdlr.handle(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 968, in handle
    self.emit(record)
  File "<input>", line 20, in emit
  File "/home/mcarans/Programming/VirtualEnvs/hdx-python-utilities-3.10/lib/python3.10/site-packages/loguru/_logger.py", line 2003, in log
    __self._log(level_id, static_level_no, False, __self._options, __message, args, kwargs)
  File "/home/mcarans/Programming/VirtualEnvs/hdx-python-utilities-3.10/lib/python3.10/site-packages/loguru/_logger.py", line 1948, in _log
    colored_message = Colorizer.prepare_message(message, args, kwargs)
  File "/home/mcarans/Programming/VirtualEnvs/hdx-python-utilities-3.10/lib/python3.10/site-packages/loguru/_colorizer.py", line 363, in prepare_message
    tokens = Colorizer._parse_with_formatting(string, args, kwargs)
  File "/home/mcarans/Programming/VirtualEnvs/hdx-python-utilities-3.10/lib/python3.10/site-packages/loguru/_colorizer.py", line 414, in _parse_with_formatting
    obj, _ = formatter.get_field(field_name, args, kwargs)
  File "/usr/lib/python3.10/string.py", line 270, in get_field
    obj = self.get_value(first, args, kwargs)
  File "/usr/lib/python3.10/string.py", line 227, in get_value
    return kwargs[key]
KeyError: 'fred'

mcarans avatar Mar 01 '22 03:03 mcarans

@mcarans Are you able to provide a minimum reproducible example please (eventually in a new ticket)?

I tested it locally and it worked as expected.

Delgan avatar Mar 05 '22 09:03 Delgan

@Delgan It seems to occur when setting record=True in the InterceptHandler (something I put in when I was trying to solve https://github.com/Delgan/loguru/issues/605) ie:

from loguru import logger
import logging

class InterceptHandler(logging.Handler):
    def emit(self, record):
        # Get corresponding Loguru level if it exists
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # Find caller from where originated the logged message
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(
            colors=True,
            record=True,
            depth=depth,
            exception=record.exc_info,
        ).log(level, record.getMessage())


logging.basicConfig(handlers=[InterceptHandler()], level=logging.NOTSET)

standard_logger = logging.getLogger("test")
standard_logger.error("hello {fred}")

mcarans avatar Mar 05 '22 17:03 mcarans

@mcarans When record=True Loguru expects the logged message to contain the "{record}" formatting field, e.g:

logger.opt(record=True).info("Logging some message from thread {record[thread].id}")

To do so, Loguru will call message.format(...). In your case, the message is "hello {fred}" but there is no fred keyword argument to be fetched to format the message, hence the error.

For this reason I would advice against using record=True when you don't control the formatted message.

Delgan avatar Mar 07 '22 22:03 Delgan

Hi @Delgan , I've just started using loguru with FastAPI and it's very good, but I bumped into the same issue above with our InterceptHandler, and in some cases we have dictionaries being pushed to the log message, and I could not bypass the message.format(...), using any configuration above. Any plans to include some bypass config? Thanks for the nice work with loguru!

fernhtls avatar Apr 15 '22 13:04 fernhtls

+1

fmmoret avatar Jul 19 '22 22:07 fmmoret

I want to stress the problem with this automatic format behavior. Say we want to log, for whatever reason, data input by users.

There would be no way for me to do this without risking a crash when any string containing curly brackets is passed.

mastern2k3 avatar Aug 23 '22 13:08 mastern2k3

@mastern2k3 I'm not sure to understand.

logger.info("User data: {}", input())

That would work fine and wouldn't not crash regardless of user data containing curly brackets or not.

Delgan avatar Aug 23 '22 17:08 Delgan

@Delgan you are right, but, This means I'm prevented from using f-string formatting as the standard throughout my system, and need to keep asking myself whether a certain variable might contain curly braces or not, and use one or the other depending on which case.

mastern2k3 avatar Aug 23 '22 17:08 mastern2k3

@mastern2k3 You can use f-strings as long as you do not call the logging method with formatting arguments:

logger.info(f"User data: {user_data}")                      # Good.
logger.info(f"User data: {user_data}", data=user_data)      # Bad.
logger.bind(data=user_data).info(f"User data: {user_data})  # Good.

I got your point, though. I plan to remove automatic formatting in favor of f-strings.

Delgan avatar Aug 23 '22 17:08 Delgan

@Delgan Thanks! I didn't notice this is triggered only when there's extra data, that helps a bit.

We often use named parameters after the message (as an f-string) to add attributes to the extras so it ends up in our structured logs, this is what gets me.

mastern2k3 avatar Aug 23 '22 19:08 mastern2k3

@Delgan hello, I faced the same issue. In my case I want to do my own format function of json serialisation to elasticsearch json format

v1ack avatar Aug 25 '22 18:08 v1ack

No, not the same issue) I found a solution for my case by using message.record in sink function

v1ack avatar Aug 25 '22 19:08 v1ack

@Delgan Is removing automatic formatting in favor of f-strings still planned?

mcarans avatar Feb 01 '23 03:02 mcarans

@mcarans Yeah, this is something I would like to do, but I don't have an ETA or technical solution for the transition yet.

Delgan avatar Feb 01 '23 20:02 Delgan