//! An object to handle everyone's errors //! use std::thread::{self, JoinHandle}; use std::sync::{Arc, Mutex, RwLock}; use std::sync::mpsc::{self, Sender, Receiver, channel}; use std::collections::{BTreeMap, VecDeque}; use std::fmt::{self, Display, Error as FmtError, Formatter}; use zmq; use chrono::{DateTime, Utc, TimeZone}; use termion::color::{self, Fg, Bg}; use influent::measurement::{Measurement, Value as InfluentValue}; use slog::{self, OwnedKVList, Drain, Key, KV}; use super::{nanos, file_logger}; use influx; const N_WARNINGS: usize = 150; #[macro_export] macro_rules! confirmed { ($warnings:ident, $($args:tt)*) => ( { $warnings.send(Warning::Confirmed( ( format!($($args)*) ) ) ).unwrap(); } ) } /// logs a `Warning::Awesome` message to the `WarningsManager` #[macro_export] macro_rules! awesome { ($warnings:ident, $($args:tt)*) => ( { $warnings.send(Warning::Awesome( ( format!($($args)*) ) ) ).unwrap(); } ) } #[macro_export] macro_rules! critical { ($warnings:ident, $($args:tt)*) => ( { $warnings.send(Warning::Critical( ( format!($($args)*) ) ) ).unwrap(); } ) } #[macro_export] macro_rules! notice { ($warnings:ident, $($args:tt)*) => ( { $warnings.send(Warning::Notice( ( format!($($args)*) ) ) ).unwrap(); } ) } #[macro_export] macro_rules! error { ($warnings:ident, $($args:tt)*) => ( { $warnings.send(Warning::Error( ( format!($($args)*) ) ) ).unwrap(); } ) } /// represents a non-fatal error somewhere in /// the system to report either to the program interface /// or in logs. /// #[derive(Debug, Clone, PartialEq)] pub enum Warning { Notice(String), Error(String), DegradedService(String), Critical(String), Confirmed(String), Awesome(String), Debug { msg: String, kv: MeasurementRecord, }, Terminate } impl Warning { pub fn msg(&self) -> String { match *self { Warning::Notice(ref s) | Warning::Error(ref s) | Warning::DegradedService(ref s) | Warning::Critical(ref s) | Warning::Awesome(ref s) | Warning::Confirmed(ref s) | Warning::Debug { msg: ref s, .. } => s.clone(), Warning::Terminate => "".to_owned() } } pub fn msg_str(&self) -> &str { match *self { Warning::Notice(ref s) | Warning::Error(ref s) | Warning::DegradedService(ref s) | Warning::Critical(ref s) | Warning::Awesome(ref s) | Warning::Confirmed(ref s) | Warning::Debug { msg: ref s, .. } => s.as_ref(), Warning::Terminate => "Terminate" } } pub fn category_str(&self) -> &str { match self { &Warning::Notice(_) => "notice", &Warning::Error(_) => "error", &Warning::Critical(_) => "critical", &Warning::DegradedService(_) => "degraded_service", &Warning::Confirmed(_) => "confirmed", &Warning::Awesome(_) => "awesome", &Warning::Debug { .. } => "debug", &Warning::Terminate => "terminate", } } pub fn category(&self, f: &mut Formatter) { match self { &Warning::Notice(_) => { write!(f, "[ Notice ]"); } &Warning::Error(_) => { write!(f, "{yellow}[{title}]{reset}", yellow = Fg(color::LightYellow), title = " Error--", reset = Fg(color::Reset)); } &Warning::Critical(_) => { write!(f, "{bg}{fg}{title}{resetbg}{resetfg}", bg = Bg(color::Red), fg = Fg(color::White), title = " CRITICAL ", resetbg = Bg(color::Reset), resetfg = Fg(color::Reset)); } &Warning::Awesome(_) => { write!(f, "{color}[{title}]{reset}", color = Fg(color::Green), title = "Awesome!", reset = Fg(color::Reset)); } &Warning::DegradedService(_) => { write!(f, "{color}[{title}] {reset}", color = Fg(color::Blue), title = "Degraded Service ", reset = Fg(color::Reset)); } &Warning::Confirmed(_) => { write!(f, "{bg}{fg}{title}{resetbg}{resetfg}", bg = Bg(color::Blue), fg = Fg(color::White), title = "Confirmed ", resetbg = Bg(color::Reset), resetfg = Fg(color::Reset)); } _ => {} } } } impl Display for Warning { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { self.category(f); write!(f, " {}", self.msg()) } } // impl Message for Warning { // fn kill_switch() -> Self { // Warning::Terminate // } // } #[derive(Debug, Clone)] pub struct Record { pub time: DateTime, pub msg: Warning } impl Record { pub fn new(msg: Warning) -> Self { let time = Utc::now(); Record { time, msg } } pub fn to_measurement(&self, name: &'static str) -> Measurement { let cat = self.msg.category_str(); let body = self.msg.msg_str(); let mut m = Measurement::new(name); m.add_tag("category", cat); m.add_field("msg", InfluentValue::String(body)); m.set_timestamp(nanos(self.time) as i64); m } } impl Display for Record { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { write!(f, "{} | {}", self.time.format("%H:%M:%S"), self.msg) } } pub type SlogResult = Result<(), slog::Error>; #[derive(Debug, Clone, PartialEq)] pub enum Value { String(String), Float(f64), Integer(i64), Boolean(bool) } impl Value { pub fn to_influent<'a>(&'a self) -> InfluentValue<'a> { match self { &Value::String(ref s) => InfluentValue::String(s), &Value::Float(n) => InfluentValue::Float(n), &Value::Integer(i) => InfluentValue::Integer(i), &Value::Boolean(b) => InfluentValue::Boolean(b), } } } #[derive(Debug, Clone, PartialEq)] pub struct MeasurementRecord { fields: Vec<(Key, Value)>, //measurement: &'a mut Measurement<'a>, tags: Vec<(Key, String)>, } impl MeasurementRecord { pub fn new() -> Self { MeasurementRecord { fields: Vec::new(), tags: Vec::new(), } } pub fn add_field(&mut self, key: Key, val: Value) -> SlogResult { self.fields.push((key, val)); Ok(()) } pub fn add_tag(&mut self, key: Key, val: String) -> SlogResult { self.tags.push((key, val)); Ok(()) } pub fn serialize_values(&mut self, record: &slog::Record, values: &OwnedKVList) { let mut builder = TagBuilder { mrec: self }; values.serialize(record, &mut builder); } pub fn to_measurement<'a>(&'a self, name: &'a str) -> Measurement<'a> { let fields: BTreeMap<&'a str, InfluentValue<'a>> = self.fields.iter() .map(|&(k, ref v)| { (k, v.to_influent()) }).collect(); let tags: BTreeMap<&'a str, &'a str> = self.tags.iter() .map(|&(k, ref v)| { (k, v.as_ref()) }).collect(); Measurement { key: name, timestamp: Some(nanos(Utc::now()) as i64), fields, tags, } } } impl slog::Serializer for MeasurementRecord { fn emit_usize(&mut self, key: Key, val: usize) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_isize(&mut self, key: Key, val: isize) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_bool(&mut self, key: Key, val: bool) -> SlogResult { self.add_field(key, Value::Boolean(val)); Ok(()) } fn emit_u8(&mut self, key: Key, val: u8) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_i8(&mut self, key: Key, val: i8) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_u16(&mut self, key: Key, val: u16) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_i16(&mut self, key: Key, val: i16) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_u32(&mut self, key: Key, val: u32) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_i32(&mut self, key: Key, val: i32) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_f32(&mut self, key: Key, val: f32) -> SlogResult { self.add_field(key, Value::Float(val as f64)) } fn emit_u64(&mut self, key: Key, val: u64) -> SlogResult { self.add_field(key, Value::Integer(val as i64)) } fn emit_i64(&mut self, key: Key, val: i64) -> SlogResult { self.add_field(key, Value::Integer(val)) } fn emit_f64(&mut self, key: Key, val: f64) -> SlogResult { self.add_field(key, Value::Float(val)) } fn emit_str(&mut self, key: Key, val: &str) -> SlogResult { self.add_field(key, Value::String(val.to_string())) } fn emit_unit(&mut self, key: Key) -> SlogResult { self.add_field(key, Value::Boolean(true)) } fn emit_none(&mut self, key: Key) -> SlogResult { Ok(()) } //self.add_field(key, Value::String("none".into())) } fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> SlogResult { self.add_field(key, Value::String(val.to_string())) } } pub struct TagBuilder<'a> { mrec: &'a mut MeasurementRecord } impl<'a> slog::Serializer for TagBuilder<'a> { fn emit_str(&mut self, key: Key, val: &str) -> SlogResult { self.mrec.add_tag(key, val.to_string()) } fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> SlogResult { self.mrec.add_tag(key, val.to_string()) } } pub struct WarningsDrain { tx: Arc>>, drain: D } impl WarningsDrain { pub fn new(tx: Sender) -> Self { let tx = Arc::new(Mutex::new(tx)); let drain = slog::Discard; WarningsDrain { tx, drain } } } impl Drain for WarningsDrain { type Ok = (); type Err = D::Err; fn log(&self, record: &slog::Record, values: &OwnedKVList) -> Result { //let mut meas = Measurement::new("warnings"); //println!("{:?}", values); let mut ser = MeasurementRecord::new(); ser.serialize_values(record, values); //values.serialize(record, &mut ser); record.kv().serialize(record, &mut ser); //println!("{:?}", ser); let msg = record.msg().to_string(); if let Ok(lock) = self.tx.lock() { lock.send(Warning::Debug { msg, kv: ser }); } Ok(()) } } #[derive(Debug)] pub struct WarningsManager { pub tx: Sender, pub warnings: Arc>>, thread: Option> } impl WarningsManager { /// `measurement_name` is the name of the influxdb measurement /// we will save log entries to. /// pub fn new(measurement_name: &'static str) -> Self { let warnings = Arc::new(RwLock::new(VecDeque::new())); let warnings_copy = warnings.clone(); let (tx, rx) = channel(); let mut buf = String::with_capacity(4096); let ctx = zmq::Context::new(); let socket = influx::push(&ctx).unwrap(); let thread = thread::spawn(move || { let path = format!("var/log/warnings-manager-{}.log", measurement_name); let logger = file_logger(&path); info!(logger, "entering loop"); loop { if let Ok(msg) = rx.recv() { match msg { Warning::Terminate => { crit!(logger, "terminating"); break; } Warning::Debug { msg, kv } => { debug!(logger, "new Warning::Debug arrived"; "msg" => &msg); let mut meas = kv.to_measurement(measurement_name); meas.add_field("msg", InfluentValue::String(msg.as_ref())); meas.add_tag("category", "debug"); influx::serialize(&meas, &mut buf); socket.send_str(&buf, 0); buf.clear(); // and don't push to warnings // bc it's debug } other => { debug!(logger, "new {} arrived", other.category_str(); "msg" => other.category_str()); let rec = Record::new(other); { let m = rec.to_measurement(measurement_name); influx::serialize(&m, &mut buf); socket.send_str(&buf, 0); buf.clear(); } if let Ok(mut lock) = warnings.write() { lock.push_front(rec); lock.truncate(N_WARNINGS); } } } } } }); WarningsManager { warnings: warnings_copy, thread: Some(thread), tx } } } impl Drop for WarningsManager { fn drop(&mut self) { self.tx.send(Warning::Terminate); if let Some(thread) = self.thread.take() { thread.join(); } } } #[cfg(test)] mod tests { use super::*; use test::{black_box, Bencher}; #[test] fn it_creates_a_logger() { let wm = WarningsManager::new(); let im = influx::writer(wm.tx.clone()); let drain = WarningsDrain { tx: Arc::new(Mutex::new(wm.tx.clone())), drain: slog::Discard }; let logger = slog::Logger::root(drain, o!()); //for _ in 0..60 { // debug!(logger, "test 123"; "exchange" => "plnx"); //} } #[bench] fn it_sends_integers_with_a_sender_behind_a_mutex(b: &mut Bencher) { let (tx, rx) = channel(); enum Msg { Val(usize), Terminate } let worker = thread::spawn(move || { let mut xs = Vec::new(); loop { match rx.recv().unwrap() { Msg::Val(x) => { xs.push(x); } Msg::Terminate => break, } } xs.len() }); let tx = Arc::new(Mutex::new(tx)); b.iter(|| { let lock = tx.lock().unwrap(); lock.send(Msg::Val(1)); }); tx.lock().unwrap().send(Msg::Terminate); let len = worker.join().unwrap(); println!("{}", len); } }