Skip to main content

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}