#![forbid(unsafe_code)]
#![deny(missing_docs)]
use parking_lot::Mutex;
use rlg::log::Log;
use rlg::log_level::LogLevel;
use serde_json::Value;
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct Capture {
inner: Arc<Mutex<Vec<Log>>>,
}
impl Capture {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&self, record: Log) {
self.inner.lock().push(record);
}
#[must_use]
pub fn records(&self) -> Vec<Log> {
self.inner.lock().clone()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.lock().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.lock().is_empty()
}
pub fn clear(&self) {
self.inner.lock().clear();
}
}
#[must_use]
pub fn capture() -> Capture {
Capture::new()
}
pub trait LogExt {
fn log_to(self, capture: &Capture);
}
impl LogExt for Log {
fn log_to(self, capture: &Capture) {
capture.push(self);
}
}
#[must_use]
pub fn has_level(capture: &Capture, level: LogLevel) -> bool {
capture.inner.lock().iter().any(|r| r.level == level)
}
#[must_use]
pub fn description_contains(capture: &Capture, needle: &str) -> bool {
capture
.inner
.lock()
.iter()
.any(|r| r.description.contains(needle))
}
pub fn attribute_eq<V>(
capture: &Capture,
key: &str,
expected: V,
) -> bool
where
V: Into<Value>,
{
let want = expected.into();
capture
.inner
.lock()
.iter()
.any(|r| r.attributes.get(key) == Some(&want))
}
#[must_use]
pub fn has_component(capture: &Capture, component: &str) -> bool {
capture
.inner
.lock()
.iter()
.any(|r| r.component.as_ref() == component)
}
#[macro_export]
macro_rules! assert_logged {
($capture:expr, level == $level:expr) => {{
assert!(
$crate::has_level(&$capture, $level),
"expected a captured record at level {:?}, got: {:?}",
$level,
$capture.records()
);
}};
($capture:expr, contains $needle:expr) => {{
assert!(
$crate::description_contains(&$capture, $needle),
"expected a captured record description to contain {:?}, got: {:?}",
$needle,
$capture.records()
);
}};
($capture:expr, attribute $key:expr => $expected:expr) => {{
assert!(
$crate::attribute_eq(&$capture, $key, $expected),
"expected a captured record with attribute {:?} == {:?}, got: {:?}",
$key,
$expected,
$capture.records()
);
}};
($capture:expr, component $component:expr) => {{
assert!(
$crate::has_component(&$capture, $component),
"expected a captured record with component {:?}, got: {:?}",
$component,
$capture.records()
);
}};
($capture:expr, len == $n:expr) => {{
let n = $capture.len();
assert!(
n == $n,
"expected {} captured records, got {}: {:?}",
$n,
n,
$capture.records()
);
}};
}
#[cfg(test)]
mod tests {
use super::*;
use rlg::log::Log;
#[test]
fn capture_starts_empty() {
let c = capture();
assert!(c.is_empty());
assert_eq!(c.len(), 0);
}
#[test]
fn log_to_appends() {
let c = capture();
Log::info("hi").component("svc").log_to(&c);
Log::warn("oops").log_to(&c);
assert_eq!(c.len(), 2);
}
#[test]
fn clear_drops_records() {
let c = capture();
Log::info("x").log_to(&c);
c.clear();
assert!(c.is_empty());
}
#[test]
fn predicates_match_levels() {
let c = capture();
Log::error("boom").log_to(&c);
assert!(has_level(&c, LogLevel::ERROR));
assert!(!has_level(&c, LogLevel::INFO));
}
#[test]
fn predicates_match_components() {
let c = capture();
Log::info("x").component("auth").log_to(&c);
assert!(has_component(&c, "auth"));
assert!(!has_component(&c, "other"));
}
#[test]
fn predicates_match_descriptions() {
let c = capture();
Log::info("authenticated user").log_to(&c);
assert!(description_contains(&c, "authenticated"));
assert!(!description_contains(&c, "missing"));
}
#[test]
fn predicates_match_attributes() {
let c = capture();
Log::info("x")
.with("user_id", 42_u64)
.with("region", "eu-west-1")
.log_to(&c);
assert!(attribute_eq(&c, "user_id", 42_u64));
assert!(attribute_eq(&c, "region", "eu-west-1"));
assert!(!attribute_eq(&c, "user_id", 99_u64));
}
#[test]
fn macro_succeeds_on_match() {
let c = capture();
Log::info("hello")
.component("svc")
.with("k", 1_u64)
.log_to(&c);
assert_logged!(c, level == LogLevel::INFO);
assert_logged!(c, contains "hello");
assert_logged!(c, component "svc");
assert_logged!(c, attribute "k" => 1_u64);
assert_logged!(c, len == 1);
}
#[test]
#[should_panic(expected = "expected a captured record at level")]
fn macro_panics_on_missing_level() {
let c = capture();
Log::info("x").log_to(&c);
assert_logged!(c, level == LogLevel::ERROR);
}
#[test]
#[should_panic(expected = "expected 3 captured records")]
fn macro_panics_on_wrong_count() {
let c = capture();
Log::info("x").log_to(&c);
assert_logged!(c, len == 3);
}
#[test]
fn capture_handle_is_cheap_to_clone() {
let a = capture();
let b = a.clone();
Log::info("x").log_to(&a);
assert_eq!(b.len(), 1, "clones share the same buffer");
}
}