cedar_local_agent/public/log/
mod.rs

1//! Contains various logging helpers.
2use cedar_policy::Entities;
3use derive_builder::Builder;
4
5pub mod error;
6pub mod event;
7pub mod schema;
8
9/// The default requester string will be used in the log line. Unless specified otherwise,
10/// all authorizers will employ this name as their default.
11pub const DEFAULT_REQUESTER_NAME: &str = "cedar::simple::authorizer";
12
13/// This structure is used to configure the logging system by specifying the log file directory,
14/// log file name prefix, log rotation strategy, log rotation format, and meter provider.
15#[derive(Debug, Clone, Builder)]
16#[builder(setter(into))]
17pub struct Config {
18    /// `format` is used to specify the log rotation format.
19    /// By default the log rotation format is `OpenCyberSecurityFramework` (OCSF).
20    #[builder(default)]
21    pub format: Format,
22
23    /// A set specifying which fields are opted-in to be logged
24    #[builder(default)]
25    pub field_set: FieldSet,
26
27    /// A requester name is used to provide the name of the product and separate the log from
28    /// different authorizers
29    #[builder(default = "DEFAULT_REQUESTER_NAME.to_string()")]
30    pub requester: String,
31}
32
33impl Default for Config {
34    fn default() -> Self {
35        Self {
36            format: Format::default(),
37            requester: DEFAULT_REQUESTER_NAME.to_string(),
38            field_set: FieldSet::default(),
39        }
40    }
41}
42
43/// This enum is used to specify the log rotation format.
44/// The log rotation format is `OpenCyberSecurityFramework` (OCSF)
45/// <https://schema.ocsf.io/1.0.0/?extensions=>
46#[derive(Default, Eq, PartialEq, Debug, Clone)]
47#[non_exhaustive]
48pub enum Format {
49    /// Default Open Cyber Security Framework format.
50    #[default]
51    OpenCyberSecurityFramework,
52    /// For potentially adding future fields.
53    #[non_exhaustive]
54    Unknown,
55}
56
57#[derive(Builder, Eq, PartialEq, Debug, Clone, Default)]
58#[builder(pattern = "owned", default, setter(into))]
59#[allow(clippy::struct_excessive_bools)]
60/// The `FieldSet` specifies which fields are to be logged
61pub struct FieldSet {
62    /// A boolean specifying whether the principal field is to be logged
63    pub principal: bool,
64    /// A boolean specifying whether the resource field is to be logged
65    pub resource: bool,
66    /// A boolean specifying whether the action field is to be logged
67    pub action: bool,
68    /// A boolean specifying whether the context field is to be logged
69    pub context: bool,
70    /// A variant of the `FieldLevel` enum specifying which entity fields are to be logged
71    pub entities: FieldLevel<Entities>,
72}
73
74/// The `FieldLevel` enum is used to specify which fields are to be logged from a entities object.
75/// Choices include `None`, `All`, and `Custom`.
76/// The default is `None`.
77#[derive(Default, Eq, PartialEq, Debug, Clone)]
78#[non_exhaustive]
79pub enum FieldLevel<T> {
80    #[default]
81    /// No fields are logged
82    None,
83    /// All fields are logged
84    All,
85    /// Only the provided set of fields are logged.
86    Custom(fn(obj: &T) -> T),
87    /// For potentially adding future fields.
88    #[non_exhaustive]
89    Unknown,
90}
91
92#[cfg(test)]
93mod test {
94    use cedar_policy::Entities;
95
96    use crate::public::log::{
97        ConfigBuilder, FieldLevel, FieldSet, FieldSetBuilder, Format, DEFAULT_REQUESTER_NAME,
98    };
99
100    #[test]
101    fn configuration_default() {
102        let default_log_configuration = ConfigBuilder::default().build().unwrap();
103        assert_eq!(
104            default_log_configuration.format,
105            Format::OpenCyberSecurityFramework
106        );
107        assert_eq!(default_log_configuration.requester, DEFAULT_REQUESTER_NAME);
108        assert_eq!(default_log_configuration.field_set, FieldSet::default());
109    }
110
111    #[test]
112    fn configuration_equal() {
113        let log_configuration_one = ConfigBuilder::default().build().unwrap();
114        let log_configuration_two = ConfigBuilder::default().build().unwrap();
115        let log_configuration_three = ConfigBuilder::default()
116            .format(Format::OpenCyberSecurityFramework)
117            .field_set(FieldSetBuilder::default().principal(true).build().unwrap())
118            .build()
119            .unwrap();
120
121        assert_eq!(log_configuration_one.format, log_configuration_two.format);
122        assert_eq!(
123            log_configuration_one.field_set,
124            log_configuration_two.field_set
125        );
126        assert_ne!(
127            log_configuration_one.field_set,
128            log_configuration_three.field_set
129        );
130    }
131
132    #[test]
133    fn configuration_set_property() {
134        let field_set = FieldSetBuilder::default().principal(true).build().unwrap();
135        let log_configuration = ConfigBuilder::default()
136            .format(Format::OpenCyberSecurityFramework)
137            .field_set(field_set.clone())
138            .build()
139            .unwrap();
140
141        assert_eq!(log_configuration.format, Format::OpenCyberSecurityFramework);
142        assert_eq!(log_configuration.field_set, field_set);
143    }
144
145    #[test]
146    fn field_set_including_all_values_builds() {
147        assert!(FieldSetBuilder::default()
148            .principal(true)
149            .resource(true)
150            .action(true)
151            .context(true)
152            .entities(FieldLevel::All)
153            .build()
154            .is_ok());
155    }
156
157    #[test]
158    fn field_set_including_only_default_values_builds() {
159        let result = FieldSetBuilder::default().build();
160        assert!(result.is_ok());
161        assert_eq!(result.unwrap(), FieldSet::default());
162    }
163
164    #[test]
165    fn field_set_including_custom_values_builds() {
166        let filter_fn = |entities: &Entities| -> Entities { entities.clone() };
167        let result = FieldSetBuilder::default()
168            .principal(false)
169            .resource(true)
170            .action(false)
171            .context(false)
172            .entities(FieldLevel::Custom(filter_fn))
173            .build();
174        assert!(result.is_ok());
175
176        let expected = FieldSet {
177            principal: false, // explicitly false case
178            resource: true,   // explicitly true case
179            action: false,    // implicitly false case
180            context: false,
181            entities: FieldLevel::Custom(filter_fn),
182        };
183        assert_eq!(result.unwrap(), expected);
184    }
185
186    #[test]
187    fn field_set_defaults() {
188        let field_set = FieldSet::default();
189        assert!(!field_set.context);
190        assert!(!field_set.principal);
191        assert!(!field_set.action);
192        assert!(!field_set.resource);
193        assert_eq!(field_set.entities, FieldLevel::None);
194    }
195}