gin66/tui-logger

Multiple log widgets displaying different log targets.

pms1969 opened this issue · 13 comments

Firstly, Thank you for producing this crate. I have thus far found it excellent.

What I'd like to do is have 2 separate log widgets, both displaying different log data dependent on the log target. I'm pretty sure I know the answer is that it's just not possible, but I was wondering if you could shed some insight as to what I might need to do to achieve that goal.

I basically want log::info!(target: "x", "This is log X"); and log::info!(target: "y", "This is log Y"); to show up in different log panes (widgets).

 ---Logs 1-----------------------Logs 2-----------------------
| This is log X              | This is log Y                  |
|                            |                                |
 -------------------------------------------------------------

☝️ A bit like that; stylistic hell I know 😃

gin66 commented

If I understand you correctly, you like to have two pure lists without the "Smart". If you look in the demo.rs, there is this code:

    let tui_w: TuiLoggerWidget = TuiLoggerWidget::default()
        .block(
            Block::default()
                .title("Independent Tui Logger View")
                .border_style(Style::default().fg(Color::White).bg(Color::Black))
                .borders(Borders::ALL),
        )
        .output_separator('|')
        .output_timestamp(Some("%F %H:%M:%S%.3f".to_string()))
        .output_level(Some(TuiLoggerLevelOutput::Long))
        .output_target(false)
        .output_file(false)
        .output_line(false)
        .style(Style::default().fg(Color::White).bg(Color::Black));
    t.render_widget(tui_w, chunks[2]);

The TuiLoggerWidget can receive a TuiWidgetState as parameter to .state() e.g. after .style(). And that TuiWidgetState allows to configure the TuiLoggerWidget at runtime using set_level_for_target() and set_default_display_level().

So you would need to instantiate and render two TuiLoggerWidgets with different states. That should be it.

Hope this helps

Thanks @gin66 , I did try that just for one, but had a terrible time trying to get it to work, to the point I gave up thinking it was meant to be an internal representation. I can't remember what my trouble was, but I'll try again and report back.

OK, So this is what I have:

    {
        let lws = TuiWidgetState::new().set_level_for_target("task_log", log::LevelFilter::Debug);
        // let mut tui_lw: TuiLoggerWidget = TuiLoggerWidget::default();
        // let tui_lw = tui_lw.state(&lws);
        let widget = TuiLoggerWidget::default()
            .block(
                Block::default()
                    .title(" Logs ")
                    .border_style(Style::default().fg(Color::White).bg(Color::Black))
                    .borders(Borders::ALL),
            )
            .style_error(Style::default().fg(Color::Red))
            .style_debug(Style::default().fg(Color::Green))
            .style_warn(Style::default().fg(Color::Yellow))
            .style_trace(Style::default().fg(Color::Magenta))
            .style_info(Style::default().fg(Color::Cyan))
            .output_separator('|')
            // .output_timestamp(Some("%F %H:%M:%S%.3f".to_string()))
            .output_level(Some(TuiLoggerLevelOutput::Abbreviated))
            .output_target(false)
            .output_file(false)
            .output_line(false)
            .style(Style::default().fg(Color::White).bg(Color::Black))
            .state(&lws);

        f.render_widget(widget, logs_events[0]);
    }

and this is what I get in response.

error[E0277]: the trait bound `&mut TuiLoggerWidget<'_>: Widget` is not satisfied
   --> cli/src/ui/terminal.rs:129:25
    |
129 |         f.render_widget(widget, logs_events[0]);
    |           ------------- ^^^^^^ the trait `Widget` is not implemented for `&mut TuiLoggerWidget<'_>`
    |           |
    |           required by a bound introduced by this call
    |
    = help: the trait `Widget` is implemented for `TuiLoggerWidget<'b>`
note: required by a bound in `Frame::<'a, B>::render_widget`
   --> .../.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.19.0/src/terminal.rs:99:12
    |
99  |         W: Widget,
    |            ^^^^^^ required by this bound in `Frame::<'a, B>::render_widget`

I've tried getting to the widget like so

f.render_widget(*widget, logs_events[0]);

but that gives me

129 |         f.render_widget(*widget, logs_events[0]);
    |                         ^^^^^^^ move occurs because `*widget` has type `TuiLoggerWidget<'_>`, which does not implement the `Copy` trait

and I've tried a few other things. none of which have rewarded me 😢

It's probably a very simple thing I'm missing, but it just escapes me at present.

Appreciate the help.

gin66 commented

Thanks for trying and based on your feedback, I have checked the code. Unfortunately the function state() has returned a different value than Self. The fixed version is v0.8.2 and uploaded to crates.io, too

You are a super ⭐

Thank you very much.

Sorry @gin66 , I'm back. Although I can now use the state method, I think it's being ignored.
My widgets have the following states:

        let lws = TuiWidgetState::new()
            .set_level_for_target("task_log", log::LevelFilter::Debug)
            .set_default_display_level(log::LevelFilter::Off);

and

        let ews = TuiWidgetState::new()
            .set_level_for_target("event_log", log::LevelFilter::Debug)
            .set_default_display_level(log::LevelFilter::Off);

which is added appropriately to each widget.
However, regardless of what I do, the output is identical in both of them, and is governed more by init_logger(LevelFilter::Info)?; than anything I do to the WidgetState.

Any idea's what I might be doing wrong?

gin66 commented

Perhaps a typo (lws/ews) in the .state() calls ?
I have extended the demo to demonstrate how/that it works.

This patch will do it.

diff --git a/src/lib.rs b/src/lib.rs
index c99be58..23e558b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1054,6 +1054,8 @@ impl<'b> Widget for TuiLoggerWidget<'b> {
                     if *level < evt.level {
                         continue;
                     }
+                } else {
+                    continue;
                 }
                 if state.focus_selected {

gin66 commented

hmmm. I think, that patch will ensure, that the widget does not display anything.

I've observed it to work. The statement in question is

                if let Some(level) = state.config.get(&evt.target) {
                    if *level < evt.level {
                        continue;
                    }
                }

and becomes

                if let Some(level) = state.config.get(&evt.target) {
                    if *level < evt.level {
                        continue;
                    }
                } else {
                    continue;
                }

that first if is checking if there is a level set for the target, and if there is, and the level is set lower than the events level, it ignores it. It doesn't account for it not being set at all.

Now that I've typed that, and re-read your comment, I think it will behave badly for the case where the state is not explicitly set, and there is no target?

The answer to that is a catagorical yes. It ignores all the logs.... Thinking...

This one works better.

diff --git a/src/lib.rs b/src/lib.rs
index c99be58..854a78f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1054,6 +1054,12 @@ impl<'b> Widget for TuiLoggerWidget<'b> {
                     if *level < evt.level {
                         continue;
                     }
+                } else {
+                    if let Some(level) = state.config.default_display_level {
+                        if level < evt.level {
+                            continue;
+                        }
+                    }
                 }
                 if state.focus_selected {
                     if let Some(target) = state.opt_selected_target.as_ref() {
gin66 commented

Thanks for the patch. I have applied it to v0.8.3