diff --git a/src/audit.rs b/src/audit.rs
index 1622ad3e4984a9ff217f35333be640c54af142f5..5d9019fb9fc565e004153554c82efdcf6ac61958 100644
--- a/src/audit.rs
+++ b/src/audit.rs
@@ -270,12 +270,49 @@ pub fn root() -> Option<&'static slog::Logger> {
     ROOT.get()
 }
 
-// TODO: enforce entry format by introducing new macro arguments (e.g. `severity:`).
+::tarantool::define_str_enum! {
+    /// Type-safe entry severity for use in [`crate::audit!`].
+    /// Severity levels and their usage are defined in the RFC.
+    pub enum Severity {
+        Low = "low",
+        Medium = "medium",
+        High = "high",
+    }
+}
+
+/// This is the main API for adding new entries to the audit log.
+/// The required fields are `message`, `title` and `severity`,
+/// the rest is up to the caller.
+///
+/// Example:
+/// ```
+/// # use picodata::audit;
+/// audit!(
+///     message: "hello, world!",
+///     title: "greeting",
+///     severity: Low,
+/// );
+/// ```
 #[macro_export]
 macro_rules! audit(
-    ($($args:tt)+) => {
+    (
+        message: $message:expr,
+        title: $title:expr,
+        severity: $severity:ident,
+        $($key:ident: $value:expr),* $(,)?
+    ) => {
         if let Some(root) = $crate::audit::root() {
-            slog::slog_log!(root, slog::Level::Info, "", $($args)*);
+            slog::slog_log!(
+                // Boilerplate required by slog.
+                root, slog::Level::Info, "",
+                // The message itself.
+                $message;
+                // Mandatory fields.
+                "title" => $title,
+                "severity" => $crate::audit::Severity::$severity.as_str(),
+                // Arbitrary fields.
+                $(stringify!($key) => $value,)*
+            );
         }
     };
 );
@@ -291,23 +328,23 @@ pub fn init(config: &str) {
         .expect("failed to initialize global audit drain");
 
     crate::audit!(
-        "audit log is ready";
-        "title" => "init_audit",
-        "severity" => "low",
+        message: "audit log is ready",
+        title: "init_audit",
+        severity: Low,
     );
 
     // Report a local startup event & register a trigger for a local shutdown event.
     // Those will only be seen in this exact instance's audit log (hence "local").
     crate::audit!(
-        "instance is starting";
-        "title" => "local_startup",
-        "severity" => "low",
+        message: "instance is starting",
+        title: "local_startup",
+        severity: Low,
     );
     ::tarantool::trigger::on_shutdown(|| {
         crate::audit!(
-            "instance is shutting down";
-            "title" => "local_shutdown",
-            "severity" => "high",
+            message: "instance is shutting down",
+            title: "local_shutdown",
+            severity: High,
         );
     })
     .expect("failed to install audit trigger for instance shutdown");
diff --git a/src/lib.rs b/src/lib.rs
index 18913844fed9160ceb104cb1cc5d725796c34f02..c397ac3bd69a3a1c65c3777e14803d3e4846e121 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -30,7 +30,7 @@ use crate::tier::{Tier, DEFAULT_TIER};
 use crate::traft::op;
 use crate::util::{unwrap_or_terminate, validate_and_complete_unix_socket_path};
 
-mod audit;
+pub mod audit;
 mod bootstrap_entries;
 pub mod cas;
 pub mod cli;
diff --git a/src/storage.rs b/src/storage.rs
index 14b975d007ddeac9b8c49d453dea3b140c2a3ae2..1e9c57e248f7db26c0e93532aa5377b399aacbd0 100644
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -2665,10 +2665,10 @@ pub mod acl {
 
         let user = &user_def.name;
         crate::audit!(
-            "created user `{user}`";
-            "title" => "create_user",
-            "severity" => "high",
-            "auth_type" => user_def.auth.method.as_str(),
+            message: "created user `{user}`",
+            title: "create_user",
+            severity: High,
+            auth_type: user_def.auth.method.as_str(),
         );
 
         Ok(())
@@ -2685,10 +2685,10 @@ pub mod acl {
         let user_def = storage.users.by_id(user_id)?.expect("failed to get user");
         let user = &user_def.name;
         crate::audit!(
-            "password of user `{user}` was changed";
-            "title" => "change_password",
-            "severity" => "high",
-            "auth_type" => auth.method.as_str(),
+            message: "password of user `{user}` was changed",
+            title: "change_password",
+            severity: High,
+            auth_type: auth.method.as_str(),
         );
 
         Ok(())
@@ -2703,9 +2703,9 @@ pub mod acl {
 
         let user = &user_def.name;
         crate::audit!(
-            "dropped user `{user}`";
-            "severity" => "medium",
-            "title" => "drop_user",
+            message: "dropped user `{user}`",
+            title: "drop_user",
+            severity: Medium,
         );
 
         Ok(())
@@ -2717,9 +2717,9 @@ pub mod acl {
 
         let role = &role_def.name;
         crate::audit!(
-            "created role `{role}`";
-            "title" => "create_role",
-            "severity" => "high",
+            message: "created role `{role}`",
+            title: "create_role",
+            severity: High,
         );
 
         Ok(())
@@ -2739,9 +2739,9 @@ pub mod acl {
 
             let role = &role_def.name;
             crate::audit!(
-                "dropped role `{role}`";
-                "title" => "drop_role",
-                "severity" => "medium",
+                message: "dropped role `{role}`",
+                title: "drop_role",
+                severity: Medium,
             );
         }
 
@@ -2762,17 +2762,17 @@ pub mod acl {
         match (privilege.as_str(), object_type.as_str()) {
             ("execute", "role") => {
                 crate::audit!(
-                    "granted role `{object}` to {grantee_type} `{grantee}`";
-                    "title" => "grant_role",
-                    "severity" => "high",
+                    message: "granted role `{object}` to {grantee_type} `{grantee}`",
+                    title: "grant_role",
+                    severity: High,
                 );
             }
             _ => {
                 crate::audit!(
-                    "granted privilege {privilege} on {object_type} `{object}` \
-                     to {grantee_type} `{grantee}`";
-                    "title" => "grant_privilege",
-                    "severity" => "high",
+                    message: "granted privilege {privilege} on {object_type} `{object}` \
+                              to {grantee_type} `{grantee}`",
+                    title: "grant_privilege",
+                    severity: High,
                 );
             }
         }
@@ -2800,17 +2800,17 @@ pub mod acl {
         match (privilege.as_str(), object_type.as_str()) {
             ("execute", "role") => {
                 crate::audit!(
-                    "revoke role `{object}` from {grantee_type} `{grantee}`";
-                    "title" => "revoke_role",
-                    "severity" => "high",
+                    message: "revoke role `{object}` from {grantee_type} `{grantee}`",
+                    title: "revoke_role",
+                    severity: High,
                 );
             }
             _ => {
                 crate::audit!(
-                    "revoked privilege {privilege} on {object_type} `{object}` \
-                     from {grantee_type} `{grantee}`";
-                    "title" => "revoke_privilege",
-                    "severity" => "high",
+                    message: "revoked privilege {privilege} on {object_type} `{object}` \
+                              from {grantee_type} `{grantee}`",
+                    title: "revoke_privilege",
+                    severity: High,
                 );
             }
         }
diff --git a/src/traft/node.rs b/src/traft/node.rs
index 8dd0f37243e8da58cf94f7329f9ffde5e3a1ce17..dff31a071b11e381b4e516dedd82985ffbbf301e 100644
--- a/src/traft/node.rs
+++ b/src/traft/node.rs
@@ -680,9 +680,9 @@ impl NodeImpl {
                         if prev.as_ref().map(|x| x.raft_id) != Some(new.raft_id) {
                             let instance_id = &new.instance_id;
                             crate::audit!(
-                                "added a new instance `{instance_id}` to the cluster";
-                                "title" => "create_database",
-                                "severity" => "low",
+                                message: "added a new instance `{instance_id}` to the cluster",
+                                title: "create_database",
+                                severity: Low,
                             );
                         }
 
@@ -690,9 +690,9 @@ impl NodeImpl {
                             let instance_id = &new.instance_id;
                             let grade = &new.current_grade;
                             crate::audit!(
-                                "current grade of instance `{instance_id}` changed to {grade}";
-                                "title" => "change_current_grade",
-                                "severity" => "medium",
+                                message: "current grade of instance `{instance_id}` changed to {grade}",
+                                title: "change_current_grade",
+                                severity: Medium,
                             );
                         }
 
@@ -700,9 +700,9 @@ impl NodeImpl {
                             let instance_id = &new.instance_id;
                             let grade = &new.target_grade;
                             crate::audit!(
-                                "target grade of instance `{instance_id}` changed to {grade}";
-                                "title" => "change_target_grade",
-                                "severity" => "low",
+                                message: "target grade of instance `{instance_id}` changed to {grade}",
+                                title: "change_target_grade",
+                                severity: Low,
                             );
                         }
 
@@ -787,9 +787,9 @@ impl NodeImpl {
                             .expect("storage shouldn't fail");
 
                         crate::audit!(
-                            "created table `{name}`";
-                            "title" => "create_table",
-                            "severity" => "medium",
+                            message: "created table `{name}`",
+                            title: "create_table",
+                            severity: Medium,
                         );
                     }
 
@@ -800,9 +800,9 @@ impl NodeImpl {
 
                         let name = &space.name;
                         crate::audit!(
-                            "dropped table `{name}`";
-                            "title" => "drop_table",
-                            "severity" => "medium",
+                            message: "dropped table `{name}`",
+                            title: "drop_table",
+                            severity: Medium,
                         );
                     }