feat(storage): refactor audit and notification with OperationHelper (#825)

* improve code for audit

* improve code ecfs.rs

* improve code

* improve code for ecfs.rs

* feat(storage): refactor audit and notification with OperationHelper

This commit introduces a significant refactoring of the audit logging and event notification mechanisms within `ecfs.rs`.

The core of this change is the new `OperationHelper` struct, which encapsulates and simplifies the logic for both concerns. It replaces the previous `AuditHelper` and manual event dispatching.

Key improvements include:

- **Unified Handling**: `OperationHelper` manages both audit and notification builders, providing a single, consistent entry point for S3 operations.
- **RAII for Automation**: By leveraging the `Drop` trait, the helper automatically dispatches logs and notifications when it goes out of scope. This simplifies S3 method implementations and ensures cleanup even on early returns.
- **Fluent API**: A builder-like pattern with methods such as `.object()`, `.version_id()`, and `.suppress_event()` makes the code more readable and expressive.
- **Context-Aware Logic**: The helper's `.complete()` method intelligently populates log details based on the operation's `S3Result` and only triggers notifications on success.
- **Modular Design**: All helper logic is now isolated in `rustfs/src/storage/helper.rs`, improving separation of concerns and making `ecfs.rs` cleaner.

This refactoring significantly enhances code clarity, reduces boilerplate, and improves the robustness of logging and notification handling across the storage layer.

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* improve code for audit and notify

* fix

* fix

* fix
This commit is contained in:
houseme
2025-11-10 17:30:50 +08:00
committed by GitHub
parent b26aad4129
commit 98be7df0f5
25 changed files with 905 additions and 835 deletions
+20 -9
View File
@@ -12,11 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use rustfs_targets::TargetError;
use rustfs_targets::arn::TargetID;
use rustfs_targets::{TargetError, arn::TargetID};
use std::io;
use thiserror::Error;
/// Errors related to the notification system's lifecycle.
#[derive(Debug, Error)]
pub enum LifecycleError {
/// Error indicating the system has already been initialized.
#[error("System has already been initialized")]
AlreadyInitialized,
/// Error indicating the system has not been initialized yet.
#[error("System has not been initialized")]
NotInitialized,
}
/// Error types for the notification system
#[derive(Debug, Error)]
pub enum NotificationError {
@@ -38,11 +49,8 @@ pub enum NotificationError {
#[error("Rule configuration error: {0}")]
RuleConfiguration(String),
#[error("System initialization error: {0}")]
Initialization(String),
#[error("Notification system has already been initialized")]
AlreadyInitialized,
#[error("System lifecycle error: {0}")]
Lifecycle(#[from] LifecycleError),
#[error("I/O error: {0}")]
Io(io::Error),
@@ -56,6 +64,9 @@ pub enum NotificationError {
#[error("Target '{0}' not found")]
TargetNotFound(TargetID),
#[error("Server not initialized")]
ServerNotInitialized,
#[error("System initialization error: {0}")]
Initialization(String),
#[error("Storage not available: {0}")]
StorageNotAvailable(String),
}
+117
View File
@@ -276,3 +276,120 @@ impl EventArgs {
self.req_params.contains_key("x-rustfs-source-replication-request")
}
}
/// Builder for [`EventArgs`].
///
/// This builder provides a fluent API to construct an `EventArgs` instance,
/// ensuring that all required fields are provided.
///
/// # Example
///
/// ```ignore
/// let args = EventArgsBuilder::new(
/// EventName::ObjectCreatedPut,
/// "my-bucket",
/// object_info,
/// )
/// .host("localhost:9000")
/// .user_agent("my-app/1.0")
/// .build();
/// ```
#[derive(Debug, Clone, Default)]
pub struct EventArgsBuilder {
event_name: EventName,
bucket_name: String,
object: rustfs_ecstore::store_api::ObjectInfo,
req_params: HashMap<String, String>,
resp_elements: HashMap<String, String>,
version_id: String,
host: String,
user_agent: String,
}
impl EventArgsBuilder {
/// Creates a new builder with the required fields.
pub fn new(event_name: EventName, bucket_name: impl Into<String>, object: rustfs_ecstore::store_api::ObjectInfo) -> Self {
Self {
event_name,
bucket_name: bucket_name.into(),
object,
..Default::default()
}
}
/// Sets the event name.
pub fn event_name(mut self, event_name: EventName) -> Self {
self.event_name = event_name;
self
}
/// Sets the bucket name.
pub fn bucket_name(mut self, bucket_name: impl Into<String>) -> Self {
self.bucket_name = bucket_name.into();
self
}
/// Sets the object information.
pub fn object(mut self, object: rustfs_ecstore::store_api::ObjectInfo) -> Self {
self.object = object;
self
}
/// Sets the request parameters.
pub fn req_params(mut self, req_params: HashMap<String, String>) -> Self {
self.req_params = req_params;
self
}
/// Adds a single request parameter.
pub fn req_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.req_params.insert(key.into(), value.into());
self
}
/// Sets the response elements.
pub fn resp_elements(mut self, resp_elements: HashMap<String, String>) -> Self {
self.resp_elements = resp_elements;
self
}
/// Adds a single response element.
pub fn resp_element(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.resp_elements.insert(key.into(), value.into());
self
}
/// Sets the version ID.
pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
self.version_id = version_id.into();
self
}
/// Sets the host.
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
/// Sets the user agent.
pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
self.user_agent = user_agent.into();
self
}
/// Builds the final `EventArgs` instance.
///
/// This method consumes the builder and returns the constructed `EventArgs`.
pub fn build(self) -> EventArgs {
EventArgs {
event_name: self.event_name,
bucket_name: self.bucket_name,
object: self.object,
req_params: self.req_params,
resp_elements: self.resp_elements,
version_id: self.version_id,
host: self.host,
user_agent: self.user_agent,
}
}
}
+14 -29
View File
@@ -12,17 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{BucketNotificationConfig, Event, EventArgs, NotificationError, NotificationSystem};
use once_cell::sync::Lazy;
use crate::{BucketNotificationConfig, Event, EventArgs, LifecycleError, NotificationError, NotificationSystem};
use rustfs_ecstore::config::Config;
use rustfs_targets::EventName;
use rustfs_targets::arn::TargetID;
use rustfs_targets::{EventName, arn::TargetID};
use std::sync::{Arc, OnceLock};
use tracing::{error, instrument};
use tracing::error;
static NOTIFICATION_SYSTEM: OnceLock<Arc<NotificationSystem>> = OnceLock::new();
// Create a globally unique Notifier instance
static GLOBAL_NOTIFIER: Lazy<Notifier> = Lazy::new(|| Notifier {});
/// Initialize the global notification system with the given configuration.
/// This function should only be called once throughout the application life cycle.
@@ -34,7 +30,7 @@ pub async fn initialize(config: Config) -> Result<(), NotificationError> {
match NOTIFICATION_SYSTEM.set(Arc::new(system)) {
Ok(_) => Ok(()),
Err(_) => Err(NotificationError::AlreadyInitialized),
Err(_) => Err(NotificationError::Lifecycle(LifecycleError::AlreadyInitialized)),
}
}
@@ -49,14 +45,11 @@ pub fn is_notification_system_initialized() -> bool {
NOTIFICATION_SYSTEM.get().is_some()
}
/// Returns a reference to the global Notifier instance.
pub fn notifier_instance() -> &'static Notifier {
&GLOBAL_NOTIFIER
}
/// A module providing the public API for event notification.
pub mod notifier_global {
use super::*;
use tracing::instrument;
pub struct Notifier {}
impl Notifier {
/// Notify an event asynchronously.
/// This is the only entry point for all event notifications in the system.
/// # Parameter
@@ -67,8 +60,8 @@ impl Notifier {
///
/// # Using
/// This function is used to notify events in the system, such as object creation, deletion, or updates.
#[instrument(skip(self, args))]
pub async fn notify(&self, args: EventArgs) {
#[instrument(skip(args))]
pub async fn notify(args: EventArgs) {
// Dependency injection or service positioning mode obtain NotificationSystem instance
let notification_sys = match notification_system() {
// If the notification system itself cannot be retrieved, it will be returned directly
@@ -110,7 +103,6 @@ impl Notifier {
/// # Using
/// This function allows you to dynamically add notification rules for a specific bucket.
pub async fn add_bucket_notification_rule(
&self,
bucket_name: &str,
region: &str,
event_names: &[EventName],
@@ -137,7 +129,7 @@ impl Notifier {
// Get global NotificationSystem
let notification_sys = match notification_system() {
Some(sys) => sys,
None => return Err(NotificationError::ServerNotInitialized),
None => return Err(NotificationError::Lifecycle(LifecycleError::NotInitialized)),
};
// Loading configuration
@@ -159,7 +151,6 @@ impl Notifier {
/// # Using
/// Supports notification rules for adding multiple event types, prefixes, suffixes, and targets to the same bucket in batches.
pub async fn add_event_specific_rules(
&self,
bucket_name: &str,
region: &str,
event_rules: &[(Vec<EventName>, String, String, Vec<TargetID>)],
@@ -176,10 +167,7 @@ impl Notifier {
}
// Get global NotificationSystem instance
let notification_sys = match notification_system() {
Some(sys) => sys,
None => return Err(NotificationError::ServerNotInitialized),
};
let notification_sys = notification_system().ok_or(NotificationError::Lifecycle(LifecycleError::NotInitialized))?;
// Loading configuration
notification_sys
@@ -196,12 +184,9 @@ impl Notifier {
/// This function allows you to clear all notification rules for a specific bucket.
/// This is useful when you want to reset the notification configuration for a bucket.
///
pub async fn clear_bucket_notification_rules(&self, bucket_name: &str) -> Result<(), NotificationError> {
pub async fn clear_bucket_notification_rules(bucket_name: &str) -> Result<(), NotificationError> {
// Get global NotificationSystem instance
let notification_sys = match notification_system() {
Some(sys) => sys,
None => return Err(NotificationError::ServerNotInitialized),
};
let notification_sys = notification_system().ok_or(NotificationError::Lifecycle(LifecycleError::NotInitialized))?;
// Clear configuration
notification_sys.remove_bucket_notification_config(bucket_name).await;
+3 -1
View File
@@ -199,7 +199,9 @@ impl NotificationSystem {
F: FnMut(&mut Config) -> bool, // The closure returns a boolean value indicating whether the configuration has been changed
{
let Some(store) = rustfs_ecstore::global::new_object_layer_fn() else {
return Err(NotificationError::ServerNotInitialized);
return Err(NotificationError::StorageNotAvailable(
"Failed to save target configuration: server storage not initialized".to_string(),
));
};
let mut new_config = rustfs_ecstore::config::com::read_config_without_migrate(store.clone())
+7 -7
View File
@@ -18,18 +18,18 @@
//! It supports sending events to various targets
//! (like Webhook and MQTT) and includes features like event persistence and retry on failure.
pub mod error;
pub mod event;
mod error;
mod event;
pub mod factory;
pub mod global;
mod global;
pub mod integration;
pub mod notifier;
pub mod registry;
pub mod rules;
pub mod stream;
// Re-exports
pub use error::NotificationError;
pub use event::{Event, EventArgs};
pub use global::{initialize, is_notification_system_initialized, notification_system};
pub use error::{LifecycleError, NotificationError};
pub use event::{Event, EventArgs, EventArgsBuilder};
pub use global::{initialize, is_notification_system_initialized, notification_system, notifier_global};
pub use integration::NotificationSystem;
pub use rules::BucketNotificationConfig;