Решение на Logging от Християн Захариев

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

Към профила на Християн Захариев

Резултати

  • 12 точки от тестове
  • 0 бонус точки
  • 12 точки общо
  • 9 успешни тест(а)
  • 6 неуспешни тест(а)

Код

use std::time::Instant;
use std::io;
use std::io::Write;
use std::rc::Rc;
use std::cell::RefCell;
use std::time::Duration;
pub trait Logger {
fn push(&mut self, time: Instant, text: &str);
fn log(&mut self, text: &str);
fn try_flush(&mut self) -> io::Result<()>;
fn flush(&mut self);
}
pub struct BufferedLogger<W: Write> {
out: Rc<RefCell<W>>,
values: Rc<RefCell<Vec<(String, Instant)>>>,
size: usize
}
const OPENING_BRACKET: &str = "[";
const CLOSING_BRACKET: &str = "] ";
impl<W: Write> BufferedLogger<W> {
pub fn new(out: W, buffer_size: usize) -> Self {
BufferedLogger { out: Rc::new(RefCell::new(out)), values: Rc::new(RefCell::new(vec![])), size: buffer_size}
}
pub fn buffered_entries(&self) -> Vec<String> {
let mut buffer = self.values.borrow_mut().clone();
buffer.sort_by_key(|k| k.1);
buffer.into_iter().map(|m| m.0).collect()
}
}
/// Вижте по-долу за бележки за клонирането
impl<W: Write> Clone for BufferedLogger<W> {
fn clone(&self) -> Self {
BufferedLogger { out: Rc::clone(&self.out), values: Rc::clone(&self.values), size: self.size }
}
}
impl<W: Write> Logger for BufferedLogger<W> {
fn push(&mut self, time: Instant, text: &str) {
if self.values.borrow_mut().len() + 1 == self.size {
self.flush()
} else {
self.values.borrow_mut().push((text.to_owned(), time))
}

Това "гълта" стойности. Ако влезеш в клона, в който flush-ваш, не влизаш в клона, в който добавяш нова стойност. Идеята беше да си логваш неща нормално, и в случай, че се мине някаква граница, след това да се flush-не. Няма логика push-а да е в else-а в тази ситуация.

}
fn log(&mut self, text: &str) {
self.push(Instant::now(), text)
}
fn try_flush(&mut self) -> io::Result<()> {
Ok(())
}

Все пак искахме try_flush да работи коректно. Т.е., ако има грешка при писане, да засече тази грешка.

Спокойно можеше да имплементираш flush в самия trait, така:

fn flush(&mut self) {
    if let Err(e) = self.try_flush() {
        eprintln!("Logger error: {}", e);
    }
}

Оттам нататък, в try_flush можеше да сложиш долната имплементация, и вместо expect, да върнеш грешка. Метода expect ще panic-не, а не търсехме това, имаме обяснение за "Error handling" в условието.

fn flush(&mut self) {
for entity in self.buffered_entries() {
self.out.borrow_mut().write(entity.as_bytes()).expect("Couldn't write :(");
self.out.borrow_mut().write(b"\n").expect("Couldn't write :(");
}
self.values = Rc::new(RefCell::new(vec![]))

Това не работи както искахме в условието. Когато кажеш "values вече е нов Rc", този нов Rc няма никаква връзка с предния. Предишни клонинги не споделят тази стойност, и съответно ако клонираш logger-а и викнеш flush на единия от клонингите, той вече е несвързан с буфера на останалите.

Можеше да викнеш self.values.borrow_mut().clear(), например.

}
}
pub struct MultiLogger {
logger: Vec<Box<dyn Logger>>
}
impl MultiLogger {
pub fn new() -> Self {
return MultiLogger { logger: vec![]}
}
pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
self.logger.push(Box::from(logger));
}
}
impl Logger for MultiLogger {
fn push(&mut self, time: Instant, text: &str) {
for mut log in self.logger.iter_mut() {
log.push(time, text);
}
}
fn log(&mut self, text: &str) {
for mut log in self.logger.iter_mut() {
log.log(text);
}
}
fn try_flush(&mut self) -> io::Result<()> {
Ok(())
}
fn flush(&mut self) {
for mut log in self.logger.iter_mut() {
log.flush()
}
}
}
pub struct ScopedLogger<L: Logger> {
tag: String,
base_logger: Box<L>
}
impl<L: Logger> ScopedLogger<L> {
pub fn new(tag: &str, base_logger: L) -> Self {
return ScopedLogger { tag: String::from(tag), base_logger: Box::new(base_logger) }
}
}
impl<L: Logger> Logger for ScopedLogger<L> {
fn push(&mut self, time: Instant, text: &str) {
let text = OPENING_BRACKET.to_string() + self.tag.as_str() + CLOSING_BRACKET + text;
self.base_logger.as_mut().push(time, text.as_str());
}
fn log(&mut self, text: &str) {
let text = OPENING_BRACKET.to_string() + self.tag.as_str() + &CLOSING_BRACKET + text;
self.base_logger.as_mut().log(text.as_str())
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
self.base_logger.as_mut().flush();
}
}
fn main() {
let mut buffered_logger = BufferedLogger::new(io::stdout(), 100);
let now = Instant::now();
buffered_logger.push(now + Duration::from_millis(2), "Test2");
buffered_logger.push(now + Duration::from_millis(1), "Test1");
assert_eq!(buffered_logger.buffered_entries(), vec!["Test1", "Test2"]);
let mut wtf = buffered_logger.clone();
wtf.push(now + Duration::from_millis(8), "Test8");
assert_eq!(wtf.buffered_entries(), vec!["Test1", "Test2", "Test8"]);
let mut logger = BufferedLogger::new(Vec::new(), 100);
let now = Instant::now();
logger.push(now + Duration::from_millis(2), "Test2");
logger.push(now + Duration::from_millis(1), "Test1");
assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
let cloned_logger = logger.clone();
assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
logger.try_flush().unwrap();
logger.flush();
logger.log("Test");
assert_eq!(logger.buffered_entries().len(), 1);
let logger1 = BufferedLogger::new(Vec::new(), 100);
let logger2 = BufferedLogger::new(Vec::new(), 100);
let now = Instant::now();
let mut logger = MultiLogger::new();
logger.log_to(logger1.clone());
logger.push(now + Duration::from_millis(1), "Test1");
logger.log_to(ScopedLogger::new("Second", logger2.clone()));
logger.push(now + Duration::from_millis(2), "Test2");
logger.push(now + Duration::from_millis(3), "Test3");
assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
logger.try_flush().unwrap();
logger.flush();
let base = BufferedLogger::new(Vec::new(), 100);
let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
logger.log("Test");
assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
}

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

Compiling solution v0.1.0 (/tmp/d20190123-22631-sjtjwi/solution)
warning: function is never used: `main`
   --> src/lib.rs:144:1
    |
144 | fn main() {
    | ^^^^^^^^^
    |
    = note: #[warn(dead_code)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 5.20s
     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 ... FAILED
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 ... FAILED
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 ... FAILED
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 ... FAILED
test solution_test::test_scoped_logger_with_a_string_tag ... FAILED

failures:

---- solution_test::test_automatic_flushing_when_buffer_limit_is_reached stdout ----
thread 'solution_test::test_automatic_flushing_when_buffer_limit_is_reached' panicked at 'assertion failed: `(left == right)`
  left: `"One\nTwo\n"`,
 right: `"One\nTwo\nThree\n"`', tests/solution_test.rs:200:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- 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

---- solution_test::test_erroring_io stdout ----
thread 'solution_test::test_erroring_io' panicked at 'Expected try_flush with an erroring IO to return an error', tests/solution_test.rs:340:9

---- solution_test::test_multilogger_logs_and_flushes_when_needed stdout ----
thread 'solution_test::test_multilogger_logs_and_flushes_when_needed' panicked at 'assertion failed: `(left == right)`
  left: `2`,
 right: `0`', tests/solution_test.rs:288:9

---- solution_test::test_scoped_logger stdout ----
thread 'solution_test::test_scoped_logger' panicked at 'assertion failed: `(left == right)`
  left: `"[First] One\n[Second] Two\n[Second] Three\n[First] Four\n[First] One\n[Second] Two\n[Second] Three\n[First] Four\n"`,
 right: `"[First] One\n[Second] Two\n[Second] Three\n[First] Four\n"`', tests/solution_test.rs:315:5

---- solution_test::test_scoped_logger_with_a_string_tag stdout ----
thread 'solution_test::test_scoped_logger_with_a_string_tag' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"[First] Test\n"`', tests/solution_test.rs:329:5


failures:
    solution_test::test_automatic_flushing_when_buffer_limit_is_reached
    solution_test::test_automatic_flushing_when_zero_buffer_limit
    solution_test::test_erroring_io
    solution_test::test_multilogger_logs_and_flushes_when_needed
    solution_test::test_scoped_logger
    solution_test::test_scoped_logger_with_a_string_tag

test result: FAILED. 9 passed; 6 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Християн обнови решението на 18.12.2018 02:56 (преди почти 2 години)

+use std::time::Instant;
+use std::io;
+use std::io::Write;
+use std::rc::Rc;
+use std::cell::RefCell;
+use std::time::Duration;
+
+pub trait Logger {
+ fn push(&mut self, time: Instant, text: &str);
+
+ fn log(&mut self, text: &str);
+
+ fn try_flush(&mut self) -> io::Result<()>;
+
+ fn flush(&mut self);
+}
+
+pub struct BufferedLogger<W: Write> {
+ out: Rc<RefCell<W>>,
+ values: Rc<RefCell<Vec<(String, Instant)>>>,
+ size: usize
+}
+
+const OPENING_BRACKET: &str = "[";
+const CLOSING_BRACKET: &str = "] ";
+
+impl<W: Write> BufferedLogger<W> {
+ pub fn new(out: W, buffer_size: usize) -> Self {
+ BufferedLogger { out: Rc::new(RefCell::new(out)), values: Rc::new(RefCell::new(vec![])), size: buffer_size}
+ }
+
+ pub fn buffered_entries(&self) -> Vec<String> {
+ let mut buffer = self.values.borrow_mut().clone();
+ buffer.sort_by_key(|k| k.1);
+ buffer.into_iter().map(|m| m.0).collect()
+ }
+}
+
+/// Вижте по-долу за бележки за клонирането
+
+impl<W: Write> Clone for BufferedLogger<W> {
+ fn clone(&self) -> Self {
+ BufferedLogger { out: Rc::clone(&self.out), values: Rc::clone(&self.values), size: self.size }
+ }
+}
+
+impl<W: Write> Logger for BufferedLogger<W> {
+
+ fn push(&mut self, time: Instant, text: &str) {
+ if self.values.borrow_mut().len() + 1 == self.size {
+ self.flush()
+ } else {
+ self.values.borrow_mut().push((text.to_owned(), time))
+ }

Това "гълта" стойности. Ако влезеш в клона, в който flush-ваш, не влизаш в клона, в който добавяш нова стойност. Идеята беше да си логваш неща нормално, и в случай, че се мине някаква граница, след това да се flush-не. Няма логика push-а да е в else-а в тази ситуация.

+ }
+
+ fn log(&mut self, text: &str) {
+ self.push(Instant::now(), text)
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }

Все пак искахме try_flush да работи коректно. Т.е., ако има грешка при писане, да засече тази грешка.

Спокойно можеше да имплементираш flush в самия trait, така:

fn flush(&mut self) {
    if let Err(e) = self.try_flush() {
        eprintln!("Logger error: {}", e);
    }
}

Оттам нататък, в try_flush можеше да сложиш долната имплементация, и вместо expect, да върнеш грешка. Метода expect ще panic-не, а не търсехме това, имаме обяснение за "Error handling" в условието.

+
+ fn flush(&mut self) {
+ for entity in self.buffered_entries() {
+ self.out.borrow_mut().write(entity.as_bytes()).expect("Couldn't write :(");
+ self.out.borrow_mut().write(b"\n").expect("Couldn't write :(");
+ }
+ self.values = Rc::new(RefCell::new(vec![]))

Това не работи както искахме в условието. Когато кажеш "values вече е нов Rc", този нов Rc няма никаква връзка с предния. Предишни клонинги не споделят тази стойност, и съответно ако клонираш logger-а и викнеш flush на единия от клонингите, той вече е несвързан с буфера на останалите.

Можеше да викнеш self.values.borrow_mut().clear(), например.

+ }
+}
+
+pub struct MultiLogger {
+ logger: Vec<Box<dyn Logger>>
+}
+
+impl MultiLogger {
+ pub fn new() -> Self {
+ return MultiLogger { logger: vec![]}
+ }
+
+ pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
+ self.logger.push(Box::from(logger));
+ }
+}
+
+impl Logger for MultiLogger {
+
+ fn push(&mut self, time: Instant, text: &str) {
+ for mut log in self.logger.iter_mut() {
+ log.push(time, text);
+ }
+ }
+
+ fn log(&mut self, text: &str) {
+ for mut log in self.logger.iter_mut() {
+ log.log(text);
+ }
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+
+ fn flush(&mut self) {
+ for mut log in self.logger.iter_mut() {
+ log.flush()
+ }
+ }
+}
+
+pub struct ScopedLogger<L: Logger> {
+ tag: String,
+ base_logger: Box<L>
+}
+
+impl<L: Logger> ScopedLogger<L> {
+ pub fn new(tag: &str, base_logger: L) -> Self {
+ return ScopedLogger { tag: String::from(tag), base_logger: Box::new(base_logger) }
+ }
+}
+
+impl<L: Logger> Logger for ScopedLogger<L> {
+ fn push(&mut self, time: Instant, text: &str) {
+ let text = OPENING_BRACKET.to_string() + self.tag.as_str() + CLOSING_BRACKET + text;
+ self.base_logger.as_mut().push(time, text.as_str());
+ }
+
+ fn log(&mut self, text: &str) {
+ let text = OPENING_BRACKET.to_string() + self.tag.as_str() + &CLOSING_BRACKET + text;
+ self.base_logger.as_mut().log(text.as_str())
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ return Ok(());
+ }
+
+ fn flush(&mut self) {
+ self.base_logger.as_mut().flush();
+ }
+}
+
+fn main() {
+
+ let mut buffered_logger = BufferedLogger::new(io::stdout(), 100);
+ let now = Instant::now();
+
+ buffered_logger.push(now + Duration::from_millis(2), "Test2");
+ buffered_logger.push(now + Duration::from_millis(1), "Test1");
+
+ assert_eq!(buffered_logger.buffered_entries(), vec!["Test1", "Test2"]);
+
+ let mut wtf = buffered_logger.clone();
+ wtf.push(now + Duration::from_millis(8), "Test8");
+ assert_eq!(wtf.buffered_entries(), vec!["Test1", "Test2", "Test8"]);
+
+ let mut logger = BufferedLogger::new(Vec::new(), 100);
+ let now = Instant::now();
+
+ logger.push(now + Duration::from_millis(2), "Test2");
+ logger.push(now + Duration::from_millis(1), "Test1");
+
+ assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
+
+ let cloned_logger = logger.clone();
+ assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
+
+ logger.try_flush().unwrap();
+ logger.flush();
+ logger.log("Test");
+
+ assert_eq!(logger.buffered_entries().len(), 1);
+
+ let logger1 = BufferedLogger::new(Vec::new(), 100);
+ let logger2 = BufferedLogger::new(Vec::new(), 100);
+ let now = Instant::now();
+
+ let mut logger = MultiLogger::new();
+ logger.log_to(logger1.clone());
+ logger.push(now + Duration::from_millis(1), "Test1");
+
+ logger.log_to(ScopedLogger::new("Second", logger2.clone()));
+ logger.push(now + Duration::from_millis(2), "Test2");
+ logger.push(now + Duration::from_millis(3), "Test3");
+
+ assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
+ assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
+
+ logger.try_flush().unwrap();
+ logger.flush();
+
+ let base = BufferedLogger::new(Vec::new(), 100);
+ let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
+ logger.log("Test");
+
+ assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
+}