use std::fmt;
use serde::Serialize;
use serde_json::Value as JsonValue;
use slog::{Key, Record, Result as SlogResult, Serializer, Value as SlogValue};
pub use crate::redaction::RedactedJson;
use crate::{
policy::RedactionPolicy,
redaction::{
NotSensitive, NotSensitiveDebug, NotSensitiveDisplay, NotSensitiveJson, Redactable,
RedactableWithFormatter, RedactedJsonRef, RedactedOutput, RedactedOutputRef,
SensitiveValue, SensitiveWithPolicy, ToRedactedOutput,
},
};
pub trait SlogRedacted: SlogValue {}
impl<T: SlogRedacted + ?Sized> SlogRedacted for &T {}
impl SlogValue for RedactedJson {
fn serialize(
&self,
record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
let nested = slog::Serde(self.value().clone());
SlogValue::serialize(&nested, record, key, serializer)
}
}
impl SlogRedacted for RedactedJson {}
fn emit_output(
output: &RedactedOutput,
record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
match output {
RedactedOutput::Text(text) => serializer.emit_str(key, text),
#[cfg(feature = "json")]
RedactedOutput::Json(json) => {
let nested = slog::Serde(json.clone());
SlogValue::serialize(&nested, record, key, serializer)
}
}
}
macro_rules! impl_slog_redacted {
(@ [$($gen:ident),+] $ty:ty where $($bounds:tt)+) => {
impl<$($gen),+> SlogValue for $ty where $($bounds)+ {
fn serialize(
&self,
record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
emit_output(&self.to_redacted_output(), record, key, serializer)
}
}
impl<$($gen),+> SlogRedacted for $ty where $($bounds)+ {}
};
($ty:ty) => {
impl SlogValue for $ty {
fn serialize(
&self,
record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
emit_output(&self.to_redacted_output(), record, key, serializer)
}
}
impl SlogRedacted for $ty {}
};
}
impl_slog_redacted!(RedactedOutput);
impl_slog_redacted!(@ [T, P] SensitiveValue<T, P> where T: SensitiveWithPolicy<P>, P: RedactionPolicy);
impl_slog_redacted!(@ [T] NotSensitiveDisplay<T> where T: fmt::Display);
impl_slog_redacted!(@ [T] NotSensitiveDebug<T> where T: fmt::Debug);
impl_slog_redacted!(@ [T] NotSensitiveJson<'_, T> where T: Serialize + ?Sized);
impl_slog_redacted!(@ [T] RedactedOutputRef<'_, T> where T: Redactable + Clone + fmt::Debug);
impl_slog_redacted!(@ [T] RedactedJsonRef<'_, T> where T: Redactable + Clone + Serialize);
pub trait SlogRedactedExt: Redactable + fmt::Debug + Serialize + Sized {
fn slog_redacted_json(self) -> RedactedJson {
let redacted = self.redact();
let json_value = serde_json::to_value(redacted).unwrap_or_else(|err| {
JsonValue::String(format!("Failed to serialize redacted value: {err}"))
});
RedactedJson::new(json_value)
}
}
impl<T> SlogRedactedExt for T where T: Redactable + fmt::Debug + Serialize {}
impl<T> SlogValue for NotSensitive<T>
where
T: SlogValue,
{
fn serialize(
&self,
record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
self.0.serialize(record, key, serializer)
}
}
impl<T> SlogRedacted for NotSensitive<T> where T: SlogRedacted {}
#[doc(hidden)]
pub fn __slog_serialize_not_sensitive<T: Serialize>(
value: &T,
record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
let json_value = serde_json::to_value(value)
.unwrap_or_else(|err| JsonValue::String(format!("Failed to serialize value: {err}")));
let nested = slog::Serde(json_value);
SlogValue::serialize(&nested, record, key, serializer)
}
pub struct RedactedDisplayValue<'a, T: ?Sized>(&'a T);
impl<'a, T: ?Sized> RedactedDisplayValue<'a, T> {
pub fn new(value: &'a T) -> Self {
Self(value)
}
}
impl<T> SlogValue for RedactedDisplayValue<'_, T>
where
T: RedactableWithFormatter,
{
fn serialize(
&self,
_record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> SlogResult {
let redacted = self.0.redacted_display();
serializer.emit_arguments(key, &format_args!("{redacted}"))
}
}
impl<T> SlogRedacted for RedactedDisplayValue<'_, T> where T: RedactableWithFormatter {}
pub trait SlogRedactedDisplayExt: RedactableWithFormatter {
fn slog_redacted_display(&self) -> RedactedDisplayValue<'_, Self>
where
Self: Sized,
{
RedactedDisplayValue::new(self)
}
}
impl<T> SlogRedactedDisplayExt for T where T: RedactableWithFormatter {}
#[cfg(feature = "tracing")]
impl<T> crate::tracing::TracingRedacted for RedactedDisplayValue<'_, T> where
T: RedactableWithFormatter
{
}