dipstick/src/output/log.rs

187 lines
5.3 KiB
Rust
Executable File

use crate::attributes::{Attributes, Buffered, MetricId, OnFlush, Prefixed, WithAttributes};
use crate::input::{Input, InputKind, InputMetric, InputScope};
use crate::name::MetricName;
use crate::output::format::{Formatting, LineFormat, SimpleFormat};
use crate::Flush;
use crate::{CachedInput, QueuedInput};
use std::sync::Arc;
#[cfg(not(feature = "parking_lot"))]
use std::sync::RwLock;
#[cfg(feature = "parking_lot")]
use parking_lot::RwLock;
use std::io::Write;
use std::io;
/// Buffered metrics log output.
#[derive(Clone)]
pub struct Log {
attributes: Attributes,
format: Arc<dyn LineFormat>,
level: log::Level,
target: Option<String>,
}
impl Input for Log {
type SCOPE = LogScope;
fn metrics(&self) -> Self::SCOPE {
LogScope {
attributes: self.attributes.clone(),
entries: Arc::new(RwLock::new(Vec::new())),
log: self.clone(),
}
}
}
impl WithAttributes for Log {
fn get_attributes(&self) -> &Attributes {
&self.attributes
}
fn mut_attributes(&mut self) -> &mut Attributes {
&mut self.attributes
}
}
impl Buffered for Log {}
impl Formatting for Log {
fn formatting(&self, format: impl LineFormat + 'static) -> Self {
let mut cloned = self.clone();
cloned.format = Arc::new(format);
cloned
}
}
/// A scope for metrics log output.
#[derive(Clone)]
pub struct LogScope {
attributes: Attributes,
entries: Arc<RwLock<Vec<Vec<u8>>>>,
log: Log,
}
impl Log {
/// Write metric values to the standard log using `info!`.
// TODO parameterize log level, logger
pub fn to_log() -> Log {
Log {
attributes: Attributes::default(),
format: Arc::new(SimpleFormat::default()),
level: log::Level::Info,
target: None,
}
}
/// Sets the log `target` to use when logging metrics.
/// See the (log!)[https://docs.rs/log/0.4.6/log/macro.log.html] documentation.
pub fn level(&self, level: log::Level) -> Self {
let mut cloned = self.clone();
cloned.level = level;
cloned
}
/// Sets the log `target` to use when logging metrics.
/// See the (log!)[https://docs.rs/log/0.4.6/log/macro.log.html] documentation.
pub fn target(&self, target: &str) -> Self {
let mut cloned = self.clone();
cloned.target = Some(target.to_string());
cloned
}
}
impl WithAttributes for LogScope {
fn get_attributes(&self) -> &Attributes {
&self.attributes
}
fn mut_attributes(&mut self) -> &mut Attributes {
&mut self.attributes
}
}
impl Buffered for LogScope {}
impl QueuedInput for Log {}
impl CachedInput for Log {}
impl InputScope for LogScope {
fn new_metric(&self, name: MetricName, kind: InputKind) -> InputMetric {
let name = self.prefix_append(name);
let template = self.log.format.template(&name, kind);
let entries = self.entries.clone();
if self.is_buffered() {
// buffered
InputMetric::new(MetricId::forge("log", name), move |value, labels| {
let mut buffer = Vec::with_capacity(32);
match template.print(&mut buffer, value, |key| labels.lookup(key)) {
Ok(()) => {
let mut entries = write_lock!(entries);
entries.push(buffer)
}
Err(err) => debug!("Could not format buffered log metric: {}", err),
}
})
} else {
// unbuffered
let level = self.log.level;
let target = self.log.target.clone();
InputMetric::new(MetricId::forge("log", name), move |value, labels| {
let mut buffer = Vec::with_capacity(32);
match template.print(&mut buffer, value, |key| labels.lookup(key)) {
Ok(()) => {
if let Some(target) = &target {
log!(target: target, level, "{:?}", &buffer)
} else {
log!(level, "{:?}", &buffer)
}
}
Err(err) => debug!("Could not format buffered log metric: {}", err),
}
})
}
}
}
impl Flush for LogScope {
fn flush(&self) -> io::Result<()> {
self.notify_flush_listeners();
let mut entries = write_lock!(self.entries);
if !entries.is_empty() {
let mut buf: Vec<u8> = Vec::with_capacity(32 * entries.len());
for entry in entries.drain(..) {
writeln!(&mut buf, "{:?}", &entry)?;
}
if let Some(target) = &self.log.target {
log!(target: target, self.log.level, "{:?}", &buf)
} else {
log!(self.log.level, "{:?}", &buf)
}
}
Ok(())
}
}
impl Drop for LogScope {
fn drop(&mut self) {
if let Err(e) = self.flush() {
warn!("Could not flush log metrics on Drop. {}", e)
}
}
}
#[cfg(test)]
mod test {
use crate::input::*;
#[test]
fn test_to_log() {
let c = super::Log::to_log().metrics();
let m = c.new_metric("test".into(), InputKind::Marker);
m.write(33, labels![]);
}
}