Browse Source

Start new logging system

main
ThePerkinrex 4 years ago
parent
commit
d7073f1b96
  1. 417
      server/src/logger.rs
  2. 376
      server/src/logger/ansi.rs
  3. 0
      server/src/logger/ansi/color_message.rs

417
server/src/logger.rs

@ -1,53 +1,47 @@
use std::sync::mpsc;
use std::io::Write;
mod ansi;
use fern::Dispatch;
use crossterm::event::MouseEventKind;
use crossterm::{
cursor::{MoveTo, MoveToNextLine},
event::{Event, KeyCode, KeyModifiers},
queue,
style::Print,
terminal::{Clear, ClearType},
};
use crate::{allocator::{self, Allocator}, command::command_handler, server_properties::ServerProperties};
use fern::colors::{Color, ColoredLevelConfig};
pub type Stdout = mpsc::Sender<String>;
pub type Stdin<T> = mpsc::Receiver<T>;
pub struct Close {
close: mpsc::Sender<()>,
handle: tokio::task::JoinHandle<()>,
close_handler: Box<dyn Fn()>
}
impl Close {
pub async fn close(self) {
self.close.send(()).unwrap();
self.handle.await.unwrap();
(self.close_handler)();
}
}
mod color_message;
use color_message::print_line;
trait LoggingFrontend {
fn setup(
properties: &crate::server_properties::ServerProperties,
logger_filter: Dispatch
) -> anyhow::Result<(Close, Stdin<String>)> {
Self::setup_with_handler(properties, command_handler, logger_filter)
}
use crate::allocator::{self, Allocator};
use crate::command::command_handler;
fn setup_with_handler<F: Fn(String) -> String + Send + 'static>(
properties: &crate::server_properties::ServerProperties,
handler: F,
logger_filter: Dispatch
) -> anyhow::Result<(Close, Stdin<String>)>;
// TODO: Cleanup
fn cleanup(&self);
pub fn setup(
properties: &crate::server_properties::ServerProperties,
) -> Result<(Close, Stdin<String>), fern::InitError> {
setup_with_handler(properties, command_handler)
fn name() -> &'static str;
}
pub fn setup_with_handler<F: Fn(String) -> String + Send + 'static>(
properties: &crate::server_properties::ServerProperties,
handler: F,
) -> Result<(Close, Stdin<String>), fern::InitError> {
let (stdout, stdin, close, join_handle) = TerminalHandler::new(handler, properties.use_colors);
// let simple_formatter = |out: fern::FormatCallback, message: _, record: &log::Record| {
// out.finish(format_args!(
// "{}[{}][{}] {}",
// chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
// record.level(),
// record.target(),
// message,
// ))
// };
let stdout_logger = if properties.use_colors {
colored_formatter(fern::Dispatch::new())
} else {
simple_formatter(fern::Dispatch::new())
}
.chain(stdout);
pub fn setup(properties: &ServerProperties) -> anyhow::Result<(Close, Stdin<String>)> {
let file = simple_formatter(fern::Dispatch::new()).chain(
std::fs::OpenOptions::new()
.write(true)
@ -56,23 +50,14 @@ pub fn setup_with_handler<F: Fn(String) -> String + Send + 'static>(
.open("output.log")?,
);
fern::Dispatch::new()
.level(log::LevelFilter::Debug)
.level_for("h2", log::LevelFilter::Info)
.level_for("tokio::codec", log::LevelFilter::Info)
.level_for("sqlx::query", log::LevelFilter::Info)
.chain(stdout_logger)
.chain(file)
.apply()?;
log::info!("Saving output to output.log");
// std::process::exit(1);
Ok((
Close {
close,
handle: join_handle,
},
stdin,
))
let d = fern::Dispatch::new()
.level(log::LevelFilter::Debug)
.level_for("h2", log::LevelFilter::Info)
.level_for("tokio::codec", log::LevelFilter::Info)
.level_for("sqlx::query", log::LevelFilter::Info)
.chain(file);
// TODO select logging frontend
ansi::ANSIFrontend::setup(properties, d)
}
fn simple_formatter(d: fern::Dispatch) -> fern::Dispatch {
@ -89,321 +74,3 @@ fn simple_formatter(d: fern::Dispatch) -> fern::Dispatch {
},
)
}
fn colored_formatter(d: fern::Dispatch) -> fern::Dispatch {
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
// we actually don't need to specify the color for debug and info, they are white by default
.info(Color::White)
.debug(Color::BrightBlack)
// depending on the terminals color scheme, this is the same as the background color
.trace(Color::BrightBlack);
let colors_level = colors_line.info(Color::Green).debug(Color::BrightBlack);
d.format(
move |out: fern::FormatCallback, message: _, record: &log::Record| {
out.finish(format_args!(
"{colors_line}{}[{}{colors_line}][{:>9}][{}] {}\x1B[0m",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
colors_level.clone().color(record.level()),
allocator::as_string(Allocator::allocated()),
record.target(),
message,
colors_line = format!(
"\x1B[{}m",
colors_line.clone().get_color(&record.level()).to_fg_str()
),
// colors_level = colors_level.clone().get_color(&record.level()).to_fg_str(),
))
},
)
}
pub type Stdout = mpsc::Sender<String>;
pub type Stdin<T> = mpsc::Receiver<T>;
struct TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
stdout: mpsc::Receiver<String>,
stdin: mpsc::Sender<T>,
close: mpsc::Receiver<()>,
handler: F,
lines: LimitedVec<String>,
command: String,
use_colors: bool,
}
impl<T, F> TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
#[allow(clippy::new_ret_no_self)]
fn new(
handler: F,
use_colors: bool,
) -> (
Stdout,
Stdin<T>,
mpsc::Sender<()>,
tokio::task::JoinHandle<()>,
) {
crossterm::terminal::enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
queue!(stdout, crossterm::event::EnableMouseCapture).unwrap();
let (stdout_send, stdout_recv) = mpsc::channel();
let (stdin_send, stdin_recv) = mpsc::channel();
let (close_send, close_recv) = mpsc::channel();
let join_handle = tokio::task::spawn_blocking(move || {
let mut s = Self {
stdout: stdout_recv,
stdin: stdin_send,
close: close_recv,
handler,
lines: LimitedVec::new(crossterm::terminal::size().unwrap().1 as usize - 1),
command: String::new(),
use_colors,
};
loop {
if s.iterate() {
break;
}
}
cleanup();
});
std::panic::set_hook(Box::new(|info| {
let thread = std::thread::current();
let name = thread.name().unwrap_or("<unnamed>");
log::error!("thread '{}' {}", name, info);
log::info!("Press any key to exit");
loop {
match crossterm::event::read() {
// Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1,
Ok(Event::Key(_)) => break,
Ok(_) => (),
Err(e) => {
log::error!(
"Errored while panicking, this is unrecoverable, exiting: {}",
e
);
break;
}
}
}
cleanup();
std::process::exit(0)
}));
(stdout_send, stdin_recv, close_send, join_handle)
}
fn iterate(&mut self) -> bool {
let mut updated = false;
match self.close.try_recv() {
Err(mpsc::TryRecvError::Empty) => (),
_ => return true,
}
while is_event_available() {
updated = true;
match crossterm::event::read().unwrap() {
Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1,
Event::Key(k) => match (k.code, k.modifiers) {
(KeyCode::Char('c'), KeyModifiers::CONTROL) => {
cleanup();
std::process::exit(0)
}
(k, _m) => match k {
KeyCode::Char(c) => self.command.push(c),
KeyCode::Backspace => {
self.command.pop();
}
KeyCode::Enter => {
log::info!(target: "command", " >> {}", self.command);
match self.stdin.send((self.handler)(self.command.clone())) {
Ok(_) => (),
Err(e) => log::error!("{}", e),
};
self.command = String::new();
}
KeyCode::Esc => {
self.lines.scroll_to_bottom();
}
_ => (),
},
},
Event::Mouse(m) => match m.kind {
MouseEventKind::ScrollUp => self.lines.scroll_up(),
MouseEventKind::ScrollDown => self.lines.scroll_down(),
_ => {
updated = false;
}
},
}
}
while let Some(x) = match self.stdout.try_recv() {
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => return true,
Ok(v) => Some(v),
} {
for line in x.split('\n') {
if !line.is_empty() {
self.lines.push(line.trim_end().to_string());
}
}
updated = true;
}
let mut stdout = std::io::stdout();
// println!("Got stdout");
if updated {
queue!(stdout, Clear(ClearType::All), MoveTo(0, 0)).unwrap();
let mut i = 0;
let size = crossterm::terminal::size().unwrap();
for v in self.lines.iter(self.use_colors) {
print_line(&v, self.use_colors, &mut stdout);
queue!(stdout, MoveToNextLine(1)).unwrap();
i += 1;
if i >= size.1 as usize - 1 {
break;
}
// println!("{}", v);
}
queue!(
stdout,
MoveTo(0, size.1),
// Clear(ClearType::CurrentLine),
Print(&format!(" >> {}", self.command))
)
.unwrap();
stdout.flush().unwrap();
}
false
}
}
fn cleanup() {
log::info!(target: "cleanup", "Exiting server");
let mut stdout = std::io::stdout();
queue!(
stdout,
Clear(ClearType::All),
MoveTo(0, 0),
crossterm::event::DisableMouseCapture
)
.unwrap();
crossterm::terminal::disable_raw_mode().unwrap();
stdout.flush().unwrap();
println!("Exiting server, output has been saved to output.log");
}
impl<T, F> Drop for TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
fn drop(&mut self) {
queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap();
}
}
fn is_event_available() -> bool {
crossterm::event::poll(std::time::Duration::from_millis(10))
.expect("Error polling for terminal events")
}
pub struct Close {
close: mpsc::Sender<()>,
handle: tokio::task::JoinHandle<()>,
}
impl Close {
pub async fn close(self) {
self.close.send(()).unwrap();
self.handle.await.unwrap();
queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap();
}
}
struct LimitedVec<T> {
inner: Vec<T>,
size: usize,
pos: usize,
}
impl<T> LimitedVec<T> {
fn new(size: usize) -> Self {
Self {
inner: Vec::with_capacity(size),
size,
pos: 0,
}
}
fn push(&mut self, v: T) {
// while self.inner.len() >= self.size {
// self.inner.remove(0);
// }
if self.pos != 0 {
self.pos += 1;
}
self.inner.push(v);
// assert!(self.inner.len() <= self.size)
}
fn iter<'a>(&'a self, use_colors: bool) -> Box<dyn Iterator<Item = String> + 'a>
where
T: std::fmt::Display,
{
let start = if self.inner.len() < self.size {
0
} else {
self.inner
.len()
.checked_sub(self.size)
.and_then(|x| x.checked_sub(self.pos))
.unwrap_or(0)
};
let mut end = self.inner.len().saturating_sub(self.pos);
if end < self.inner.len() {
end = self.inner.len()
}
let iter = self.inner[start..end].iter();
let map = iter.enumerate().map(move |(i, x)| {
format!(
"{}{}",
if use_colors {
format!(
"\x1B[{}m{:>5}\x1B[0m ",
fern::colors::Color::BrightBlack.to_fg_str(),
start + i
)
} else {
String::new()
},
x
)
});
Box::new(map)
}
fn scroll_up(&mut self) {
if self.pos + self.size < self.inner.len() {
// log::info!("SCROLLING UP");
self.pos += 1;
}
}
fn scroll_down(&mut self) {
if self.pos > 0 {
// log::info!("SCROLLING DOWN");
self.pos -= 1;
}
}
fn scroll_to_bottom(&mut self) {
self.pos = 0;
}
}

376
server/src/logger/ansi.rs

@ -0,0 +1,376 @@
use super::{Close, LoggingFrontend, Stdin, Stdout};
use std::sync::mpsc;
use std::io::Write;
use crossterm::event::MouseEventKind;
use crossterm::{
cursor::{MoveTo, MoveToNextLine},
event::{Event, KeyCode, KeyModifiers},
queue,
style::Print,
terminal::{Clear, ClearType},
};
use fern::Dispatch;
use fern::colors::{Color, ColoredLevelConfig};
mod color_message;
use color_message::print_line;
use crate::allocator::{self, Allocator};
use crate::logger::simple_formatter;
pub(super) struct ANSIFrontend;
impl LoggingFrontend for ANSIFrontend {
fn setup_with_handler<F: Fn(String) -> String + Send + 'static>(
properties: &crate::server_properties::ServerProperties,
handler: F,
logger_filter: Dispatch
) -> anyhow::Result<(Close, Stdin<String>)> {
let (stdout, stdin, close, join_handle) =
TerminalHandler::new(handler, properties.use_colors);
// let simple_formatter = |out: fern::FormatCallback, message: _, record: &log::Record| {
// out.finish(format_args!(
// "{}[{}][{}] {}",
// chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
// record.level(),
// record.target(),
// message,
// ))
// };
let stdout_logger = if properties.use_colors {
colored_formatter(fern::Dispatch::new())
} else {
simple_formatter(fern::Dispatch::new())
}
.chain(stdout);
logger_filter
.chain(stdout_logger)
.apply()?;
log::info!("Saving output to output.log");
// std::process::exit(1);
Ok((
Close {
close,
handle: join_handle,
close_handler: Box::new(|| queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap())
},
stdin,
))
}
fn cleanup(&self) {
cleanup()
}
fn name() -> &'static str {
"ANSI"
}
}
fn cleanup() {
log::info!(target: "cleanup", "Exiting server");
let mut stdout = std::io::stdout();
queue!(
stdout,
Clear(ClearType::All),
MoveTo(0, 0),
crossterm::event::DisableMouseCapture
)
.unwrap();
crossterm::terminal::disable_raw_mode().unwrap();
stdout.flush().unwrap();
println!("Exiting server, output has been saved to output.log");
}
fn colored_formatter(d: fern::Dispatch) -> fern::Dispatch {
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
// we actually don't need to specify the color for debug and info, they are white by default
.info(Color::White)
.debug(Color::BrightBlack)
// depending on the terminals color scheme, this is the same as the background color
.trace(Color::BrightBlack);
let colors_level = colors_line.info(Color::Green).debug(Color::BrightBlack);
d.format(
move |out: fern::FormatCallback, message: _, record: &log::Record| {
out.finish(format_args!(
"{colors_line}{}[{}{colors_line}][{:>9}][{}] {}\x1B[0m",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
colors_level.clone().color(record.level()),
allocator::as_string(Allocator::allocated()),
record.target(),
message,
colors_line = format!(
"\x1B[{}m",
colors_line.clone().get_color(&record.level()).to_fg_str()
),
// colors_level = colors_level.clone().get_color(&record.level()).to_fg_str(),
))
},
)
}
struct TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
stdout: mpsc::Receiver<String>,
stdin: mpsc::Sender<T>,
close: mpsc::Receiver<()>,
handler: F,
lines: LimitedVec<String>,
command: String,
use_colors: bool,
}
impl<T, F> TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
#[allow(clippy::new_ret_no_self)]
fn new(
handler: F,
use_colors: bool,
) -> (
Stdout,
Stdin<T>,
mpsc::Sender<()>,
tokio::task::JoinHandle<()>,
) {
crossterm::terminal::enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
queue!(stdout, crossterm::event::EnableMouseCapture).unwrap();
let (stdout_send, stdout_recv) = mpsc::channel();
let (stdin_send, stdin_recv) = mpsc::channel();
let (close_send, close_recv) = mpsc::channel();
let join_handle = tokio::task::spawn_blocking(move || {
let mut s = Self {
stdout: stdout_recv,
stdin: stdin_send,
close: close_recv,
handler,
lines: LimitedVec::new(crossterm::terminal::size().unwrap().1 as usize - 1),
command: String::new(),
use_colors,
};
loop {
if s.iterate() {
break;
}
}
cleanup();
});
std::panic::set_hook(Box::new(|info| {
let thread = std::thread::current();
let name = thread.name().unwrap_or("<unnamed>");
log::error!("thread '{}' {}", name, info);
log::info!("Press any key to exit");
loop {
match crossterm::event::read() {
// Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1,
Ok(Event::Key(_)) => break,
Ok(_) => (),
Err(e) => {
log::error!(
"Errored while panicking, this is unrecoverable, exiting: {}",
e
);
break;
}
}
}
cleanup();
std::process::exit(0)
}));
(stdout_send, stdin_recv, close_send, join_handle)
}
fn iterate(&mut self) -> bool {
let mut updated = false;
match self.close.try_recv() {
Err(mpsc::TryRecvError::Empty) => (),
_ => return true,
}
while is_event_available() {
updated = true;
match crossterm::event::read().unwrap() {
Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1,
Event::Key(k) => match (k.code, k.modifiers) {
(KeyCode::Char('c'), KeyModifiers::CONTROL) => {
cleanup();
std::process::exit(0)
}
(k, _m) => match k {
KeyCode::Char(c) => self.command.push(c),
KeyCode::Backspace => {
self.command.pop();
}
KeyCode::Enter => {
log::info!(target: "command", " >> {}", self.command);
match self.stdin.send((self.handler)(self.command.clone())) {
Ok(_) => (),
Err(e) => log::error!("{}", e),
};
self.command = String::new();
}
KeyCode::Esc => {
self.lines.scroll_to_bottom();
}
_ => (),
},
},
Event::Mouse(m) => match m.kind {
MouseEventKind::ScrollUp => self.lines.scroll_up(),
MouseEventKind::ScrollDown => self.lines.scroll_down(),
_ => {
updated = false;
}
},
}
}
while let Some(x) = match self.stdout.try_recv() {
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => return true,
Ok(v) => Some(v),
} {
for line in x.split('\n') {
if !line.is_empty() {
self.lines.push(line.trim_end().to_string());
}
}
updated = true;
}
let mut stdout = std::io::stdout();
// println!("Got stdout");
if updated {
queue!(stdout, Clear(ClearType::All), MoveTo(0, 0)).unwrap();
let mut i = 0;
let size = crossterm::terminal::size().unwrap();
for v in self.lines.iter(self.use_colors) {
print_line(&v, self.use_colors, &mut stdout);
queue!(stdout, MoveToNextLine(1)).unwrap();
i += 1;
if i >= size.1 as usize - 1 {
break;
}
// println!("{}", v);
}
queue!(
stdout,
MoveTo(0, size.1),
// Clear(ClearType::CurrentLine),
Print(&format!(" >> {}", self.command))
)
.unwrap();
stdout.flush().unwrap();
}
false
}
}
impl<T, F> Drop for TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
fn drop(&mut self) {
queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap();
}
}
fn is_event_available() -> bool {
crossterm::event::poll(std::time::Duration::from_millis(10))
.expect("Error polling for terminal events")
}
struct LimitedVec<T> {
inner: Vec<T>,
size: usize,
pos: usize,
}
impl<T> LimitedVec<T> {
fn new(size: usize) -> Self {
Self {
inner: Vec::with_capacity(size),
size,
pos: 0,
}
}
fn push(&mut self, v: T) {
// while self.inner.len() >= self.size {
// self.inner.remove(0);
// }
if self.pos != 0 {
self.pos += 1;
}
self.inner.push(v);
// assert!(self.inner.len() <= self.size)
}
fn iter<'a>(&'a self, use_colors: bool) -> Box<dyn Iterator<Item = String> + 'a>
where
T: std::fmt::Display,
{
let start = if self.inner.len() < self.size {
0
} else {
self.inner
.len()
.checked_sub(self.size)
.and_then(|x| x.checked_sub(self.pos))
.unwrap_or(0)
};
let mut end = self.inner.len().saturating_sub(self.pos);
if end < self.inner.len() {
end = self.inner.len()
}
let iter = self.inner[start..end].iter();
let map = iter.enumerate().map(move |(i, x)| {
format!(
"{}{}",
if use_colors {
format!(
"\x1B[{}m{:>5}\x1B[0m ",
fern::colors::Color::BrightBlack.to_fg_str(),
start + i
)
} else {
String::new()
},
x
)
});
Box::new(map)
}
fn scroll_up(&mut self) {
if self.pos + self.size < self.inner.len() {
// log::info!("SCROLLING UP");
self.pos += 1;
}
}
fn scroll_down(&mut self) {
if self.pos > 0 {
// log::info!("SCROLLING DOWN");
self.pos -= 1;
}
}
fn scroll_to_bottom(&mut self) {
self.pos = 0;
}
}

0
server/src/logger/color_message.rs → server/src/logger/ansi/color_message.rs

Loading…
Cancel
Save