1use crate::{EvaluationContext, EvaluationDetails, EvaluationError, Value};
2
3use super::{Hook, HookContext, HookHints};
4
5use log::Level;
6
7#[derive(Default)]
10pub struct LoggingHook {
11 pub(crate) include_evaluation_context: bool,
12}
13
14#[async_trait::async_trait]
15impl Hook for LoggingHook {
16 async fn before<'a>(
17 &self,
18 context: &HookContext<'a>,
19 _: Option<&'a HookHints>,
20 ) -> Result<Option<EvaluationContext>, EvaluationError> {
21 self.log_before(context, Level::Debug);
22
23 Ok(None)
24 }
25 async fn after<'a>(
26 &self,
27 context: &HookContext<'a>,
28 value: &EvaluationDetails<Value>,
29 _: Option<&'a HookHints>,
30 ) -> Result<(), EvaluationError> {
31 self.log_after(context, value, Level::Debug);
32
33 Ok(())
34 }
35 async fn error<'a>(
36 &self,
37 context: &HookContext<'a>,
38 error: &EvaluationError,
39 _: Option<&'a HookHints>,
40 ) {
41 self.log_error(context, error);
42 }
43 async fn finally<'a>(
44 &self,
45 _: &HookContext<'a>,
46 _: &EvaluationDetails<Value>,
47 _: Option<&'a HookHints>,
48 ) {
49 }
50}
51
52#[cfg(not(feature = "structured-logging"))]
53impl LoggingHook {
54 fn log_args(
55 &self,
56 msg: &str,
57 context: &HookContext,
58 level: Level,
59 additional_args: std::fmt::Arguments,
60 ) {
61 log::log!(
62 level,
63 "{}: domain={}, provider_name={}, flag_key={}, default_value={:?}{additional_args}{}",
64 msg,
65 context.client_metadata.name,
66 context.provider_metadata.name,
67 context.flag_key,
68 context.default_value,
69 if self.include_evaluation_context {
70 format!(", evaluation_context={:?}", context.evaluation_context)
71 } else {
72 String::new()
73 },
74 );
75 }
76
77 fn log_before(&self, context: &HookContext, level: Level) {
78 self.log_args("Before stage", context, level, format_args!(""));
79 }
80
81 fn log_after(&self, context: &HookContext, value: &EvaluationDetails<Value>, level: Level) {
82 self.log_args(
83 "After stage",
84 context,
85 level,
86 format_args!(
87 ", reason={:?}, variant={:?}, value={:?}",
88 value.reason, value.variant, value.value
89 ),
90 );
91 }
92
93 fn log_error(&self, context: &HookContext, error: &EvaluationError) {
94 self.log_args(
95 "Error stage",
96 context,
97 Level::Error,
98 format_args!(", error_message={:?}", error.message),
99 );
100 }
101}
102
103#[cfg(feature = "structured-logging")]
104mod structured {
105 use super::*;
106 use log::{kv::Value as LogValue, Level, Record};
107
108 const DOMAIN_KEY: &str = "domain";
109 const PROVIDER_NAME_KEY: &str = "provider_name";
110 const FLAG_KEY_KEY: &str = "flag_key";
111 const DEFAULT_VALUE_KEY: &str = "default_value";
112 const EVALUATION_CONTEXT_KEY: &str = "evaluation_context";
113 const ERROR_MESSAGE_KEY: &str = "error_message";
114 const REASON_KEY: &str = "reason";
115 const VARIANT_KEY: &str = "variant";
116 const VALUE_KEY: &str = "value";
117
118 impl LoggingHook {
119 fn log_args(
120 &self,
121 msg: &str,
122 context: &HookContext,
123 level: Level,
124 additional_kvs: Vec<(&str, LogValue)>,
125 ) {
126 let mut kvs = vec![
127 (
128 DOMAIN_KEY,
129 LogValue::from_display(&context.client_metadata.name),
130 ),
131 (
132 PROVIDER_NAME_KEY,
133 LogValue::from_display(&context.provider_metadata.name),
134 ),
135 (FLAG_KEY_KEY, LogValue::from_display(&context.flag_key)),
136 (
137 DEFAULT_VALUE_KEY,
138 LogValue::from_debug(&context.default_value),
139 ),
140 ];
141
142 kvs.extend(additional_kvs);
143
144 if self.include_evaluation_context {
145 kvs.push((
146 EVALUATION_CONTEXT_KEY,
147 LogValue::from_debug(&context.evaluation_context),
148 ));
149 }
150
151 let kvs = kvs.as_slice();
152
153 log::logger().log(
156 &Record::builder()
157 .args(format_args!("{}", msg))
158 .level(level)
159 .target("open_feature")
160 .module_path_static(Some(module_path!()))
161 .file_static(Some(file!()))
162 .line(Some(line!()))
163 .key_values(&kvs)
164 .build(),
165 );
166 }
167
168 pub(super) fn log_before(&self, context: &HookContext, level: Level) {
169 self.log_args("Before stage", context, level, vec![]);
170 }
171
172 pub(super) fn log_after(
173 &self,
174 context: &HookContext,
175 value: &EvaluationDetails<Value>,
176 level: Level,
177 ) {
178 self.log_args(
179 "After stage",
180 context,
181 level,
182 evaluation_details_to_kvs(value),
183 );
184 }
185
186 pub(super) fn log_error(&self, context: &HookContext, error: &EvaluationError) {
187 self.log_args("Error stage", context, Level::Error, error_to_kvs(error));
188 }
189 }
190
191 fn evaluation_details_to_kvs<'a>(
192 details: &'a EvaluationDetails<Value>,
193 ) -> Vec<(&'static str, LogValue<'a>)> {
194 let kvs = vec![
195 (REASON_KEY, LogValue::from_debug(&details.reason)),
196 (VARIANT_KEY, LogValue::from_debug(&details.variant)),
197 (VALUE_KEY, LogValue::from_debug(&details.value)),
198 ];
199
200 kvs
201 }
202
203 fn error_to_kvs<'a>(error: &'a EvaluationError) -> Vec<(&'static str, LogValue<'a>)> {
204 let kvs = vec![(ERROR_MESSAGE_KEY, LogValue::from_debug(&error.message))];
205
206 kvs
207 }
208}