open_feature/hooks/
logging.rs

1use crate::{EvaluationContext, EvaluationDetails, EvaluationError, Value};
2
3use super::{Hook, HookContext, HookHints};
4
5use log::Level;
6
7/// A hook that logs the evaluation lifecycle of a flag.
8/// See the [spec](https://github.com/open-feature/spec/blob/main/specification/appendix-a-included-utilities.md#logging-hook)
9#[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            // Single statement to avoid borrowing issues
154            // See issue https://github.com/rust-lang/rust/issues/92698
155            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}