use ntime;
use std::io;
use std::sync::{Arc, Mutex};
use crate::attributes;
use crate::constant::{ATTRIBUTE_KEY_TRACE_FILENAME, ATTRIBUTE_KEY_TRACE_LINE, ATTRIBUTE_KEY_TRACE_LOGGER_ID};
use crate::format;
use crate::sink;
pub struct MemoryConfig {
pub type_str: String,
pub formatter_cfg: format::FormatterConfig,
pub mock_time: bool,
pub mock_trace: bool,
}
pub struct MemoryOutput {
out: Arc<Mutex<Vec<u8>>>,
}
impl MemoryOutput {
fn new(out: &Arc<Mutex<Vec<u8>>>) -> Self {
Self { out: out.clone() }
}
pub fn as_bytes(&self) -> Vec<u8> {
self.out.lock().unwrap().clone()
}
pub fn as_string(&self) -> String {
String::from_utf8(self.out.lock().unwrap().clone()).expect("invalid UTF-8 contents for Memory sink")
}
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
type_str: "default".into(),
formatter_cfg: format::FormatterConfig {
time_format: ntime::Format::UtcMillisDateTime,
delimiter: vec![b'\n'],
..format::FormatterConfig::default()
},
mock_time: false,
mock_trace: false,
}
}
}
pub struct Memory {
name: String,
formatter: format::Formatter,
out: Arc<Mutex<Vec<u8>>>,
frozen_now: Option<ntime::Timestamp>,
frozen_now_tick: Option<ntime::Duration>,
frozen_trace_logger_id: Option<u32>,
frozen_trace_filename: Option<&'static str>,
frozen_trace_line: Option<u64>,
mock_partial_update: sink::PartialLogUpdate,
mock_attributes: attributes::Map,
}
impl Memory {
pub fn new(conf: MemoryConfig) -> Self {
let formatter = format::Formatter::new(conf.formatter_cfg);
Self {
name: format!("{} log string", conf.type_str),
formatter: formatter,
out: Arc::new(Mutex::new(Vec::new())),
frozen_now: if conf.mock_time {
Some(ntime::Timestamp::from_secs(1772637015))
} else {
None
},
frozen_now_tick: if conf.mock_time { Some(ntime::Duration::from_millis(1234)) } else { None },
frozen_trace_logger_id: if conf.mock_trace { Some(100 as u32) } else { None },
frozen_trace_filename: if conf.mock_trace { Some("src/some_file.rs") } else { None },
frozen_trace_line: if conf.mock_trace { Some(567) } else { None },
mock_partial_update: sink::PartialLogUpdate::blank(),
mock_attributes: attributes::Map::new(),
}
}
pub fn output(&self) -> MemoryOutput {
MemoryOutput::new(&self.out)
}
pub fn clear(&mut self) {
self.out.lock().unwrap().clear();
}
#[inline]
fn has_mocks(&self) -> bool {
self.frozen_now.is_some() || self.frozen_trace_logger_id.is_some() || self.frozen_trace_filename.is_some() || self.frozen_trace_line.is_some()
}
}
impl sink::Sink for Memory {
fn name(&self) -> &str {
self.name.as_str()
}
fn log<'f>(&mut self, update: &'f sink::LogUpdate) -> io::Result<()> {
let mut out = self.out.lock().unwrap();
let entry = if self.has_mocks() {
let (pupdate, uattrs) = update.parts();
self.mock_partial_update.copy_from(pupdate);
self.mock_attributes.copy_from(uattrs);
if let Some(t) = self.frozen_now.as_mut() {
self.mock_partial_update.when = t.clone();
}
if let Some(id) = self.frozen_trace_logger_id {
if self.mock_attributes.has(ATTRIBUTE_KEY_TRACE_LOGGER_ID) {
self.mock_attributes.insert(ATTRIBUTE_KEY_TRACE_LOGGER_ID, attributes::Value::from(id));
self.frozen_trace_logger_id = Some(id + 1);
};
}
if let Some(filename) = self.frozen_trace_filename {
if self.mock_attributes.has(ATTRIBUTE_KEY_TRACE_FILENAME) {
self.mock_attributes.insert(ATTRIBUTE_KEY_TRACE_FILENAME, attributes::Value::from(filename));
};
}
if let Some(line) = self.frozen_trace_line {
if self.mock_attributes.has(ATTRIBUTE_KEY_TRACE_LINE) {
self.mock_attributes.insert(ATTRIBUTE_KEY_TRACE_LINE, attributes::Value::from(line));
self.frozen_trace_line = Some(line + 10);
};
}
self.formatter.as_bytes(&sink::LogUpdate::from((&self.mock_partial_update, &self.mock_attributes)))
} else {
self.formatter.as_bytes(update)
};
if !out.is_empty() {
out.extend_from_slice(self.formatter.delimiter());
}
out.extend(entry);
if let Some(now) = &mut (self.frozen_now) {
if let Some(tick) = self.frozen_now_tick {
now.add_duration(&tick);
}
}
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub fn default() -> Memory {
Memory::new(MemoryConfig::default())
}