Решение на Logging от Даниела Русева

Обратно към всички решения

Към профила на Даниела Русева

Резултати

  • 19 точки от тестове
  • 0 бонус точки
  • 19 точки общо
  • 14 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::time::Instant;
use std::io;
use std::io::Write;
use std::rc::Rc;
use std::cell::RefCell;
pub trait Logger {
fn push(&mut self, time: Instant, text: &str);
fn log(&mut self, text: &str) {
self.push(Instant::now(), text);
}
fn try_flush(&mut self) -> io::Result<()>;
fn flush(&mut self);
}
#[derive(Eq, Ord, PartialEq, PartialOrd)]
struct Event {
time: Instant,
text: String,
}

Харесва ми създаването на отделен тип за Event-и -- и аз така направих в моето решение :). Нещо, което можеше да направиш, е да имплементираш PartialOrd експлицитно, и там да сложиш сравнението им по time. Така щеше да можеш да ползваш sort_unstable вместо sort_unstable_by. Въпрос на вкус, донякъде.

impl Event {
fn new(time: Instant, text: String) -> Self { Self {time, text} }
}
pub struct BufferedLogger<W: Write> {
out: Rc<RefCell<W>>,
buffer: Rc<RefCell<Vec<Event>>>,
buffer_size: usize,
}
impl<W: Write> BufferedLogger<W> {
pub fn new(out: W, buffer_size: usize) -> Self {
Self {
out: Rc::new(RefCell::new(out)),
buffer: Rc::new(RefCell::new(Vec::with_capacity(buffer_size))),
buffer_size
}
}
pub fn buffered_entries(&self) -> Vec<String> {
self.buffer.borrow_mut().sort_unstable_by(|a, b| a.time.cmp(&b.time));
self.buffer.borrow_mut().iter().map(|e| e.text.clone()).collect()
}
}
impl<W: Write> Clone for BufferedLogger<W> {
fn clone(&self) -> Self {
Self {
out: Rc::clone(&self.out),
buffer: Rc::clone(&self.buffer),
buffer_size: self.buffer_size
}
}
}
impl<W: Write> Logger for BufferedLogger<W> {
fn push(&mut self, time: Instant, text: &str) {
self.buffer.borrow_mut().push(Event::new(time, text.to_string()));
if self.buffer.borrow_mut().len() == self.buffer_size {
self.flush();
}
}
fn try_flush(&mut self) -> io::Result<()> {
let mut buffer: Vec<String> = self.buffered_entries();
for s in &mut buffer {
if !s.ends_with("\n") {
s.push('\n');
}
}
self.out.borrow_mut().write(buffer.concat().as_bytes())?;
self.buffer.borrow_mut().clear();
Ok(())
}
fn flush(&mut self){
let mut buffer: Vec<String> = self.buffered_entries();
for s in &mut buffer {
if !s.ends_with("\n") {
s.push('\n');
}
}
if let Err(e) = self.out.borrow_mut().write(buffer.concat().as_bytes()) {
eprintln!("Error while writing to file: {}", e);
}
self.buffer.borrow_mut().clear();

Тук можеше да reuse-неш горния метод, но вероятно не си го направила с цел да не повтаряш clear-а. Можеше да го сложиш условно някак.

Друго нещо, което би могла да ползваш вместо това, е drain: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain. Но не съм го пробвал. Трето нещо, което можеш да пробваш (което аз направих), е вместо вектор да използвам BinaryHeap, който автоматично се поддържа в правилния ред.

}
}
pub struct MultiLogger {
loggers: Vec<Box<dyn Logger>>,
}
impl MultiLogger {
pub fn new() -> Self {
Self { loggers: Vec::new() }
}
pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
self.loggers.push(Box::new(logger));
}
}
impl Logger for MultiLogger {
fn push(&mut self, time: Instant, text: &str) {
for l in &mut self.loggers { l.push(time, text); }
}
fn try_flush(&mut self) -> io::Result<()> {
for l in &mut self.loggers { l.try_flush()?; }
Ok(())
}
fn flush(&mut self) {
for l in &mut self.loggers { l.flush(); }
}
}
pub struct ScopedLogger<L: Logger> {
tag: String,
base_logger: L,
}
impl<L: Logger> ScopedLogger<L> {
pub fn new(tag: &str, base_logger: L) -> Self {
Self { tag: tag.to_string(), base_logger: base_logger }
}
}
impl<L: Logger> Logger for ScopedLogger<L> {
fn push(&mut self, time: Instant, text: &str) {
self.base_logger.push(time, &format!("[{}] {}", self.tag, text));
}
fn try_flush(&mut self) -> io::Result<()> {
self.base_logger.try_flush()?;
Ok(())
}
fn flush(&mut self) { self.base_logger.flush(); }
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20190123-22631-1s61bpx/solution)
    Finished dev [unoptimized + debuginfo] target(s) in 5.58s
     Running target/debug/deps/solution-2e785d603b538f71

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/solution_test-29808948fb50ed3a

running 15 tests
test solution_test::test_automatic_flushing_when_buffer_limit_is_reached ... ok
test solution_test::test_automatic_flushing_when_zero_buffer_limit ... FAILED
test solution_test::test_basic_log ... ok
test solution_test::test_basic_push ... ok
test solution_test::test_cloning_a_logger_shares_a_buffer ... ok
test solution_test::test_cloning_a_logger_shares_their_io ... ok
test solution_test::test_erroring_io ... ok
test solution_test::test_flushing_the_buffer ... ok
test solution_test::test_logger_combinations ... ok
test solution_test::test_multilogger_logs_and_flushes_when_needed ... ok
test solution_test::test_multilogger_logs_to_several_ios ... ok
test solution_test::test_reordering_logs_in_buffer ... ok
test solution_test::test_reordering_logs_in_io ... ok
test solution_test::test_scoped_logger ... ok
test solution_test::test_scoped_logger_with_a_string_tag ... ok

failures:

---- solution_test::test_automatic_flushing_when_zero_buffer_limit stdout ----
thread 'solution_test::test_automatic_flushing_when_zero_buffer_limit' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`', tests/solution_test.rs:210:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    solution_test::test_automatic_flushing_when_zero_buffer_limit

test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--test solution_test'

История (1 версия и 3 коментара)

Даниела обнови решението на 18.12.2018 02:39 (преди почти 2 години)

+use std::time::Instant;
+use std::io;
+use std::io::Write;
+use std::rc::Rc;
+use std::cell::RefCell;
+
+pub trait Logger {
+ fn push(&mut self, time: Instant, text: &str);
+
+ fn log(&mut self, text: &str) {
+ self.push(Instant::now(), text);
+ }
+
+ fn try_flush(&mut self) -> io::Result<()>;
+
+ fn flush(&mut self);
+}
+
+#[derive(Eq, Ord, PartialEq, PartialOrd)]
+struct Event {
+ time: Instant,
+ text: String,
+}

Харесва ми създаването на отделен тип за Event-и -- и аз така направих в моето решение :). Нещо, което можеше да направиш, е да имплементираш PartialOrd експлицитно, и там да сложиш сравнението им по time. Така щеше да можеш да ползваш sort_unstable вместо sort_unstable_by. Въпрос на вкус, донякъде.

+
+impl Event {
+ fn new(time: Instant, text: String) -> Self { Self {time, text} }
+}
+
+pub struct BufferedLogger<W: Write> {
+ out: Rc<RefCell<W>>,
+ buffer: Rc<RefCell<Vec<Event>>>,
+ buffer_size: usize,
+}
+
+impl<W: Write> BufferedLogger<W> {
+ pub fn new(out: W, buffer_size: usize) -> Self {
+ Self {
+ out: Rc::new(RefCell::new(out)),
+ buffer: Rc::new(RefCell::new(Vec::with_capacity(buffer_size))),
+ buffer_size
+ }
+ }
+
+ pub fn buffered_entries(&self) -> Vec<String> {
+ self.buffer.borrow_mut().sort_unstable_by(|a, b| a.time.cmp(&b.time));
+ self.buffer.borrow_mut().iter().map(|e| e.text.clone()).collect()
+ }
+}
+
+impl<W: Write> Clone for BufferedLogger<W> {
+ fn clone(&self) -> Self {
+ Self {
+ out: Rc::clone(&self.out),
+ buffer: Rc::clone(&self.buffer),
+ buffer_size: self.buffer_size
+ }
+ }
+}
+
+impl<W: Write> Logger for BufferedLogger<W> {
+ fn push(&mut self, time: Instant, text: &str) {
+ self.buffer.borrow_mut().push(Event::new(time, text.to_string()));
+
+ if self.buffer.borrow_mut().len() == self.buffer_size {
+ self.flush();
+ }
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ let mut buffer: Vec<String> = self.buffered_entries();
+ for s in &mut buffer {
+ if !s.ends_with("\n") {
+ s.push('\n');
+ }
+ }
+ self.out.borrow_mut().write(buffer.concat().as_bytes())?;
+ self.buffer.borrow_mut().clear();
+ Ok(())
+ }
+
+ fn flush(&mut self){
+ let mut buffer: Vec<String> = self.buffered_entries();
+ for s in &mut buffer {
+ if !s.ends_with("\n") {
+ s.push('\n');
+ }
+ }
+ if let Err(e) = self.out.borrow_mut().write(buffer.concat().as_bytes()) {
+ eprintln!("Error while writing to file: {}", e);
+ }
+ self.buffer.borrow_mut().clear();

Тук можеше да reuse-неш горния метод, но вероятно не си го направила с цел да не повтаряш clear-а. Можеше да го сложиш условно някак.

Друго нещо, което би могла да ползваш вместо това, е drain: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain. Но не съм го пробвал. Трето нещо, което можеш да пробваш (което аз направих), е вместо вектор да използвам BinaryHeap, който автоматично се поддържа в правилния ред.

+ }
+}
+
+pub struct MultiLogger {
+ loggers: Vec<Box<dyn Logger>>,
+}
+
+impl MultiLogger {
+ pub fn new() -> Self {
+ Self { loggers: Vec::new() }
+ }
+
+ pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
+ self.loggers.push(Box::new(logger));
+ }
+}
+
+impl Logger for MultiLogger {
+ fn push(&mut self, time: Instant, text: &str) {
+ for l in &mut self.loggers { l.push(time, text); }
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ for l in &mut self.loggers { l.try_flush()?; }
+ Ok(())
+ }
+
+ fn flush(&mut self) {
+ for l in &mut self.loggers { l.flush(); }
+ }
+}
+
+pub struct ScopedLogger<L: Logger> {
+ tag: String,
+ base_logger: L,
+}
+
+impl<L: Logger> ScopedLogger<L> {
+ pub fn new(tag: &str, base_logger: L) -> Self {
+ Self { tag: tag.to_string(), base_logger: base_logger }
+ }
+}
+
+impl<L: Logger> Logger for ScopedLogger<L> {
+ fn push(&mut self, time: Instant, text: &str) {
+ self.base_logger.push(time, &format!("[{}] {}", self.tag, text));
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ self.base_logger.try_flush()?;
+ Ok(())
+ }
+
+ fn flush(&mut self) { self.base_logger.flush(); }
+}