refactor(logging): standardize protocol and observability events (#3419)

* refactor(logging): standardize object capacity events

* refactor(logging): standardize protocol server events

* refactor(logging): standardize swift protocol events

* refactor(logging): standardize observability events

* refactor(logging): move masking helper and extend guardrails
This commit is contained in:
houseme
2026-06-14 07:14:45 +08:00
committed by GitHub
parent 22460243bf
commit efa89a98ed
48 changed files with 2976 additions and 434 deletions
+36 -8
View File
@@ -60,6 +60,10 @@ use super::{SwiftError, SwiftResult};
use std::fmt;
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_ACL: &str = "swift_acl";
const EVENT_SWIFT_ACL_DECISION: &str = "swift_acl_decision";
/// Container ACL configuration
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ContainerAcl {
@@ -228,14 +232,22 @@ impl ContainerAcl {
for grant in &self.read {
match grant {
AclGrant::PublicRead => {
debug!("Read access granted: public read enabled");
debug!(
event = EVENT_SWIFT_ACL_DECISION,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ACL,
action = "read",
result = "granted",
reason = "public_read",
"swift acl decision"
);
return true;
}
AclGrant::PublicReadReferrer(pattern) => {
if let Some(ref_header) = referrer
&& Self::matches_referrer_pattern(ref_header, pattern)
{
debug!("Read access granted: referrer matches pattern {}", pattern);
debug!(event = EVENT_SWIFT_ACL_DECISION, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_ACL, action = "read", result = "granted", reason = "referrer_match", pattern = %pattern, "swift acl decision");
return true;
}
}
@@ -243,7 +255,7 @@ impl ContainerAcl {
if let Some(req_account) = request_account
&& req_account == account
{
debug!("Read access granted: account {} matches", account);
debug!(event = EVENT_SWIFT_ACL_DECISION, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_ACL, action = "read", result = "granted", reason = "account_match", account = %account, "swift acl decision");
return true;
}
}
@@ -255,14 +267,22 @@ impl ContainerAcl {
&& req_account == account
&& req_user == grant_user
{
debug!("Read access granted: user {}:{} matches", account, grant_user);
debug!(event = EVENT_SWIFT_ACL_DECISION, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_ACL, action = "read", result = "granted", reason = "user_match", account = %account, user = %grant_user, "swift acl decision");
return true;
}
}
}
}
debug!("Read access denied: no matching ACL grant");
debug!(
event = EVENT_SWIFT_ACL_DECISION,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ACL,
action = "read",
result = "denied",
reason = "no_matching_grant",
"swift acl decision"
);
false
}
@@ -288,7 +308,7 @@ impl ContainerAcl {
}
AclGrant::Account(account) => {
if request_account == account {
debug!("Write access granted: account {} matches", account);
debug!(event = EVENT_SWIFT_ACL_DECISION, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_ACL, action = "write", result = "granted", reason = "account_match", account = %account, "swift acl decision");
return true;
}
}
@@ -300,14 +320,22 @@ impl ContainerAcl {
&& request_account == account
&& req_user == grant_user
{
debug!("Write access granted: user {}:{} matches", account, grant_user);
debug!(event = EVENT_SWIFT_ACL_DECISION, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_ACL, action = "write", result = "granted", reason = "user_match", account = %account, user = %grant_user, "swift acl decision");
return true;
}
}
}
}
debug!("Write access denied: no matching ACL grant");
debug!(
event = EVENT_SWIFT_ACL_DECISION,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ACL,
action = "write",
result = "denied",
reason = "no_matching_grant",
"swift acl decision"
);
false
}
+88 -9
View File
@@ -82,6 +82,11 @@ use s3s::Body;
use serde::{Deserialize, Serialize};
use tracing::{debug, error};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_BULK: &str = "swift_bulk";
const EVENT_SWIFT_BULK_DELETE_STATE: &str = "swift_bulk_delete_state";
const EVENT_SWIFT_BULK_EXTRACT_STATE: &str = "swift_bulk_extract_state";
/// Result of a single delete operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteResult {
@@ -211,7 +216,14 @@ fn parse_object_path(path: &str) -> SwiftResult<(String, String)> {
///
/// Deletes multiple objects specified in the request body
pub async fn handle_bulk_delete(account: &str, body: String, credentials: &Credentials) -> SwiftResult<Response<Body>> {
debug!("Bulk delete request for account: {}", account);
debug!(
event = EVENT_SWIFT_BULK_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
state = "started",
account = %account,
"swift bulk delete state changed"
);
let mut response = BulkDeleteResponse::default();
let mut delete_results = Vec::new();
@@ -223,7 +235,15 @@ pub async fn handle_bulk_delete(account: &str, body: String, credentials: &Crede
return Err(SwiftError::BadRequest("No paths provided for bulk delete".to_string()));
}
debug!("Processing {} delete requests", paths.len());
debug!(
event = EVENT_SWIFT_BULK_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
state = "processing",
account = %account,
path_count = paths.len(),
"swift bulk delete state changed"
);
// Process each path
for path in paths {
@@ -248,7 +268,16 @@ pub async fn handle_bulk_delete(account: &str, body: String, credentials: &Crede
}
}
Err(e) => {
error!("Error deleting {}: {}", path, e);
error!(
event = EVENT_SWIFT_BULK_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
result = "delete_failed",
account = %account,
path = %path,
error = %e,
"swift bulk delete state changed"
);
response.errors.push(vec![path.to_string(), e.to_string()]);
DeleteResult {
path: path.to_string(),
@@ -259,7 +288,16 @@ pub async fn handle_bulk_delete(account: &str, body: String, credentials: &Crede
}
}
Err(e) => {
error!("Invalid path {}: {}", path, e);
error!(
event = EVENT_SWIFT_BULK_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
result = "invalid_path",
account = %account,
path = %path,
error = %e,
"swift bulk delete state changed"
);
response.errors.push(vec![path.to_string(), e.to_string()]);
DeleteResult {
path: path.to_string(),
@@ -328,7 +366,16 @@ pub async fn handle_bulk_extract(
body: Vec<u8>,
credentials: &Credentials,
) -> SwiftResult<Response<Body>> {
debug!("Bulk extract request for container: {}, format: {:?}", container, format);
debug!(
event = EVENT_SWIFT_BULK_EXTRACT_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
state = "started",
account = %account,
container = %container,
format = ?format,
"swift bulk extract state changed"
);
let mut response = BulkExtractResponse::default();
@@ -358,10 +405,27 @@ pub async fn handle_bulk_extract(
{
Ok(_) => {
response.number_files_created += 1;
debug!("Extracted: {}", path_str);
debug!(
event = EVENT_SWIFT_BULK_EXTRACT_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
state = "file_created",
container = %container,
object = %path_str,
"swift bulk extract state changed"
);
}
Err(e) => {
error!("Failed to upload {}: {}", path_str, e);
error!(
event = EVENT_SWIFT_BULK_EXTRACT_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
result = "upload_failed",
container = %container,
object = %path_str,
error = %e,
"swift bulk extract state changed"
);
response.errors.push(vec![path_str.clone(), e.to_string()]);
}
}
@@ -430,14 +494,29 @@ async fn extract_tar_entries(format: ArchiveFormat, body: Vec<u8>) -> SwiftResul
// Skip directories
if entry.header().entry_type().is_dir() {
debug!("Skipping directory: {}", path_str);
debug!(
event = EVENT_SWIFT_BULK_EXTRACT_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
state = "skipped_directory",
path = %path_str,
"swift bulk extract state changed"
);
continue;
}
// Read file contents
let mut contents = Vec::new();
if let Err(e) = tokio::io::AsyncReadExt::read_to_end(&mut entry, &mut contents).await {
error!("Failed to read tar entry {}: {}", path_str, e);
error!(
event = EVENT_SWIFT_BULK_EXTRACT_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_BULK,
result = "tar_entry_read_failed",
path = %path_str,
error = %e,
"swift bulk extract state changed"
);
continue;
}
+24 -1
View File
@@ -27,13 +27,25 @@ use s3s::dto::{Tag, Tagging};
use sha2::{Digest, Sha256};
use tracing::{debug, error};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_CONTAINER: &str = "swift_container";
const EVENT_SWIFT_CONTAINER_STORAGE_STATE: &str = "swift_container_storage_state";
/// Sanitize storage layer errors for client responses
///
/// Logs detailed error server-side while returning generic message to client.
/// This prevents information disclosure vulnerabilities.
fn sanitize_storage_error<E: std::fmt::Display>(operation: &str, error: E) -> SwiftError {
// Log detailed error server-side
error!("Storage operation '{}' failed: {}", operation, error);
error!(
event = EVENT_SWIFT_CONTAINER_STORAGE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_CONTAINER,
operation = %operation,
error = %error,
result = "failed",
"swift container storage state changed"
);
// Return generic error to client
SwiftError::InternalServerError(format!("{} operation failed", operation))
@@ -247,6 +259,17 @@ pub async fn list_containers(account: &str, credentials: &Credentials) -> SwiftR
.filter_map(|info| bucket_info_to_container(info, &mapper, &project_id))
.collect();
debug!(
event = EVENT_SWIFT_CONTAINER_STORAGE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_CONTAINER,
operation = "list_containers",
account = %account,
container_count = containers.len(),
result = "ok",
"swift container storage state changed"
);
Ok(containers)
}
+13 -1
View File
@@ -56,6 +56,10 @@ use rustfs_credentials::Credentials;
use s3s::Body;
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_CORS: &str = "swift_cors";
const EVENT_SWIFT_CORS_STATE: &str = "swift_cors_state";
/// CORS configuration for a container
#[derive(Debug, Clone, Default)]
pub struct CorsConfig {
@@ -164,7 +168,15 @@ pub async fn handle_preflight(
credentials: &Credentials,
request_headers: &HeaderMap,
) -> SwiftResult<Response<Body>> {
debug!("CORS preflight request for container: {}", container_name);
debug!(
event = EVENT_SWIFT_CORS_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_CORS,
state = "preflight_requested",
account = %account,
container = %container_name,
"swift cors state changed"
);
// Load CORS configuration
let config = CorsConfig::load(account, container_name, credentials).await?;
+45 -5
View File
@@ -63,6 +63,10 @@ use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use std::collections::HashMap;
use tracing::{debug, warn};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_ENCRYPTION: &str = "swift_encryption";
const EVENT_SWIFT_ENCRYPTION_STATE: &str = "swift_encryption_state";
/// Encryption algorithm identifier
#[derive(Debug, Clone, PartialEq)]
pub enum EncryptionAlgorithm {
@@ -270,7 +274,13 @@ pub fn should_encrypt(config: &EncryptionConfig, headers: &http::HeaderMap) -> b
if let Some(disable) = headers.get("x-object-meta-crypto-disable")
&& disable.to_str().unwrap_or("") == "true"
{
debug!("Client explicitly disabled encryption");
debug!(
event = EVENT_SWIFT_ENCRYPTION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
state = "disabled_by_client",
"swift encryption state changed"
);
return false;
}
@@ -305,7 +315,15 @@ pub fn generate_iv(size: usize) -> Vec<u8> {
/// In production, this would use a proper crypto library like `aes-gcm` or `ring`.
/// This is a stub that demonstrates the API structure.
pub fn encrypt_data(data: &[u8], config: &EncryptionConfig) -> SwiftResult<(Vec<u8>, EncryptionMetadata)> {
debug!("Encrypting {} bytes with {}", data.len(), config.algorithm.as_str());
debug!(
event = EVENT_SWIFT_ENCRYPTION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
state = "encrypting",
input_bytes = data.len(),
algorithm = config.algorithm.as_str(),
"swift encryption state changed"
);
// Generate IV (12 bytes for GCM, 16 bytes for CBC)
let iv_size = match config.algorithm {
@@ -327,7 +345,14 @@ pub fn encrypt_data(data: &[u8], config: &EncryptionConfig) -> SwiftResult<(Vec<
// let ciphertext = cipher.encrypt(nonce, data)
// .map_err(|e| SwiftError::InternalServerError(format!("Encryption failed: {}", e)))?;
warn!("Encryption not yet implemented - returning plaintext with metadata");
warn!(
event = EVENT_SWIFT_ENCRYPTION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
result = "not_implemented",
mode = "encrypt",
"swift encryption state changed"
);
let metadata = EncryptionMetadata::new(config.algorithm.clone(), config.key_id.clone(), iv);
@@ -340,7 +365,15 @@ pub fn encrypt_data(data: &[u8], config: &EncryptionConfig) -> SwiftResult<(Vec<
/// In production, this would use a proper crypto library like `aes-gcm` or `ring`.
/// This is a stub that demonstrates the API structure.
pub fn decrypt_data(encrypted_data: &[u8], metadata: &EncryptionMetadata, config: &EncryptionConfig) -> SwiftResult<Vec<u8>> {
debug!("Decrypting {} bytes with {}", encrypted_data.len(), metadata.algorithm.as_str());
debug!(
event = EVENT_SWIFT_ENCRYPTION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
state = "decrypting",
input_bytes = encrypted_data.len(),
algorithm = metadata.algorithm.as_str(),
"swift encryption state changed"
);
// Verify key ID matches
if metadata.key_id != config.key_id {
@@ -361,7 +394,14 @@ pub fn decrypt_data(encrypted_data: &[u8], metadata: &EncryptionMetadata, config
// let plaintext = cipher.decrypt(nonce, encrypted_data)
// .map_err(|e| SwiftError::InternalServerError(format!("Decryption failed: {}", e)))?;
warn!("Decryption not yet implemented - returning data as-is");
warn!(
event = EVENT_SWIFT_ENCRYPTION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
result = "not_implemented",
mode = "decrypt",
"swift encryption state changed"
);
// In production, return plaintext
Ok(encrypted_data.to_vec())
+21 -3
View File
@@ -57,6 +57,10 @@ use super::{SwiftError, SwiftResult};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_EXPIRATION: &str = "swift_expiration";
const EVENT_SWIFT_EXPIRATION_STATE: &str = "swift_expiration_state";
/// Parse X-Delete-At header value
///
/// Returns Unix timestamp in seconds
@@ -92,7 +96,7 @@ pub fn extract_expiration(headers: &http::HeaderMap) -> SwiftResult<Option<u64>>
&& let Ok(value_str) = delete_after.to_str()
{
let delete_at = parse_delete_after(value_str)?;
debug!("X-Delete-After: {} seconds -> X-Delete-At: {}", value_str, delete_at);
debug!(event = EVENT_SWIFT_EXPIRATION_STATE, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION, state = "delete_after_parsed", delete_after = %value_str, delete_at, "swift expiration state changed");
return Ok(Some(delete_at));
}
@@ -101,7 +105,14 @@ pub fn extract_expiration(headers: &http::HeaderMap) -> SwiftResult<Option<u64>>
&& let Ok(value_str) = delete_at.to_str()
{
let timestamp = parse_delete_at(value_str)?;
debug!("X-Delete-At: {}", timestamp);
debug!(
event = EVENT_SWIFT_EXPIRATION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "delete_at_parsed",
delete_at = timestamp,
"swift expiration state changed"
);
return Ok(Some(timestamp));
}
@@ -137,7 +148,14 @@ pub fn validate_expiration(delete_at: u64) -> SwiftResult<()> {
// Warn if expiration is more than 10 years in the future
let ten_years = 10 * 365 * 24 * 60 * 60;
if delete_at > now + ten_years {
debug!("X-Delete-At timestamp is more than 10 years in the future: {}", delete_at);
debug!(
event = EVENT_SWIFT_EXPIRATION_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "far_future_timestamp",
delete_at,
"swift expiration state changed"
);
}
Ok(())
+167 -20
View File
@@ -57,6 +57,14 @@ use tokio::sync::RwLock;
use tokio::time::interval;
use tracing::{debug, error, info, warn};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_EXPIRATION: &str = "swift_expiration_worker";
const EVENT_SWIFT_EXPIRATION_WORKER_STATE: &str = "swift_expiration_worker_state";
const EVENT_SWIFT_EXPIRATION_OBJECT_TRACKING: &str = "swift_expiration_object_tracking";
const EVENT_SWIFT_EXPIRATION_ITERATION_SUMMARY: &str = "swift_expiration_iteration_summary";
const EVENT_SWIFT_EXPIRATION_DELETE_STATE: &str = "swift_expiration_delete_state";
const EVENT_SWIFT_EXPIRATION_SCAN_STATE: &str = "swift_expiration_scan_state";
/// Configuration for expiration worker
#[derive(Debug, Clone)]
pub struct ExpirationWorkerConfig {
@@ -156,15 +164,29 @@ impl ExpirationWorker {
pub async fn start(&self) {
let mut running = self.running.write().await;
if *running {
warn!("Expiration worker already running");
warn!(
event = EVENT_SWIFT_EXPIRATION_WORKER_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "already_running",
worker_id = self.config.worker_id,
max_workers = self.config.max_workers,
"swift expiration worker state changed"
);
return;
}
*running = true;
drop(running);
info!(
"Starting expiration worker (scan_interval={}s, worker_id={}/{})",
self.config.scan_interval_secs, self.config.worker_id, self.config.max_workers
event = EVENT_SWIFT_EXPIRATION_WORKER_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "started",
scan_interval_secs = self.config.scan_interval_secs,
worker_id = self.config.worker_id,
max_workers = self.config.max_workers,
"swift expiration worker state changed"
);
let config = self.config.clone();
@@ -180,13 +202,28 @@ impl ExpirationWorker {
// Check if still running
if !*running.read().await {
info!("Expiration worker stopped");
info!(
event = EVENT_SWIFT_EXPIRATION_WORKER_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "stopped",
worker_id = config.worker_id,
"swift expiration worker state changed"
);
break;
}
// Run cleanup iteration
if let Err(e) = Self::cleanup_iteration(&config, &priority_queue, &metrics).await {
error!("Expiration cleanup iteration failed: {}", e);
error!(
event = EVENT_SWIFT_EXPIRATION_ITERATION_SUMMARY,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "error",
worker_id = config.worker_id,
error = %e,
"swift expiration iteration summary"
);
metrics.write().await.error_count += 1;
}
}
@@ -197,7 +234,14 @@ impl ExpirationWorker {
pub async fn stop(&self) {
let mut running = self.running.write().await;
*running = false;
info!("Stopping expiration worker");
info!(
event = EVENT_SWIFT_EXPIRATION_WORKER_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "stopping",
worker_id = self.config.worker_id,
"swift expiration worker state changed"
);
}
/// Get current metrics
@@ -213,7 +257,17 @@ impl ExpirationWorker {
// Check if this worker should handle this object (distributed hashing)
if !self.should_handle_object(&path) {
debug!("Skipping object {} (handled by different worker)", path);
debug!(
event = EVENT_SWIFT_EXPIRATION_OBJECT_TRACKING,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "skipped",
reason = "assigned_to_other_worker",
path = %path,
worker_id = self.config.worker_id,
max_workers = self.config.max_workers,
"swift expiration object tracking changed"
);
return;
}
@@ -225,7 +279,16 @@ impl ExpirationWorker {
let mut queue = self.priority_queue.write().await;
queue.push(Reverse(entry));
debug!("Tracking object {} for expiration at {}", path, expires_at);
debug!(
event = EVENT_SWIFT_EXPIRATION_OBJECT_TRACKING,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "tracked",
path = %path,
expires_at,
worker_id = self.config.worker_id,
"swift expiration object tracking changed"
);
}
/// Remove object from expiration tracking
@@ -238,7 +301,15 @@ impl ExpirationWorker {
// the cleanup iteration to skip objects that no longer exist.
// This is acceptable because the queue size is bounded and cleanup is periodic.
debug!("Untracking object {} from expiration", path);
debug!(
event = EVENT_SWIFT_EXPIRATION_OBJECT_TRACKING,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "untracked",
path = %path,
worker_id = self.config.worker_id,
"swift expiration object tracking changed"
);
}
/// Check if this worker should handle the given object (consistent hashing)
@@ -273,7 +344,15 @@ impl ExpirationWorker {
let start_time = SystemTime::now();
let now = start_time.duration_since(UNIX_EPOCH).unwrap().as_secs();
info!("Starting expiration cleanup iteration (worker_id={})", config.worker_id);
debug!(
event = EVENT_SWIFT_EXPIRATION_ITERATION_SUMMARY,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "started",
worker_id = config.worker_id,
batch_size = config.batch_size,
"swift expiration iteration summary"
);
let mut deleted_count = 0;
let mut scanned_count = 0;
@@ -313,7 +392,15 @@ impl ExpirationWorker {
// Parse path: "account/container/object"
let parts: Vec<&str> = entry.path.splitn(3, '/').collect();
if parts.len() != 3 {
warn!("Invalid expiration entry path: {}", entry.path);
warn!(
event = EVENT_SWIFT_EXPIRATION_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "invalid_path",
path = %entry.path,
worker_id = config.worker_id,
"swift expiration delete state changed"
);
continue;
}
@@ -323,13 +410,42 @@ impl ExpirationWorker {
match Self::delete_expired_object(account, container, object, entry.expires_at).await {
Ok(true) => {
deleted_count += 1;
info!("Deleted expired object: {}", entry.path);
debug!(
event = EVENT_SWIFT_EXPIRATION_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "deleted",
path = %entry.path,
expires_at = entry.expires_at,
worker_id = config.worker_id,
"swift expiration delete state changed"
);
}
Ok(false) => {
debug!("Object {} no longer exists or expiration removed", entry.path);
debug!(
event = EVENT_SWIFT_EXPIRATION_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "not_deleted",
reason = "missing_or_expiration_removed",
path = %entry.path,
expires_at = entry.expires_at,
worker_id = config.worker_id,
"swift expiration delete state changed"
);
}
Err(e) => {
error!("Failed to delete expired object {}: {}", entry.path, e);
error!(
event = EVENT_SWIFT_EXPIRATION_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "delete_failed",
path = %entry.path,
expires_at = entry.expires_at,
worker_id = config.worker_id,
error = %e,
"swift expiration delete state changed"
);
metrics.write().await.error_count += 1;
}
}
@@ -345,8 +461,17 @@ impl ExpirationWorker {
m.queue_size = priority_queue.read().await.len();
info!(
"Expiration cleanup iteration complete: scanned={}, deleted={}, duration={}ms, queue_size={}",
scanned_count, deleted_count, m.last_scan_duration_ms, m.queue_size
event = EVENT_SWIFT_EXPIRATION_ITERATION_SUMMARY,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "completed",
worker_id = config.worker_id,
scanned_count,
deleted_count,
duration_ms = m.last_scan_duration_ms,
queue_size = m.queue_size,
error_count = m.error_count,
"swift expiration iteration summary"
);
Ok(())
@@ -368,8 +493,15 @@ impl ExpirationWorker {
// For now, we'll log the deletion
debug!(
"Would delete expired object: {}/{}/{} (expires_at={})",
account, container, object, expected_expires_at
event = EVENT_SWIFT_EXPIRATION_DELETE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "delete_candidate",
account = %account,
container = %container,
object = %object,
expires_at = expected_expires_at,
"swift expiration delete state changed"
);
// TODO: Integrate with actual object storage
@@ -390,13 +522,28 @@ impl ExpirationWorker {
/// This is used for initial population or recovery after restart.
/// In production, objects should be tracked incrementally via track_object().
pub async fn scan_all_objects(&self) -> SwiftResult<()> {
info!("Starting full scan of objects with expiration (worker_id={})", self.config.worker_id);
info!(
event = EVENT_SWIFT_EXPIRATION_SCAN_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
state = "started",
worker_id = self.config.worker_id,
max_workers = self.config.max_workers,
"swift expiration scan state changed"
);
// TODO: This would integrate with the storage layer to list all objects
// For each object with X-Delete-At metadata, call track_object()
// Placeholder implementation
warn!("Full object scan not yet implemented - requires storage layer integration");
warn!(
event = EVENT_SWIFT_EXPIRATION_SCAN_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
result = "unimplemented",
worker_id = self.config.worker_id,
"swift expiration scan state changed"
);
Ok(())
}
+21 -2
View File
@@ -85,6 +85,10 @@ use sha1::Sha1;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_FORMPOST: &str = "swift_formpost";
const EVENT_SWIFT_FORMPOST_STATE: &str = "swift_formpost_state";
type HmacSha1 = Hmac<Sha1>;
/// FormPost request parameters
@@ -206,7 +210,13 @@ pub fn validate_formpost(path: &str, request: &FormPostRequest, key: &str) -> Sw
)?;
if request.signature != expected_sig {
debug!("FormPost signature mismatch: expected={}, got={}", expected_sig, request.signature);
debug!(
event = EVENT_SWIFT_FORMPOST_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_FORMPOST,
result = "signature_mismatch",
"swift formpost state changed"
);
return Err(SwiftError::Unauthorized("Invalid FormPost signature".to_string()));
}
@@ -433,7 +443,16 @@ pub async fn handle_formpost(
match super::object::put_object(account, container, object_name, credentials, reader, &upload_headers).await {
Ok(_) => {
debug!("FormPost uploaded: {}/{}/{}", account, container, object_name);
debug!(
event = EVENT_SWIFT_FORMPOST_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_FORMPOST,
state = "uploaded",
account = %account,
container = %container,
object = %object_name,
"swift formpost state changed"
);
}
Err(e) => {
upload_errors.push(format!("{}: {}", file.filename, e));
+48 -5
View File
@@ -35,6 +35,11 @@ use tokio_util::io::StreamReader;
use tower::Service;
use tracing::{debug, instrument};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_HANDLER: &str = "swift_handler";
const EVENT_SWIFT_ROUTE_STATE: &str = "swift_route_state";
const EVENT_SWIFT_TEMPURL_STATE: &str = "swift_tempurl_state";
/// Swift-aware service that routes to Swift handlers or S3 service
#[derive(Clone)]
pub struct SwiftService<S> {
@@ -75,7 +80,14 @@ where
let uri = req.uri();
if let Some(route) = self.router.route(uri, method.clone()) {
debug!("Swift route matched: {:?}", route);
debug!(
event = EVENT_SWIFT_ROUTE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_HANDLER,
state = "matched",
route = ?route,
"swift route state changed"
);
// Extract credentials from Keystone task-local storage (if available)
// This is consistent with how S3 auth handler retrieves Keystone credentials
@@ -98,7 +110,13 @@ where
}
// Not a Swift request, delegate to S3 service
debug!("No Swift route matched, delegating to S3 service");
debug!(
event = EVENT_SWIFT_ROUTE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_HANDLER,
state = "delegated_to_s3",
"swift route state changed"
);
let mut s3_service = self.s3_service.clone();
Box::pin(async move { s3_service.call(req).await.map_err(Into::into) })
}
@@ -127,7 +145,16 @@ async fn handle_swift_request(
&& let Some(tempurl_params) = tempurl::TempURLParams::from_query(query)
{
// TempURL detected - validate it
debug!("TempURL detected for {}/{}/{}", account, container, object);
debug!(
event = EVENT_SWIFT_TEMPURL_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_HANDLER,
state = "detected",
account = %account,
container = %container,
object = %object,
"swift tempurl state changed"
);
// Get account TempURL key
let tempurl_key = super::account::get_tempurl_key(account, &credentials).await?;
@@ -140,7 +167,16 @@ async fn handle_swift_request(
tempurl.validate_request(method.as_str(), path, &tempurl_params)?;
// TempURL is valid - proceed with request (no credentials needed)
debug!("TempURL validated successfully");
debug!(
event = EVENT_SWIFT_TEMPURL_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_HANDLER,
result = "validated",
account = %account,
container = %container,
object = %object,
"swift tempurl state changed"
);
// Reconstruct request for object operation
let req = Request::from_parts(parts, body);
@@ -904,7 +940,14 @@ async fn handle_authenticated_request(
.await
.unwrap_or_else(|e| {
// Log restore error but don't fail the DELETE
tracing::warn!("Failed to restore version after delete: {}", e);
tracing::warn!(
event = EVENT_SWIFT_TEMPURL_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_HANDLER,
result = "restore_after_delete_failed",
error = %e,
"swift tempurl state changed"
);
false
});
+21 -2
View File
@@ -62,6 +62,10 @@ use std::collections::HashMap;
use tracing::debug;
use tracing::error;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_OBJECT: &str = "swift_object";
const EVENT_SWIFT_OBJECT_STORAGE_STATE: &str = "swift_object_storage_state";
/// Maximum number of metadata headers allowed per object (Swift standard)
const MAX_METADATA_COUNT: usize = 90;
@@ -258,7 +262,15 @@ fn validate_metadata(metadata: &HashMap<String, String>) -> SwiftResult<()> {
/// This prevents information disclosure vulnerabilities.
fn sanitize_storage_error<E: std::fmt::Display>(operation: &str, error: E) -> SwiftError {
// Log detailed error server-side
error!("Storage operation '{}' failed: {}", operation, error);
error!(
event = EVENT_SWIFT_OBJECT_STORAGE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_OBJECT,
operation = %operation,
error = %error,
result = "failed",
"swift object storage state changed"
);
// Return generic error to client
SwiftError::InternalServerError(format!("{} operation failed", operation))
@@ -334,7 +346,14 @@ where
// Store the fully qualified target (container/object)
let target_value = symlink_target.to_header_value(container);
user_metadata.insert("x-object-symlink-target".to_string(), target_value);
debug!("Creating symlink to target: {}", user_metadata.get("x-object-symlink-target").unwrap());
debug!(
event = EVENT_SWIFT_OBJECT_STORAGE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_OBJECT,
state = "symlink_target_recorded",
target = %user_metadata.get("x-object-symlink-target").unwrap(),
"swift object storage state changed"
);
}
// 9. Validate metadata limits
+14 -2
View File
@@ -70,6 +70,10 @@ use super::{SwiftError, SwiftResult, container};
use rustfs_credentials::Credentials;
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_QUOTA: &str = "swift_quota";
const EVENT_SWIFT_QUOTA_STATE: &str = "swift_quota_state";
/// Quota configuration for a container
#[derive(Debug, Clone, Default)]
pub struct QuotaConfig {
@@ -161,8 +165,16 @@ pub async fn check_upload_quota(
quota.check_quota(metadata.bytes_used, metadata.object_count, object_size)?;
debug!(
"Quota check passed: {}/{:?} bytes, {}/{:?} objects",
metadata.bytes_used, quota.quota_bytes, metadata.object_count, quota.quota_count
event = EVENT_SWIFT_QUOTA_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_QUOTA,
state = "check_passed",
current_bytes = metadata.bytes_used,
quota_bytes = ?quota.quota_bytes,
current_count = metadata.object_count,
quota_count = ?quota.quota_count,
object_size,
"swift quota state changed"
);
Ok(())
+6 -2
View File
@@ -64,6 +64,10 @@ use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_RATELIMIT: &str = "swift_ratelimit";
const EVENT_SWIFT_RATELIMIT_STATE: &str = "swift_ratelimit_state";
/// Rate limit configuration
#[derive(Debug, Clone, PartialEq)]
pub struct RateLimit {
@@ -221,11 +225,11 @@ impl RateLimiter {
match bucket.try_consume() {
Ok(remaining) => {
debug!("Rate limit OK for {}: {} remaining", key, remaining);
debug!(event = EVENT_SWIFT_RATELIMIT_STATE, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_RATELIMIT, key = %key, remaining, result = "allowed", "swift ratelimit state changed");
Ok((remaining, reset))
}
Err(retry_after) => {
debug!("Rate limit exceeded for {}: retry after {} seconds", key, retry_after);
debug!(event = EVENT_SWIFT_RATELIMIT_STATE, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_RATELIMIT, key = %key, retry_after, result = "limited", "swift ratelimit state changed");
Err(SwiftError::TooManyRequests {
retry_after,
limit: rate_limit.limit,
+53 -5
View File
@@ -73,6 +73,10 @@ use rustfs_credentials::Credentials;
use s3s::Body;
use tracing::debug;
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_STATICWEB: &str = "swift_staticweb";
const EVENT_SWIFT_STATICWEB_STATE: &str = "swift_staticweb_state";
/// Static website configuration for a container
#[derive(Debug, Clone, Default)]
pub struct StaticWebConfig {
@@ -376,14 +380,33 @@ pub async fn handle_static_web_get(
return Err(SwiftError::InternalServerError("Static web not enabled for this container".to_string()));
}
debug!("Static web request: container={}, path={}, config={:?}", container, path, config);
debug!(
event = EVENT_SWIFT_STATICWEB_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_STATICWEB,
state = "requested",
container = %container,
path = %path,
listings_enabled = config.listings_enabled(),
index_document = config.index_document().unwrap_or_default(),
error_document = config.error_document().unwrap_or_default(),
"swift staticweb state changed"
);
// Resolve path
let (object_path, _is_index, is_listing) = resolve_path(path, &config);
if is_listing {
// Generate directory listing
debug!("Generating directory listing for path: {}", object_path);
debug!(
event = EVENT_SWIFT_STATICWEB_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_STATICWEB,
state = "listing_generated",
container = %container,
path = %object_path,
"swift staticweb state changed"
);
// List objects with prefix
let prefix = if object_path.is_empty() {
@@ -419,7 +442,15 @@ pub async fn handle_static_web_get(
}
// Try to serve the object
debug!("Attempting to serve object: {}", object_path);
debug!(
event = EVENT_SWIFT_STATICWEB_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_STATICWEB,
state = "serving_object",
container = %container,
path = %object_path,
"swift staticweb state changed"
);
match object::get_object(account, container, &object_path, credentials, None).await {
Ok(reader) => {
@@ -446,7 +477,16 @@ pub async fn handle_static_web_get(
Err(SwiftError::NotFound(_)) => {
// Object not found - try to serve error document
if let Some(error_doc) = config.error_document() {
debug!("Serving error document: {}", error_doc);
debug!(
event = EVENT_SWIFT_STATICWEB_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_STATICWEB,
state = "serving_error_document",
container = %container,
path = %object_path,
error_document = %error_doc,
"swift staticweb state changed"
);
match object::get_object(account, container, error_doc, credentials, None).await {
Ok(reader) => {
@@ -470,7 +510,15 @@ pub async fn handle_static_web_get(
}
Err(_) => {
// Error document also not found - return standard 404
debug!("Error document not found, returning standard 404");
debug!(
event = EVENT_SWIFT_STATICWEB_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_STATICWEB,
result = "error_document_missing",
container = %container,
error_document = %error_doc,
"swift staticweb state changed"
);
}
}
}
+18 -2
View File
@@ -62,6 +62,10 @@ use super::{SwiftError, SwiftResult};
use std::collections::HashSet;
use tracing::{debug, warn};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_SYMLINK: &str = "swift_symlink";
const EVENT_SWIFT_SYMLINK_STATE: &str = "swift_symlink_state";
/// Maximum symlink follow depth to prevent infinite loops
const MAX_SYMLINK_DEPTH: u8 = 5;
@@ -158,7 +162,15 @@ pub fn extract_symlink_target(headers: &http::HeaderMap) -> SwiftResult<Option<S
.map_err(|_| SwiftError::BadRequest("Invalid X-Object-Symlink-Target header".to_string()))?;
let target = SymlinkTarget::parse(target_str)?;
debug!("Extracted symlink target: container={:?}, object={}", target.container, target.object);
debug!(
event = EVENT_SWIFT_SYMLINK_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_SYMLINK,
state = "target_extracted",
container = ?target.container,
object = %target.object,
"swift symlink state changed"
);
Ok(Some(target))
} else {
Ok(None)
@@ -196,10 +208,14 @@ pub fn check_circular_reference(visited: &HashSet<SymlinkPath>, account: &str, c
if visited.contains(&path) {
warn!(
event = EVENT_SWIFT_SYMLINK_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_SYMLINK,
account = %account,
container = %container,
object = %object,
"Circular symlink reference detected"
result = "circular_reference_detected",
"swift symlink state changed"
);
return Err(SwiftError::Conflict(format!(
"Circular symlink reference detected: {}/{}/{}",
+15 -3
View File
@@ -53,6 +53,11 @@ use super::{SwiftError, SwiftResult};
use std::collections::HashMap;
use tracing::{debug, warn};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_SYNC: &str = "swift_sync";
const EVENT_SWIFT_SYNC_CONFIG_STATE: &str = "swift_sync_config_state";
const EVENT_SWIFT_SYNC_RETRY_STATE: &str = "swift_sync_retry_state";
/// Container sync configuration
#[derive(Debug, Clone, PartialEq)]
pub struct SyncConfig {
@@ -109,12 +114,19 @@ impl SyncConfig {
// Warn if using HTTP instead of HTTPS
if self.sync_to.starts_with("http://") {
warn!("Container sync using unencrypted HTTP - consider using HTTPS");
warn!(event = EVENT_SWIFT_SYNC_CONFIG_STATE, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_SYNC, result = "unencrypted_http", sync_to = %self.sync_to, "swift sync config state changed");
}
// Validate key length (recommend at least 16 characters)
if self.sync_key.len() < 16 {
warn!("Container sync key is short (<16 chars) - recommend longer key");
warn!(
event = EVENT_SWIFT_SYNC_CONFIG_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_SYNC,
result = "short_key",
key_length = self.sync_key.len(),
"swift sync config state changed"
);
}
Ok(())
@@ -211,7 +223,7 @@ impl SyncQueueEntry {
let backoff_seconds = std::cmp::min(60 * (1 << (self.retry_count - 1)), 3600);
self.next_retry = current_time + backoff_seconds;
debug!("Scheduled retry #{} for '{}' at +{}s", self.retry_count, self.object, backoff_seconds);
debug!(event = EVENT_SWIFT_SYNC_RETRY_STATE, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_SYNC, state = "scheduled", retry_count = self.retry_count, object = %self.object, backoff_seconds, "swift sync retry state changed");
}
/// Check if ready for retry
+179 -18
View File
@@ -61,6 +61,12 @@ use rustfs_ecstore::store_api::{ListOperations, ObjectOperations, ObjectOptions}
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{debug, error};
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_VERSIONING: &str = "swift_versioning";
const EVENT_SWIFT_VERSIONING_ARCHIVE_STATE: &str = "swift_versioning_archive_state";
const EVENT_SWIFT_VERSIONING_RESTORE_STATE: &str = "swift_versioning_restore_state";
const EVENT_SWIFT_VERSIONING_LIST_STATE: &str = "swift_versioning_list_state";
/// Generate a version name for an archived object
///
/// Version names use inverted timestamps to sort newest-first:
@@ -131,8 +137,15 @@ pub async fn archive_current_version(
credentials: &Credentials,
) -> SwiftResult<()> {
debug!(
"Archiving current version of {}/{}/{} to {}",
account, container, object, archive_container
event = EVENT_SWIFT_VERSIONING_ARCHIVE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
state = "started",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
"swift versioning archive state changed"
);
// Check if object exists
@@ -140,7 +153,18 @@ pub async fn archive_current_version(
Ok(info) => info,
Err(SwiftError::NotFound(_)) => {
// Object doesn't exist - nothing to archive
debug!("Object does not exist, nothing to archive");
debug!(
event = EVENT_SWIFT_VERSIONING_ARCHIVE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "skipped",
reason = "object_missing",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
"swift versioning archive state changed"
);
return Ok(());
}
Err(e) => return Err(e),
@@ -149,7 +173,16 @@ pub async fn archive_current_version(
// Generate version name
let version_name = generate_version_name(container, object);
debug!("Generated version name: {}", version_name);
debug!(
event = EVENT_SWIFT_VERSIONING_ARCHIVE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
state = "version_name_generated",
container = %container,
object = %object,
version_name = %version_name,
"swift versioning archive state changed"
);
// Validate account and get project_id
let project_id = validate_account_access(account, credentials)?;
@@ -174,7 +207,18 @@ pub async fn archive_current_version(
// Get source object info for copy operation
let mut src_info = store.get_object_info(&source_bucket, &source_key, &opts).await.map_err(|e| {
error!("Failed to get source object info: {}", e);
error!(
event = EVENT_SWIFT_VERSIONING_ARCHIVE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "source_info_failed",
source_bucket = %source_bucket,
source_key = %source_key,
archive_bucket = %archive_bucket,
version_key = %version_key,
error = %e,
"swift versioning archive state changed"
);
SwiftError::InternalServerError(format!("Failed to get object info for archiving: {}", e))
})?;
@@ -182,11 +226,33 @@ pub async fn archive_current_version(
.copy_object(&source_bucket, &source_key, &archive_bucket, &version_key, &mut src_info, &opts, &opts)
.await
.map_err(|e| {
error!("Failed to copy object to archive: {}", e);
error!(
event = EVENT_SWIFT_VERSIONING_ARCHIVE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "copy_failed",
source_bucket = %source_bucket,
source_key = %source_key,
archive_bucket = %archive_bucket,
version_key = %version_key,
error = %e,
"swift versioning archive state changed"
);
SwiftError::InternalServerError(format!("Failed to archive version: {}", e))
})?;
debug!("Successfully archived version to {}/{}", archive_container, version_name);
debug!(
event = EVENT_SWIFT_VERSIONING_ARCHIVE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "archived",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
version_name = %version_name,
"swift versioning archive state changed"
);
Ok(())
}
@@ -220,22 +286,46 @@ pub async fn restore_previous_version(
credentials: &Credentials,
) -> SwiftResult<bool> {
debug!(
"Restoring previous version of {}/{}/{} from {}",
account, container, object, archive_container
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
state = "started",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
"swift versioning restore state changed"
);
// List versions for this object
let versions = list_object_versions(account, container, object, archive_container, credentials).await?;
if versions.is_empty() {
debug!("No versions found to restore");
debug!(
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "not_found",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
"swift versioning restore state changed"
);
return Ok(false);
}
// Get newest version (first in list, since they're sorted newest-first)
let newest_version = &versions[0];
debug!("Restoring version: {}", newest_version);
debug!(
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
state = "selected",
version_name = %newest_version,
"swift versioning restore state changed"
);
// Validate account and get project_id
let project_id = validate_account_access(account, credentials)?;
@@ -261,7 +351,18 @@ pub async fn restore_previous_version(
.get_object_info(&archive_bucket, &version_key, &opts)
.await
.map_err(|e| {
error!("Failed to get version object info: {}", e);
error!(
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "version_info_failed",
archive_bucket = %archive_bucket,
version_key = %version_key,
target_bucket = %target_bucket,
target_key = %target_key,
error = %e,
"swift versioning restore state changed"
);
SwiftError::InternalServerError(format!("Failed to get version info for restore: {}", e))
})?;
@@ -278,18 +379,49 @@ pub async fn restore_previous_version(
)
.await
.map_err(|e| {
error!("Failed to restore version: {}", e);
error!(
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "copy_failed",
archive_bucket = %archive_bucket,
version_key = %version_key,
target_bucket = %target_bucket,
target_key = %target_key,
error = %e,
"swift versioning restore state changed"
);
SwiftError::InternalServerError(format!("Failed to restore version: {}", e))
})?;
// Delete the version from archive after successful restore
store.delete_object(&archive_bucket, &version_key, opts).await.map_err(|e| {
error!("Failed to delete archived version after restore: {}", e);
error!(
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "archive_cleanup_failed",
archive_bucket = %archive_bucket,
version_key = %version_key,
error = %e,
"swift versioning restore state changed"
);
// Don't fail the restore if deletion fails - object is restored
SwiftError::InternalServerError(format!("Version restored but cleanup failed: {}", e))
})?;
debug!("Successfully restored version from {}", newest_version);
debug!(
event = EVENT_SWIFT_VERSIONING_RESTORE_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "restored",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
version_name = %newest_version,
"swift versioning restore state changed"
);
Ok(true)
}
@@ -324,7 +456,17 @@ pub async fn list_object_versions(
archive_container: &str,
credentials: &Credentials,
) -> SwiftResult<Vec<String>> {
debug!("Listing versions of {}/{}/{} in {}", account, container, object, archive_container);
debug!(
event = EVENT_SWIFT_VERSIONING_LIST_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
state = "started",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
"swift versioning list state changed"
);
// Validate account and get project_id
let project_id = validate_account_access(account, credentials)?;
@@ -359,7 +501,15 @@ pub async fn list_object_versions(
)
.await
.map_err(|e| {
error!("Failed to list archive container: {}", e);
error!(
event = EVENT_SWIFT_VERSIONING_LIST_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "archive_list_failed",
archive_bucket = %archive_bucket,
error = %e,
"swift versioning list state changed"
);
SwiftError::InternalServerError(format!("Failed to list versions: {}", e))
})?;
@@ -382,7 +532,18 @@ pub async fn list_object_versions(
// gives us newest first because smaller numbers sort first lexicographically
versions.sort(); // Ascending sort for inverted timestamps
debug!("Found {} versions", versions.len());
debug!(
event = EVENT_SWIFT_VERSIONING_LIST_STATE,
component = LOG_COMPONENT_PROTOCOLS,
subsystem = LOG_SUBSYSTEM_SWIFT_VERSIONING,
result = "listed",
account = %account,
container = %container,
object = %object,
archive_container = %archive_container,
version_count = versions.len(),
"swift versioning list state changed"
);
Ok(versions)
}