#![deny(missing_docs)]
use rlg::log::Log;
use serde_json::Value;
pub trait Enricher {
#[must_use]
fn enrich(&self, log: Log) -> Log;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct ProcessEnricher;
impl ProcessEnricher {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl Enricher for ProcessEnricher {
fn enrich(&self, mut log: Log) -> Log {
log.attributes.insert(
"pid".into(),
Value::from(u64::from(std::process::id())),
);
#[cfg(unix)]
{
log.attributes
.insert("tid".into(), Value::from(current_tid()));
log.attributes
.insert("uid".into(), Value::from(current_uid()));
}
log
}
}
#[cfg(unix)]
#[allow(unsafe_code)]
mod unix_ffi {
pub(super) fn current_tid() -> u64 {
#[cfg(target_os = "linux")]
{
unsafe { libc::syscall(libc::SYS_gettid) as u64 }
}
#[cfg(not(target_os = "linux"))]
{
unsafe { libc::pthread_self() as u64 }
}
}
pub(super) fn current_uid() -> u64 {
u64::from(unsafe { libc::getuid() })
}
}
#[cfg(unix)]
use unix_ffi::{current_tid, current_uid};
#[cfg(feature = "ebpf")]
#[cfg_attr(docsrs, doc(cfg(feature = "ebpf")))]
#[derive(Debug, Default, Clone, Copy)]
pub struct EbpfEnricher {
_reserved: (),
}
#[cfg(feature = "ebpf")]
impl EbpfEnricher {
#[must_use]
pub const fn new() -> Self {
Self { _reserved: () }
}
}
#[cfg(feature = "ebpf")]
impl Enricher for EbpfEnricher {
fn enrich(&self, log: Log) -> Log {
ProcessEnricher::new().enrich(log)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Chain<A, B> {
pub first: A,
pub second: B,
}
impl<A, B> Chain<A, B> {
#[must_use]
pub const fn new(first: A, second: B) -> Self {
Self { first, second }
}
}
impl<A: Enricher, B: Enricher> Enricher for Chain<A, B> {
fn enrich(&self, log: Log) -> Log {
self.second.enrich(self.first.enrich(log))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn process_enricher_adds_pid() {
let e = ProcessEnricher::new();
let out = e.enrich(Log::info("test"));
let pid = out.attributes.get("pid").unwrap();
assert!(pid.is_u64());
assert!(pid.as_u64().unwrap() > 0);
}
#[cfg(unix)]
#[test]
fn process_enricher_adds_tid_and_uid_on_unix() {
let e = ProcessEnricher::new();
let out = e.enrich(Log::info("test"));
let tid = out.attributes.get("tid").unwrap();
assert!(tid.is_u64());
assert!(tid.as_u64().unwrap() > 0);
assert!(out.attributes.contains_key("uid"));
}
#[test]
fn process_enricher_preserves_original_attributes() {
let e = ProcessEnricher::new();
let log = Log::info("test").with("order_id", 42_u64);
let out = e.enrich(log);
assert_eq!(
out.attributes.get("order_id").unwrap().as_u64(),
Some(42)
);
assert!(out.attributes.contains_key("pid"));
}
#[test]
fn process_enricher_new_and_default_are_both_constructible() {
let _new = ProcessEnricher::new();
#[allow(clippy::default_constructed_unit_structs)]
let _default: ProcessEnricher = ProcessEnricher::default();
}
#[test]
fn chain_applies_both_enrichers() {
struct AddFoo;
impl Enricher for AddFoo {
fn enrich(&self, mut log: Log) -> Log {
log.attributes.insert("foo".into(), Value::from("bar"));
log
}
}
struct AddBaz;
impl Enricher for AddBaz {
fn enrich(&self, mut log: Log) -> Log {
log.attributes.insert("baz".into(), Value::from("qux"));
log
}
}
let chain = Chain::new(AddFoo, AddBaz);
let out = chain.enrich(Log::info("test"));
assert_eq!(
out.attributes.get("foo").unwrap().as_str(),
Some("bar")
);
assert_eq!(
out.attributes.get("baz").unwrap().as_str(),
Some("qux")
);
}
#[test]
fn chain_composes_with_process_enricher() {
struct AddTag;
impl Enricher for AddTag {
fn enrich(&self, mut log: Log) -> Log {
log.attributes
.insert("service".into(), Value::from("api"));
log
}
}
let chain = Chain::new(ProcessEnricher::new(), AddTag);
let out = chain.enrich(Log::info("test"));
assert!(out.attributes.contains_key("pid"));
assert_eq!(
out.attributes.get("service").unwrap().as_str(),
Some("api")
);
}
#[cfg(feature = "ebpf")]
#[test]
fn ebpf_enricher_scaffold_delegates_to_process() {
let e = EbpfEnricher::new();
let out = e.enrich(Log::info("test"));
assert!(out.attributes.contains_key("pid"));
}
}