Delgan / loguru

Python logging made (stupidly) simple
MIT License
18.98k stars 681 forks source link

Logging a dictionary avoiding conversion to string #945

Open FrancescoSaverioZuppichini opened 11 months ago

FrancescoSaverioZuppichini commented 11 months ago

Hi There, I am trying to log a dictionary

def serialize(record):
    print(type(record["message"]))
    subset = {
        "timestamp": record["time"].timestamp(),
        "message": record["message"],
        "level": record["level"].name,
        "process": {"id": record["process"].id},
    }
    return orjson.dumps(subset)

def patching(record):
    record["extra"]["serialized"] = serialize(record)

logger = logger.patch(patching)
logger.remove(0)
logger.add(sys.stderr, format="{extra[serialized]}")

logger.info({"foo": "hey"})

However, inside logger.info({"foo": "hey"}) my dictionary gets converted to a string. Actually everything there gets converted to string for some reason. Is there a way to change this behaviour?

FrancescoSaverioZuppichini commented 11 months ago

I can see in the source code you do

"message": str(message),

Which is kinda of bad imho because I cannot change it :(

AlTosterino commented 11 months ago

Hey,

I think this is proper behaviour, as the log message is treated as a piece of text.

Isn't. log.bind(**your_dict).info("") enough for your use case? I think bind and contextualize were build for these type of scenarios

Delgan commented 11 months ago

The methods such as logger.info() accepts any type for convenience, but the produced record["message"] is guaranteed to be a str as static typing is less error-prone (for custom formatters and sinks).

I would advice to pass the additional context using bind() or logger.info(..., **your_dict) as suggested by @AlTosterino.

To my knowledge, it's not very conventional to use logging without an actual context message. Is there any reason for that?

FrancescoSaverioZuppichini commented 11 months ago

Hi @AlTosterino and @Delgan thanks a lot for the replies (🤗 ). The reason is that I would like to log structured content and I need to log dictionaries, in the nodejs world pinojs is the goto and I really like it so I was looking for something similar with loguru. Imagine you want to log some data, sink them and search in them, logging just strings seems to be very limiting

So basically, I can log.bind(**dict).info("") and then I take record["extra"]?

Delgan commented 11 months ago

Yes, you can use logger.bind(**dict).info("Message") or even logger.info("Message", **dict). This is the standard way to work with structured logging using Loguru. These additional parameters will indeed be made available in record["extra"].

Although I welcome usage of structured logging, I don't quite understand why one would want to use an empty "" string as the logged message, instead of an human-friendly description of the logged event. This doesn't prevent to attach additional parameters when needed.

Actually, pinojs example is very similar to usage of bind() in Loguru:

const logger = require('pino')()

logger.info('hello world')

const child = logger.child({ a: 'property' })
child.info('hello child!')

Which is equivalent to the following using Loguru (assuming the appropriate sink is configured):

from loguru import logger

logger.info("hello world")

child = logger.bind(a="property")
child.info("hello child!")

In both example, non-empty messages are logged. However, it's up to you, it should work anyway. :)

FrancescoSaverioZuppichini commented 11 months ago

Yes, you can use logger.bind(**dict).info("Message") or even logger.info("Message", **dict). This is the standard way to work with structured logging using Loguru. These additional parameters will indeed be made available in record["extra"].

Although I welcome usage of structured logging, I don't quite understand why one would want to use an empty "" string as the logged message, instead of an human-friendly description of the logged event. This doesn't prevent to attach additional parameters when needed.

Actually, pinojs example is very similar to usage of bind() in Loguru:

const logger = require('pino')()

logger.info('hello world')

const child = logger.child({ a: 'property' })
child.info('hello child!')

Which is equivalent to the following using Loguru (assuming the appropriate sink is configured):

from loguru import logger

logger.info("hello world")

child = logger.bind(a="property")
child.info("hello child!")

In both example, non-empty messages are logged. However, it's up to you, it should work anyway. :)

Thanks a lot, I'll try an report back 🤗