loguru icon indicating copy to clipboard operation
loguru copied to clipboard

Give sinks access to colour data from the format in the add() call

Open pfmoore opened this issue 4 years ago • 3 comments

Currently, if sinks want to colorize their output, then unless they can use a string with ANSI escape codes included directly, they have to do this entirely "by hand", building the coloured message string from the parts in the msg.record attribute. This means that the format argument is mostly useless for such sinks. In addition, there's no way of determining what colour the <level> markup corresponds to, because that's message level dependent, and there's no documented API that I could find to translate a level name to a colour).

I'm hitting this because I want to integrate loguru with rich, and I'd like to write a sink that takes a log message and writes it as a list of Rich Text objects.

As a concrete suggestion, would it be possible to:

  1. Add the applicable format to the msg.record attribute passed to sinks.
  2. Add a color attribute to the msg.record["level"] object.

With those two changes, it would be easier to write a sink that handled the format string in a custom way.

pfmoore avatar Sep 25 '21 16:09 pfmoore

For (2), I just discovered that the output of logger.level('ERROR') includes the colour, so that's sufficient to cover that one.

pfmoore avatar Sep 25 '21 22:09 pfmoore

Hi @pfmoore.

Can you please share an example of the output you expect from your sink?

It's possible to use a custom format function if one needs to deal with dynamic colorization and let it handled by Loguru. If more control is required, then logs can be colored using user preferred library in the sink, as you tried.

I'm not sure how exposing the colors fomat in the msg.record would help, because there is no public API to colorize the message anyway.

Delgan avatar Oct 03 '21 13:10 Delgan

What I want to do is roughly the following:

def RichSink(msg):
    # Stuff goes here to construct a rich.Text instance that displays the message
    log_table.add_row(msg_formatted_text)

# elsewhere in the program
logger.add(RichSink)

# Now, the log output goes to the Rich table, coloured the same as on the console

# Alternatively
logger.add(RichSink, format="...")

# Log output goes to Rich, formatted as the user requested.

The user might want a format of something like

format="[green]{time:HH:mm:ss}[/green] | [level]{message}[/level]"

Making them write that as

format=lambda msg: RichFormat(msg, "[green]{time:HH:mm:ss}[/green] | [level]{message}[/level]")

isn't exactly user friendly.

I'm fine with having to write code in my sink that takes a format string with loguru-style markup and translating that into the markup that the sink wants. Or having the user specify rich-style markup in the format string. But either way, letting loguru do the formatting won't work how I want - if (for example) the message has something in it that looks like a Rich formatting directive, I don't want that to get interpreted as markup, so I need to know where any given directive came from.

I'm not wedded to the idea of having the format argument available on the msg.record dictionary, but it seems like the simplest way of getting the data I need without too many changes to loguru.

Here's a working example, without the colour formatting, to give you an idea of what I'm doing:

from loguru import logger
from rich.table import Table
from rich.live import Live
from time import sleep

class Log:
    def __init__(self):
        self.logs = []
    def sink(self, msg):
        self.logs.append(msg)
    def get_table(self):
        t = Table()
        t.add_column("Log")
        for row in self.logs:
            t.add_row(row)
        return t

log = Log()
logger.remove()
logger.add(log.sink)

with Live(get_renderable=log.get_table):
    for i in range(10):
        logger.debug(f"Line {i}")
        sleep(0.5)

pfmoore avatar Oct 03 '21 14:10 pfmoore