mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 11:56:38 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {}/{}/{}",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user