Решение на Logging от Петко Георгиев

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

Към профила на Петко Георгиев

Резултати

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

Код

use std::time::Instant;
use std::io;
use std::io::Write;
use std::cell::RefCell;
use std::rc::Rc;
pub trait Logger {
/// Метод, който добавя нов запис за логване. Първия аргумент, от тип `std::time::Instant`, е
/// момента във времето, който се асоциира със събитието. Обикновено ще се ползва метода `log`
/// директно, който запълва този параметър, но метода `push` ще е удобен за тестване на
/// логиката.
///
/// Втория аргумент е низа, който ще се логва.
///
fn push(&mut self, time: Instant, text: &str);
/// Метод който ще работи като `push`, с тази разлика, че директно използва `Instant::now()` за
/// да вземе текущ timestamp.
///
fn log(&mut self, text: &str);
/// Метод, който записва нещата от вътрешния буфер към някакъв външен носител -- файл, сокет,
/// стандартния изход. В случай на имплементация, която няма нужда от този метод, винаги може да
/// се имплементира като просто `Ok(())`.
///
fn try_flush(&mut self) -> io::Result<()>;
/// Метод, който прави същото като по-горния, но не връща грешка. Вижте по-долу за бележки за
/// Error handling-а, който очакваме.
///
fn flush(&mut self);
}
pub struct BufferedLogger<W: Write> {
out: Rc<RefCell<W>>,
buffer_size: usize,
buffer: Rc<RefCell<Vec<(String, Instant)>>>
}
impl<W: Write> BufferedLogger<W> {
/// Конструира структура, която ще пази записи в буфер с размер `buffer_size`, и ще ги записва
/// в подадената структура от тип, който имплементира `Write`;
///
pub fn new(out: W, buffer_size: usize) -> Self {
Self {
out: Rc::new(RefCell::new(out)),
buffer_size: buffer_size,
buffer: Rc::new(RefCell::new(Vec::new()))
}
}
/// Връща списък от записите, които са буферирани в момента. Записите се очаква да бъдат
/// подредени по времето, в което са log-нати, от най-ранни до най-късни.
///
pub fn buffered_entries(&self) -> Vec<String> {
let mut ret = Vec::new();
for message in self.buffer.borrow().iter() {
ret.push(message.0.clone());
}
ret
}
}
/// Вижте по-долу за бележки за клонирането
impl<W: Write> Clone for BufferedLogger<W> {
fn clone(&self) -> Self {
Self {
out: self.out.clone(),
buffer_size: self.buffer_size,
buffer: self.buffer.clone(),
}
}
}
impl<W: Write> Logger for BufferedLogger<W> {
fn push(&mut self, time: Instant, text: &str) {
let buffer_len;
{
let mut buffer = self.buffer.borrow_mut();
let mut i = buffer.len();
while i > 0 && buffer[i - 1].1 > time {
i -= 1;
}
buffer.insert(i, (String::from(text), time));
buffer_len = buffer.len();
}
if buffer_len == self.buffer_size {
self.flush();
}
}
fn log(&mut self, text: &str) {
self.push(Instant::now(), text)
}
fn try_flush(&mut self) -> io::Result<()> {
let mut buffer = self.buffer.borrow_mut();
for message in buffer.iter() {
self.out.borrow_mut().write_fmt(format_args!("{}\n", message.0))?;
}
buffer.clear();
Ok(())
}
fn flush(&mut self) {
if let Err(error) = self.try_flush() {
eprintln!("{}", error);
}
}
}
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 logger in self.loggers.iter_mut() {
logger.push(time, text);
}
}
fn log(&mut self, text: &str) {
for logger in self.loggers.iter_mut() {
logger.log(text);
}
}
fn try_flush(&mut self) -> io::Result<()> {
let mut result = Ok(());
for logger in self.loggers.iter_mut() {
let r = logger.try_flush();
if r.is_err() {
result = r;
}
}
result

Хмм, тук бих казал, че можеш просто да ползваш try_flush()?, но като се замисля, това би спряло flush-ването на останалите буфери. Доколко това е добро или лошо не мога да преценя -- зависи от use-case-а.

Не сме тествали за това, така че няма значение в контекста. но добро хрумване все пак и смислено решение от твоя страна.

}
fn flush(&mut self) {
for logger in self.loggers.iter_mut() {
logger.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: String::from(tag),
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).as_str());
}
fn log(&mut self, text: &str) {
self.push(Instant::now(), text);
}
fn try_flush(&mut self) -> io::Result<()> {
self.base_logger.try_flush()
}
fn flush(&mut self) {
self.base_logger.flush()
}
}

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

Compiling solution v0.1.0 (/tmp/d20190123-22631-yp5f9l/solution)
    Finished dev [unoptimized + debuginfo] target(s) in 4.91s
     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 00:45 (преди почти 2 години)

+use std::time::Instant;
+use std::io;
+use std::io::Write;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+pub trait Logger {
+ /// Метод, който добавя нов запис за логване. Първия аргумент, от тип `std::time::Instant`, е
+ /// момента във времето, който се асоциира със събитието. Обикновено ще се ползва метода `log`
+ /// директно, който запълва този параметър, но метода `push` ще е удобен за тестване на
+ /// логиката.
+ ///
+ /// Втория аргумент е низа, който ще се логва.
+ ///
+ fn push(&mut self, time: Instant, text: &str);
+
+ /// Метод който ще работи като `push`, с тази разлика, че директно използва `Instant::now()` за
+ /// да вземе текущ timestamp.
+ ///
+ fn log(&mut self, text: &str);
+
+ /// Метод, който записва нещата от вътрешния буфер към някакъв външен носител -- файл, сокет,
+ /// стандартния изход. В случай на имплементация, която няма нужда от този метод, винаги може да
+ /// се имплементира като просто `Ok(())`.
+ ///
+ fn try_flush(&mut self) -> io::Result<()>;
+
+ /// Метод, който прави същото като по-горния, но не връща грешка. Вижте по-долу за бележки за
+ /// Error handling-а, който очакваме.
+ ///
+ fn flush(&mut self);
+}
+
+
+
+pub struct BufferedLogger<W: Write> {
+ out: Rc<RefCell<W>>,
+ buffer_size: usize,
+ buffer: Rc<RefCell<Vec<(String, Instant)>>>
+}
+
+impl<W: Write> BufferedLogger<W> {
+ /// Конструира структура, която ще пази записи в буфер с размер `buffer_size`, и ще ги записва
+ /// в подадената структура от тип, който имплементира `Write`;
+ ///
+ pub fn new(out: W, buffer_size: usize) -> Self {
+ Self {
+ out: Rc::new(RefCell::new(out)),
+ buffer_size: buffer_size,
+ buffer: Rc::new(RefCell::new(Vec::new()))
+ }
+ }
+
+ /// Връща списък от записите, които са буферирани в момента. Записите се очаква да бъдат
+ /// подредени по времето, в което са log-нати, от най-ранни до най-късни.
+ ///
+ pub fn buffered_entries(&self) -> Vec<String> {
+ let mut ret = Vec::new();
+ for message in self.buffer.borrow().iter() {
+ ret.push(message.0.clone());
+ }
+ ret
+ }
+}
+
+/// Вижте по-долу за бележки за клонирането
+impl<W: Write> Clone for BufferedLogger<W> {
+ fn clone(&self) -> Self {
+ Self {
+ out: self.out.clone(),
+ buffer_size: self.buffer_size,
+ buffer: self.buffer.clone(),
+ }
+ }
+}
+
+impl<W: Write> Logger for BufferedLogger<W> {
+ fn push(&mut self, time: Instant, text: &str) {
+ let buffer_len;
+ {
+ let mut buffer = self.buffer.borrow_mut();
+ let mut i = buffer.len();
+ while i > 0 && buffer[i - 1].1 > time {
+ i -= 1;
+ }
+ buffer.insert(i, (String::from(text), time));
+ buffer_len = buffer.len();
+ }
+ if buffer_len == self.buffer_size {
+ self.flush();
+ }
+ }
+
+ fn log(&mut self, text: &str) {
+ self.push(Instant::now(), text)
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ let mut buffer = self.buffer.borrow_mut();
+ for message in buffer.iter() {
+ self.out.borrow_mut().write_fmt(format_args!("{}\n", message.0))?;
+ }
+ buffer.clear();
+ Ok(())
+ }
+
+ fn flush(&mut self) {
+ if let Err(error) = self.try_flush() {
+ eprintln!("{}", error);
+ }
+ }
+}
+
+
+
+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 logger in self.loggers.iter_mut() {
+ logger.push(time, text);
+ }
+ }
+
+ fn log(&mut self, text: &str) {
+ for logger in self.loggers.iter_mut() {
+ logger.log(text);
+ }
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ let mut result = Ok(());
+ for logger in self.loggers.iter_mut() {
+ let r = logger.try_flush();
+ if r.is_err() {
+ result = r;
+ }
+ }
+ result

Хмм, тук бих казал, че можеш просто да ползваш try_flush()?, но като се замисля, това би спряло flush-ването на останалите буфери. Доколко това е добро или лошо не мога да преценя -- зависи от use-case-а.

Не сме тествали за това, така че няма значение в контекста. но добро хрумване все пак и смислено решение от твоя страна.

+ }
+
+ fn flush(&mut self) {
+ for logger in self.loggers.iter_mut() {
+ logger.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: String::from(tag),
+ 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).as_str());
+ }
+
+ fn log(&mut self, text: &str) {
+ self.push(Instant::now(), text);
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ self.base_logger.try_flush()
+ }
+
+ fn flush(&mut self) {
+ self.base_logger.flush()
+ }
+}