quorum_core/db/queries/server_logs.rs
1//! File to handle logging server events
2//!
3//! This module defines functions to log different types of server events (startup, shutdown, errors) into the database.
4//! It uses a caching mechanism to minimize database queries for event type IDs, ensuring efficient logging.
5
6use crate::db::DB;
7use std::error::Error;
8use std::sync::OnceLock;
9use surrealdb_types::{RecordId, SurrealValue};
10
11static STARTUP_ID: OnceLock<RecordId> = OnceLock::new();
12static SHUTDOWN_ID: OnceLock<RecordId> = OnceLock::new();
13static ERROR_ID: OnceLock<RecordId> = OnceLock::new();
14
15#[derive(Debug, SurrealValue)]
16struct EventTypeRecord {
17 id: RecordId,
18}
19
20/// Gets the event type ID for the given event name, creating a new event type if it doesn't already exist.
21///
22/// This function performs a database query to check if the event type already exists. If it does, it returns the existing ID.
23/// If it doesn't exist, it creates a new event type and returns the new ID.
24///
25/// # Arguments
26/// * `db` - A reference to the database connection.
27/// * `event_name` - The name of the event type to get or create.
28///
29/// # Returns
30/// * `Ok(RecordId)` - The ID of the event type, either existing or newly created.
31/// * `Err(Box<dyn Error>)` - An error if the database query fails or if creating a new event type fails.
32///
33/// # Errors
34/// * "Failed to create event type" - If the database query to create a new event type does not return a valid record, indicating that the creation failed.
35async fn get_or_create_event_type(
36 db: &DB,
37 event_name: String,
38) -> Result<RecordId, Box<dyn Error + Send + Sync>> {
39 //Check if the event type already exists by trying to get it.
40 let mut response = db
41 .query("SELECT id FROM server_log_event_types WHERE name = $name")
42 .bind(("name", event_name.clone()))
43 .await?;
44
45 let result: Option<EventTypeRecord> = response.take(0)?;
46
47 //If event type exists already in `server_log_event_types` return its ID
48 if let Some(record) = result {
49 Ok(record.id)
50 } else {
51 //event type doesn't exist, create it then return its ID
52 let mut create_response = db
53 .query("CREATE server_log_event_types SET name = $name RETURN id")
54 .bind(("name", event_name))
55 .await?;
56
57 let created: Option<EventTypeRecord> = create_response.take(0)?;
58 Ok(created.ok_or("Failed to create event type")?.id)
59 }
60}
61
62/// Gets the cached event type ID for the given event name, using a OnceLock to cache the ID after the first retrieval.
63///
64/// This function first checks the provided OnceLock cache for the event type ID. If the ID is already cached, it returns it immediately.
65/// If the ID is not cached, it calls `get_or_create_event_type` to retrieve or create the event type ID from the database, caches it in the OnceLock, and then returns it.
66///
67/// # Arguments
68/// * `db` - A reference to the database connection.
69/// * `cache` - A reference to a OnceLock that will cache the event type ID after the first retrieval.
70/// * `event_name` - The name of the event type to get or create.
71///
72/// # Returns
73/// * `Ok(RecordId)` - The ID of the event type, either retrieved from the cache or obtained from the database.
74/// * `Err(Box<dyn Error + Send + Sync>)` - An error if the database query fails or if creating a new event type fails.
75///
76/// # Errors
77/// * "Failed to create event type" - If the database query to create a new event type does not return a valid record, indicating that the creation failed.
78async fn get_cached_event_type(
79 db: &DB,
80 cache: &OnceLock<RecordId>,
81 event_name: &str,
82) -> Result<RecordId, Box<dyn Error + Send + Sync>> {
83 if let Some(id) = cache.get() {
84 return Ok(id.clone());
85 }
86
87 let id = get_or_create_event_type(db, event_name.to_string()).await?;
88 let _ = cache.set(id.clone());
89 Ok(id)
90}
91
92/// Logs a startup event to the database with the given duration in milliseconds.
93///
94/// This function retrieves the event type ID for "startup" using the caching mechanism and then creates a new log entry in the `server_logs` table with the event type ID and duration.
95///
96/// # Arguments
97/// * `db` - A reference to the database connection.
98/// * `duration_ms` - The duration of the startup event in milliseconds.
99///
100/// # Returns
101/// * `Ok(())` - If the log entry was successfully created.
102/// * `Err(Box<dyn Error>)` - An error if the database query fails or if retrieving the event type ID fails.
103///
104/// # Errors
105/// * "Failed to create event type" - If the database query to create a new event type does not return a valid record, indicating that the creation failed.
106///
107/// # Example
108/// ```
109/// use crate::db::DB;
110/// use crate::db::queries::server_logs;
111/// async fn example_log_startup(db: &DB) {
112/// let duration_ms = 1500;
113/// match server_logs::log_startup(db, duration_ms).await {
114/// Ok(()) => println!("Startup event logged successfully"),
115/// Err(e) => eprintln!("Error logging startup event: {}", e),
116/// }
117/// }
118/// ```
119pub async fn log_startup(db: &DB, duration_ms: i64) -> Result<(), Box<dyn Error + Send + Sync>> {
120 let event_type_id = get_cached_event_type(db, &STARTUP_ID, "startup").await?;
121
122 db.query("CREATE server_logs SET event_type_id = $event_type_id, duration_ms = $duration_ms")
123 .bind(("event_type_id", event_type_id))
124 .bind(("duration_ms", duration_ms))
125 .await?;
126
127 Ok(())
128}
129
130/// Logs a shutdown event to the database with the given duration in milliseconds.
131///
132/// This function retrieves the event type ID for "shutdown" using the caching mechanism and then creates a new log entry in the `server_logs` table with the event type ID and duration.
133///
134/// # Arguments
135/// * `db` - A reference to the database connection.
136/// * `duration_ms` - The duration of the shutdown event in milliseconds.
137///
138/// # Returns
139/// * `Ok(())` - If the log entry was successfully created.
140/// * `Err(Box<dyn Error>)` - An error if the database query fails or if retrieving the event type ID fails.
141///
142/// # Errors
143/// * "Failed to create event type" - If the database query to create a new event type does not return a valid record, indicating that the creation failed.
144///
145/// # Example
146/// ```
147/// use crate::db::DB;
148/// use crate::db::queries::server_logs;
149/// async fn example_log_shutdown(db: &DB) {
150/// let duration_ms = 1200;
151/// match server_logs::log_shutdown(db, duration_ms).await {
152/// Ok(()) => println!("Shutdown event logged successfully"),
153/// Err(e) => eprintln!("Error logging shutdown event: {}", e),
154/// }
155/// }
156/// ```
157pub async fn log_shutdown(db: &DB, duration_ms: i64) -> Result<(), Box<dyn Error + Send + Sync>> {
158 let event_type_id = get_cached_event_type(db, &SHUTDOWN_ID, "shutdown").await?;
159
160 db.query("CREATE server_logs SET event_type_id = $event_type_id, duration_ms = $duration_ms")
161 .bind(("event_type_id", event_type_id))
162 .bind(("duration_ms", duration_ms))
163 .await?;
164
165 Ok(())
166}
167
168/// Logs an error event to the database with the given message and error code.
169///
170/// This function retrieves the event type ID for "error" using the caching mechanism and then creates a new log entry in the `server_logs` table with the event type ID, message, and error code.
171///
172/// # Arguments
173/// * `db` - A reference to the database connection.
174/// * `message` - A string describing the error message to log.
175/// * `error_code` - A numeric code representing the error to log.
176///
177/// # Returns
178/// * `Ok(())` - If the log entry was successfully created.
179/// * `Err(Box<dyn Error>)` - An error if the database query fails or if retrieving the event type ID fails.
180///
181/// # Errors
182/// * "Failed to create event type" - If the database query to create a new event type does not return a valid record, indicating that the creation failed.
183///
184/// # Example
185/// ```
186/// use crate::db::DB;
187/// use crate::db::queries::server_logs;
188/// async fn example_log_error(db: &DB) {
189/// let message = "An unexpected error occurred".to_string();
190/// let error_code = 500;
191/// match server_logs::log_error(db, message, error_code).await {
192/// Ok(()) => println!("Error event logged successfully"),
193/// Err(e) => eprintln!("Error logging error event: {}", e),
194/// }
195/// }
196/// ```
197pub async fn log_error(
198 db: &DB,
199 message: String,
200 error_code: u32,
201) -> Result<(), Box<dyn Error + Send + Sync>> {
202 let event_type_id = get_cached_event_type(db, &ERROR_ID, "error").await?;
203
204 db.query(
205 "CREATE server_logs SET event_type_id = $event_type_id, message = $message, error_code = $error_code",
206 )
207 .bind(("event_type_id", event_type_id))
208 .bind(("message", message))
209 .bind(("error_code", error_code))
210 .await?;
211
212 Ok(())
213}