use crate::{EvaluationContext, EvaluationDetails, EvaluationError, Value};
use super::{Hook, HookContext, HookHints};
use log::Level;
#[derive(Default)]
pub struct LoggingHook {
pub(crate) include_evaluation_context: bool,
}
#[async_trait::async_trait]
impl Hook for LoggingHook {
async fn before<'a>(
&self,
context: &HookContext<'a>,
_: Option<&'a HookHints>,
) -> Result<Option<EvaluationContext>, EvaluationError> {
self.log_before(context, Level::Debug);
Ok(None)
}
async fn after<'a>(
&self,
context: &HookContext<'a>,
value: &EvaluationDetails<Value>,
_: Option<&'a HookHints>,
) -> Result<(), EvaluationError> {
self.log_after(context, value, Level::Debug);
Ok(())
}
async fn error<'a>(
&self,
context: &HookContext<'a>,
error: &EvaluationError,
_: Option<&'a HookHints>,
) {
self.log_error(context, error);
}
async fn finally<'a>(
&self,
_: &HookContext<'a>,
_: &EvaluationDetails<Value>,
_: Option<&'a HookHints>,
) {
}
}
#[cfg(not(feature = "structured-logging"))]
impl LoggingHook {
fn log_args(
&self,
msg: &str,
context: &HookContext,
level: Level,
additional_args: std::fmt::Arguments,
) {
log::log!(
level,
"{}: domain={}, provider_name={}, flag_key={}, default_value={:?}{additional_args}{}",
msg,
context.client_metadata.name,
context.provider_metadata.name,
context.flag_key,
context.default_value,
if self.include_evaluation_context {
format!(", evaluation_context={:?}", context.evaluation_context)
} else {
String::new()
},
);
}
fn log_before(&self, context: &HookContext, level: Level) {
self.log_args("Before stage", context, level, format_args!(""));
}
fn log_after(&self, context: &HookContext, value: &EvaluationDetails<Value>, level: Level) {
self.log_args(
"After stage",
context,
level,
format_args!(
", reason={:?}, variant={:?}, value={:?}",
value.reason, value.variant, value.value
),
);
}
fn log_error(&self, context: &HookContext, error: &EvaluationError) {
self.log_args(
"Error stage",
context,
Level::Error,
format_args!(", error_message={:?}", error.message),
);
}
}
#[cfg(feature = "structured-logging")]
mod structured {
use super::*;
use log::{kv::Value as LogValue, Level, Record};
const DOMAIN_KEY: &str = "domain";
const PROVIDER_NAME_KEY: &str = "provider_name";
const FLAG_KEY_KEY: &str = "flag_key";
const DEFAULT_VALUE_KEY: &str = "default_value";
const EVALUATION_CONTEXT_KEY: &str = "evaluation_context";
const ERROR_MESSAGE_KEY: &str = "error_message";
const REASON_KEY: &str = "reason";
const VARIANT_KEY: &str = "variant";
const VALUE_KEY: &str = "value";
impl LoggingHook {
fn log_args(
&self,
msg: &str,
context: &HookContext,
level: Level,
additional_kvs: Vec<(&str, LogValue)>,
) {
let mut kvs = vec![
(
DOMAIN_KEY,
LogValue::from_display(&context.client_metadata.name),
),
(
PROVIDER_NAME_KEY,
LogValue::from_display(&context.provider_metadata.name),
),
(FLAG_KEY_KEY, LogValue::from_display(&context.flag_key)),
(
DEFAULT_VALUE_KEY,
LogValue::from_debug(&context.default_value),
),
];
kvs.extend(additional_kvs);
if self.include_evaluation_context {
kvs.push((
EVALUATION_CONTEXT_KEY,
LogValue::from_debug(&context.evaluation_context),
));
}
let kvs = kvs.as_slice();
log::logger().log(
&Record::builder()
.args(format_args!("{}", msg))
.level(level)
.target("open_feature")
.module_path_static(Some(module_path!()))
.file_static(Some(file!()))
.line(Some(line!()))
.key_values(&kvs)
.build(),
);
}
pub(super) fn log_before(&self, context: &HookContext, level: Level) {
self.log_args("Before stage", context, level, vec![]);
}
pub(super) fn log_after(
&self,
context: &HookContext,
value: &EvaluationDetails<Value>,
level: Level,
) {
self.log_args(
"After stage",
context,
level,
evaluation_details_to_kvs(value),
);
}
pub(super) fn log_error(&self, context: &HookContext, error: &EvaluationError) {
self.log_args("Error stage", context, Level::Error, error_to_kvs(error));
}
}
fn evaluation_details_to_kvs<'a>(
details: &'a EvaluationDetails<Value>,
) -> Vec<(&'static str, LogValue<'a>)> {
let kvs = vec![
(REASON_KEY, LogValue::from_debug(&details.reason)),
(VARIANT_KEY, LogValue::from_debug(&details.variant)),
(VALUE_KEY, LogValue::from_debug(&details.value)),
];
kvs
}
fn error_to_kvs<'a>(error: &'a EvaluationError) -> Vec<(&'static str, LogValue<'a>)> {
let kvs = vec![(ERROR_MESSAGE_KEY, LogValue::from_debug(&error.message))];
kvs
}
}