loguru icon indicating copy to clipboard operation
loguru copied to clipboard

Better exception with custom serializer

Open JargeZ opened this issue 2 years ago • 5 comments

Hi

There is already some related topics like #876

I am trying to use this method, but I ran into a problem with exception formatting. I want to see the better_exception format in my logs, but I can't find a way to achieve this with custom serializing.

because all of the documented ways called before this https://github.com/Delgan/loguru/blob/d379fedd9b19401e937c63de0154caf9a11252ec/loguru/_handler.py#L138

how I can use formatted exceptions as text message in my custom serializer instead of https://github.com/Delgan/loguru/blob/d379fedd9b19401e937c63de0154caf9a11252ec/loguru/_handler.py#L278-L280

JargeZ avatar Jun 07 '23 17:06 JargeZ

Hi.

The way exception formatting works in Loguru depends on whether you provide a string or a function as the format argument. When a string is used, the "\n{exception}" placeholder will automatically be appended for convenience. However, if a custom function is passed, Loguru gives you full control over the formatted placeholders.

In the ticket you linked, the sink is configured with format=lambda _: "{extra[serialized]}". This has the effect of disabling the exception formatting. However, you can re-enable it simply by using a string format="{extra[serialized]}" which will internally be expanded as format="{extra[serialized]}\n{exception}".

Delgan avatar Jun 07 '23 18:06 Delgan

Yeah this way is obvious However, unfortunately, it makes the log unstructured and, for example, ELK does not recognize this

I want to achieve the same effect as using serialize=True but with my own serialization function

in the standard behavior, the formatted exception log goes into the .text field and does not break the JSON structure as if I had used format="{extra[serialized]}\n{exception}"

JargeZ avatar Jun 08 '23 03:06 JargeZ

Then why not serialize the exception in your custom serializer?

Delgan avatar Jun 08 '23 06:06 Delgan

You mean something like this?

from loguru._better_exceptions import ExceptionFormatter

exception_formatter = ExceptionFormatter(
    colorize=False,
    encoding='utf-8',
    diagnose=settings.DEBUG,
    backtrace=settings.DEBUG,
    hidden_frames_filename=None,
    prefix='',
)

def serialize_log(record) -> str:
    subset = {
        "time": record["time"].isoformat(),
        "level": record["level"].name,
        "trace_id": record["extra"].get("trace_id", None),
        "message": record["message"]
    }

    if record["exception"]:
        type_, value, tb = record["exception"]
        lines = exception_formatter.format_exception(type_, value, tb, from_decorator=False)
        subset.update({
            "exception": "".join(lines)
        })

    entry = json.dumps(subset)
    return entry

def json_formatter(record):
    record["extra"]["serialized"] = serialize_log(record)
    return "{extra[serialized]}\n"

logger.configure(
    handlers=[
        {
            "sink": sys.stdout,
            "format": json_formatter if settings.JSON_LOGS else loguru_defaults.LOGURU_FORMAT,
            "level": settings.LEVEL,
        }
    ],
)

It certainly works, but it doesn't looks like the right way to me 🤷‍♀️

JargeZ avatar Jun 08 '23 07:06 JargeZ

Yes, I mean something like this, except that you don't have to use the non-public loguru._better_exceptions module, you can directly import and use better-exceptions or any other exception formatter of your choice.

Delgan avatar Jun 08 '23 19:06 Delgan