Colorize log output #97
11 changed files with 246 additions and 44 deletions
110
Cargo.lock
generated
110
Cargo.lock
generated
|
@ -38,6 +38,25 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi-parser"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcb2392079bf27198570d6af79ecbd9ec7d8f16d3ec6b60933922fdb66287127"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nom 4.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.69"
|
||||
|
@ -62,6 +81,18 @@ version = "0.7.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
"generic-array 0.13.3",
|
||||
"generic-array 0.14.6",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "associative-cache"
|
||||
version = "1.0.1"
|
||||
|
@ -191,7 +222,7 @@ version = "0.10.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -321,7 +352,7 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -614,7 +645,7 @@ version = "0.1.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.14.6",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
|
@ -807,6 +838,7 @@ dependencies = [
|
|||
name = "dtmm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi-parser",
|
||||
"bitflags",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
|
@ -874,6 +906,7 @@ dependencies = [
|
|||
name = "dtmt-shared"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"color-eyre",
|
||||
"serde",
|
||||
"steamlocate",
|
||||
|
@ -1272,6 +1305,24 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
|
@ -1279,7 +1330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"version_check 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1480,12 +1531,33 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
"generic-array 0.13.3",
|
||||
"hash32",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
|
@ -1609,7 +1681,7 @@ dependencies = [
|
|||
"serde",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
"version_check 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2023,6 +2095,16 @@ version = "1.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"version_check 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
@ -2576,7 +2658,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
"version_check 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2587,7 +2669,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
"version_check 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3091,6 +3173,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "steamid-ng"
|
||||
version = "1.0.0"
|
||||
|
@ -3634,7 +3722,7 @@ version = "2.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
"version_check 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3768,6 +3856,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
|
@ -31,3 +31,4 @@ lazy_static = "1.4.0"
|
|||
colors-transform = "0.2.11"
|
||||
usvg = "0.25.0"
|
||||
druid-widget-nursery = "0.1"
|
||||
ansi-parser = "0.8.0"
|
||||
|
|
|
@ -174,7 +174,7 @@ async fn handle_action(
|
|||
|
||||
async fn handle_log(
|
||||
event_sink: Arc<RwLock<ExtEventSink>>,
|
||||
log_queue: Arc<RwLock<UnboundedReceiver<String>>>,
|
||||
log_queue: Arc<RwLock<UnboundedReceiver<Vec<u8>>>>,
|
||||
) {
|
||||
while let Some(line) = log_queue.write().await.recv().await {
|
||||
let event_sink = event_sink.clone();
|
||||
|
@ -189,7 +189,7 @@ async fn handle_log(
|
|||
pub(crate) fn work_thread(
|
||||
event_sink: Arc<RwLock<ExtEventSink>>,
|
||||
action_queue: Arc<RwLock<UnboundedReceiver<AsyncAction>>>,
|
||||
log_queue: Arc<RwLock<UnboundedReceiver<String>>>,
|
||||
log_queue: Arc<RwLock<UnboundedReceiver<Vec<u8>>>>,
|
||||
) -> Result<()> {
|
||||
let rt = Runtime::new()?;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::ui::theme;
|
|||
mod controller;
|
||||
mod state;
|
||||
mod util {
|
||||
pub mod ansi;
|
||||
pub mod config;
|
||||
pub mod log;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||
use std::sync::Arc;
|
||||
|
||||
use druid::im::{HashMap, Vector};
|
||||
use druid::text::RichText;
|
||||
use druid::{Data, ImageBuf, Lens, WindowHandle, WindowId};
|
||||
use dtmt_shared::ModConfig;
|
||||
use nexusmods::Mod as NexusMod;
|
||||
|
@ -155,9 +156,7 @@ pub(crate) struct State {
|
|||
pub game_dir: Arc<PathBuf>,
|
||||
pub data_dir: Arc<PathBuf>,
|
||||
pub nexus_api_key: Arc<String>,
|
||||
|
||||
#[data(ignore)]
|
||||
pub log: Arc<String>,
|
||||
pub log: Vector<RichText>,
|
||||
// True, when the initial loading of configuration and mods is still in progress
|
||||
pub loading: bool,
|
||||
|
||||
|
@ -194,7 +193,7 @@ impl State {
|
|||
game_dir: Arc::new(PathBuf::new()),
|
||||
data_dir: Arc::new(PathBuf::new()),
|
||||
nexus_api_key: Arc::new(String::new()),
|
||||
log: Arc::new(String::new()),
|
||||
log: Vector::new(),
|
||||
windows: HashMap::new(),
|
||||
loading: true,
|
||||
}
|
||||
|
@ -223,9 +222,4 @@ impl State {
|
|||
pub fn can_move_mod_up(&self) -> bool {
|
||||
self.selected_mod_index.map(|i| i > 0).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn add_log_line(&mut self, line: String) {
|
||||
let log = Arc::make_mut(&mut self.log);
|
||||
log.push_str(&line);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use color_eyre::Report;
|
||||
use druid::im::Vector;
|
||||
use druid::{
|
||||
im::Vector, AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse,
|
||||
Target, WindowHandle, WindowId,
|
||||
AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, Target,
|
||||
WindowHandle, WindowId,
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use crate::util::ansi::ansi_to_rich_text;
|
||||
use crate::{ui::window, util::config::Config};
|
||||
|
||||
use super::{ModInfo, State};
|
||||
|
@ -32,7 +34,7 @@ pub(crate) const ACTION_ADD_MOD: Selector<FileInfo> = Selector::new("dtmm.action
|
|||
pub(crate) const ACTION_FINISH_ADD_MOD: Selector<SingleUse<Arc<ModInfo>>> =
|
||||
Selector::new("dtmm.action.finish-add-mod");
|
||||
|
||||
pub(crate) const ACTION_LOG: Selector<SingleUse<String>> = Selector::new("dtmm.action.log");
|
||||
pub(crate) const ACTION_LOG: Selector<SingleUse<Vec<u8>>> = Selector::new("dtmm.action.log");
|
||||
|
||||
pub(crate) const ACTION_START_SAVE_SETTINGS: Selector =
|
||||
Selector::new("dtmm.action.start-save-settings");
|
||||
|
@ -252,7 +254,8 @@ impl AppDelegate<State> for Delegate {
|
|||
.get(ACTION_LOG)
|
||||
.expect("command type matched but didn't contain the expected value");
|
||||
if let Some(line) = line.take() {
|
||||
state.add_log_line(line);
|
||||
let line = String::from_utf8_lossy(&line);
|
||||
state.log.push_back(ansi_to_rich_text(line.trim()));
|
||||
}
|
||||
Handled::Yes
|
||||
}
|
||||
|
|
|
@ -451,17 +451,18 @@ fn build_main() -> impl Widget<State> {
|
|||
}
|
||||
|
||||
fn build_log_view() -> impl Widget<State> {
|
||||
let font = FontDescriptor::new(FontFamily::MONOSPACE);
|
||||
let label = Label::raw()
|
||||
.with_font(font)
|
||||
.with_line_break_mode(LineBreaking::WordWrap)
|
||||
.lens(State::log)
|
||||
.padding(4.)
|
||||
.scroll()
|
||||
.vertical()
|
||||
.controller(AutoScrollController);
|
||||
let list = List::new(|| {
|
||||
Label::raw()
|
||||
.with_font(FontDescriptor::new(FontFamily::MONOSPACE))
|
||||
.with_line_break_mode(LineBreaking::WordWrap)
|
||||
})
|
||||
.lens(State::log)
|
||||
.padding(4.)
|
||||
.scroll()
|
||||
.vertical()
|
||||
.controller(AutoScrollController);
|
||||
|
||||
let inner = Border::new(label)
|
||||
let inner = Border::new(list)
|
||||
.with_color(theme::COLOR_FG2)
|
||||
.with_top_border(1.);
|
||||
|
||||
|
|
90
crates/dtmm/src/util/ansi.rs
Normal file
90
crates/dtmm/src/util/ansi.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use ansi_parser::{AnsiParser, AnsiSequence, Output};
|
||||
use druid::text::{RichText, RichTextBuilder};
|
||||
use druid::{Color, FontStyle, FontWeight};
|
||||
|
||||
use crate::ui::theme;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct TextState {
|
||||
color: Option<Color>,
|
||||
dim: bool,
|
||||
bold: bool,
|
||||
underline: bool,
|
||||
strikethrough: bool,
|
||||
italic: bool,
|
||||
}
|
||||
|
||||
pub fn ansi_to_rich_text(input: &str) -> RichText {
|
||||
let mut builder = RichTextBuilder::new();
|
||||
|
||||
let mut state = TextState::default();
|
||||
|
||||
for token in input.ansi_parse() {
|
||||
match token {
|
||||
Output::TextBlock(text) => {
|
||||
let mut attr = builder.push(text);
|
||||
attr.underline(state.underline);
|
||||
attr.strikethrough(state.strikethrough);
|
||||
|
||||
if state.bold {
|
||||
attr.weight(FontWeight::BOLD);
|
||||
}
|
||||
|
||||
if state.italic {
|
||||
attr.style(FontStyle::Italic);
|
||||
}
|
||||
|
||||
if let Some(color) = state.color {
|
||||
attr.text_color(color);
|
||||
}
|
||||
}
|
||||
Output::Escape(AnsiSequence::SetGraphicsMode(values)) => {
|
||||
for v in values {
|
||||
match v {
|
||||
0 => {
|
||||
state = Default::default();
|
||||
break;
|
||||
}
|
||||
1 => state.bold = true,
|
||||
2 => state.dim = true,
|
||||
3 => state.italic = true,
|
||||
4 => state.underline = true,
|
||||
9 => state.strikethrough = true,
|
||||
22 => {
|
||||
state.bold = false;
|
||||
state.dim = false;
|
||||
}
|
||||
23 => state.italic = false,
|
||||
24 => state.underline = false,
|
||||
29 => state.underline = false,
|
||||
30..=40 | 90..=100 => {
|
||||
let mut col = v - 30;
|
||||
if col > 9 {
|
||||
state.bold = true;
|
||||
col -= 60;
|
||||
}
|
||||
|
||||
state.color = match col {
|
||||
// This escape code is usually called 'black', but is actually used
|
||||
// as "foreground color", in regards to light themes.
|
||||
1 => Some(theme::COLOR_FG),
|
||||
2 => Some(theme::COLOR_RED_LIGHT),
|
||||
3 => Some(theme::COLOR_GREEN_LIGHT),
|
||||
4 => Some(theme::COLOR_YELLOW_LIGHT),
|
||||
5 => Some(theme::COLOR_BLUE_LIGHT),
|
||||
6 => Some(theme::COLOR_PURPLE_LIGHT),
|
||||
7 => Some(theme::COLOR_AQUA_LIGHT),
|
||||
9 => None,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Output::Escape(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
builder.build()
|
||||
}
|
|
@ -8,11 +8,11 @@ use tracing_subscriber::prelude::*;
|
|||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
pub struct ChannelWriter {
|
||||
tx: UnboundedSender<String>,
|
||||
tx: UnboundedSender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ChannelWriter {
|
||||
pub fn new(tx: UnboundedSender<String>) -> Self {
|
||||
pub fn new(tx: UnboundedSender<Vec<u8>>) -> Self {
|
||||
Self { tx }
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,9 @@ impl ChannelWriter {
|
|||
impl std::io::Write for ChannelWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let tx = self.tx.clone();
|
||||
let stripped = strip_ansi_escapes::strip(buf)?;
|
||||
let string = String::from_utf8_lossy(&stripped).to_string();
|
||||
|
||||
// The `send` errors when the receiving end has closed.
|
||||
// But there's not much we can do at that point, so we just ignore it.
|
||||
let _ = tx.send(string);
|
||||
let _ = tx.send(buf.to_vec());
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
@ -35,7 +32,7 @@ impl std::io::Write for ChannelWriter {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_tracing_subscriber(tx: UnboundedSender<String>) {
|
||||
pub fn create_tracing_subscriber(tx: UnboundedSender<Vec<u8>>) {
|
||||
let env_layer = if cfg!(debug_assertions) {
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
|
||||
} else {
|
||||
|
@ -50,8 +47,6 @@ pub fn create_tracing_subscriber(tx: UnboundedSender<String>) {
|
|||
};
|
||||
|
||||
let channel_layer = fmt::layer()
|
||||
// TODO: Re-enable and implement a formatter for the Druid widget
|
||||
.with_ansi(false)
|
||||
.event_format(dtmt_shared::Formatter)
|
||||
.fmt_fields(debug_fn(dtmt_shared::format_fields))
|
||||
.with_writer(move || ChannelWriter::new(tx.clone()))
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
color-eyre = "0.6.2"
|
||||
serde = "1.0.152"
|
||||
steamlocate = { path = "../../lib/steamlocate-rs", version = "*" }
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use std::fmt::Result;
|
||||
|
||||
use ansi_term::Color;
|
||||
use time::format_description::FormatItem;
|
||||
use time::macros::format_description;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::field::Field;
|
||||
use tracing::{Event, Metadata, Subscriber};
|
||||
use tracing::{Event, Level, Metadata, Subscriber};
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::filter::FilterFn;
|
||||
use tracing_subscriber::fmt::format::{debug_fn, Writer};
|
||||
|
@ -49,7 +50,28 @@ where
|
|||
let time = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
|
||||
let time = time.format(TIME_FORMAT).map_err(|_| std::fmt::Error)?;
|
||||
|
||||
write!(writer, "[{}] [{:>5}] ", time, meta.level())?;
|
||||
let level = meta.level();
|
||||
// Sadly, tracing's `Level` is a struct, not an enum, so we can't properly `match` it.
|
||||
let color = if *level == Level::TRACE {
|
||||
Color::Purple
|
||||
} else if *level == Level::DEBUG {
|
||||
Color::Blue
|
||||
} else if *level == Level::INFO {
|
||||
Color::Green
|
||||
} else if *level == Level::WARN {
|
||||
Color::Yellow
|
||||
} else if *level == Level::ERROR {
|
||||
Color::Red
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"[{}] [{:>5}] ",
|
||||
time,
|
||||
color.bold().paint(format!("{}", level))
|
||||
)?;
|
||||
|
||||
ctx.field_format().format_fields(writer.by_ref(), event)?;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue