feat(etag): implement Phase 3.1 ETag support for efficient content fetching
- Add DailyNotificationETagManager for Android with conditional request handling - Add DailyNotificationETagManager for iOS with URLSession integration - Update DailyNotificationFetcher with ETag manager integration - Implement If-None-Match header support for conditional requests - Add 304 Not Modified response handling for cached content - Add ETag storage and validation with TTL management - Add network efficiency metrics and cache statistics - Add conditional request logic with fallback handling - Add ETag cache management and cleanup methods - Add phase3-1-etag-support.ts usage examples This implements Phase 3.1 ETag support for network optimization: - Conditional requests with If-None-Match headers - 304 Not Modified response handling for bandwidth savings - ETag caching with 24-hour TTL for efficient storage - Network metrics tracking cache hit ratios and efficiency - Graceful fallback when ETag requests fail - Comprehensive cache management and cleanup - Cross-platform implementation (Android + iOS) Files: 4 changed, 800+ insertions(+)
This commit is contained in:
650
ios/Plugin/DailyNotificationErrorHandler.swift
Normal file
650
ios/Plugin/DailyNotificationErrorHandler.swift
Normal file
@@ -0,0 +1,650 @@
|
||||
/**
|
||||
* DailyNotificationErrorHandler.swift
|
||||
*
|
||||
* iOS Error Handler for comprehensive error management
|
||||
* Implements error categorization, retry logic, and telemetry
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Manages comprehensive error handling with categorization, retry logic, and telemetry
|
||||
*
|
||||
* This class implements the critical error handling functionality:
|
||||
* - Categorizes errors by type, code, and severity
|
||||
* - Implements exponential backoff retry logic
|
||||
* - Tracks error metrics and telemetry
|
||||
* - Provides debugging information
|
||||
* - Manages retry state and limits
|
||||
*/
|
||||
class DailyNotificationErrorHandler {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private static let TAG = "DailyNotificationErrorHandler"
|
||||
|
||||
// Retry configuration
|
||||
private static let DEFAULT_MAX_RETRIES = 3
|
||||
private static let DEFAULT_BASE_DELAY_SECONDS: TimeInterval = 1.0
|
||||
private static let DEFAULT_MAX_DELAY_SECONDS: TimeInterval = 30.0
|
||||
private static let DEFAULT_BACKOFF_MULTIPLIER: Double = 2.0
|
||||
|
||||
// Error severity levels
|
||||
enum ErrorSeverity {
|
||||
case low // Minor issues, non-critical
|
||||
case medium // Moderate issues, may affect functionality
|
||||
case high // Serious issues, significant impact
|
||||
case critical // Critical issues, system failure
|
||||
}
|
||||
|
||||
// Error categories
|
||||
enum ErrorCategory {
|
||||
case network // Network-related errors
|
||||
case storage // Storage/database errors
|
||||
case scheduling // Notification scheduling errors
|
||||
case permission // Permission-related errors
|
||||
case configuration // Configuration errors
|
||||
case system // System-level errors
|
||||
case unknown // Unknown/unclassified errors
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let logger: DailyNotificationLogger
|
||||
private var retryStates: [String: RetryState] = [:]
|
||||
private let retryQueue = DispatchQueue(label: "error.retry", attributes: .concurrent)
|
||||
private let metrics = ErrorMetrics()
|
||||
private let config: ErrorConfiguration
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/**
|
||||
* Constructor with default configuration
|
||||
*/
|
||||
init(logger: DailyNotificationLogger) {
|
||||
self.logger = logger
|
||||
self.config = ErrorConfiguration()
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "ErrorHandler initialized with max retries: \(config.maxRetries)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with custom configuration
|
||||
*
|
||||
* @param logger Logger instance for debugging
|
||||
* @param config Error handling configuration
|
||||
*/
|
||||
init(logger: DailyNotificationLogger, config: ErrorConfiguration) {
|
||||
self.logger = logger
|
||||
self.config = config
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "ErrorHandler initialized with max retries: \(config.maxRetries)")
|
||||
}
|
||||
|
||||
// MARK: - Error Handling
|
||||
|
||||
/**
|
||||
* Handle error with automatic retry logic
|
||||
*
|
||||
* @param operationId Unique identifier for the operation
|
||||
* @param error Error to handle
|
||||
* @param retryable Whether this error is retryable
|
||||
* @return ErrorResult with handling information
|
||||
*/
|
||||
func handleError(operationId: String, error: Error, retryable: Bool) -> ErrorResult {
|
||||
do {
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Handling error for operation: \(operationId)")
|
||||
|
||||
// Categorize error
|
||||
let errorInfo = categorizeError(error)
|
||||
|
||||
// Update metrics
|
||||
metrics.recordError(errorInfo)
|
||||
|
||||
// Check if retryable and within limits
|
||||
if retryable && shouldRetry(operationId: operationId, errorInfo: errorInfo) {
|
||||
return handleRetryableError(operationId: operationId, errorInfo: errorInfo)
|
||||
} else {
|
||||
return handleNonRetryableError(operationId: operationId, errorInfo: errorInfo)
|
||||
}
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error in error handler: \(error)")
|
||||
return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle error with custom retry configuration
|
||||
*
|
||||
* @param operationId Unique identifier for the operation
|
||||
* @param error Error to handle
|
||||
* @param retryConfig Custom retry configuration
|
||||
* @return ErrorResult with handling information
|
||||
*/
|
||||
func handleError(operationId: String, error: Error, retryConfig: RetryConfiguration) -> ErrorResult {
|
||||
do {
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Handling error with custom retry config for operation: \(operationId)")
|
||||
|
||||
// Categorize error
|
||||
let errorInfo = categorizeError(error)
|
||||
|
||||
// Update metrics
|
||||
metrics.recordError(errorInfo)
|
||||
|
||||
// Check if retryable with custom config
|
||||
if shouldRetry(operationId: operationId, errorInfo: errorInfo, retryConfig: retryConfig) {
|
||||
return handleRetryableError(operationId: operationId, errorInfo: errorInfo, retryConfig: retryConfig)
|
||||
} else {
|
||||
return handleNonRetryableError(operationId: operationId, errorInfo: errorInfo)
|
||||
}
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error in error handler with custom config: \(error)")
|
||||
return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error Categorization
|
||||
|
||||
/**
|
||||
* Categorize error by type, code, and severity
|
||||
*
|
||||
* @param error Error to categorize
|
||||
* @return ErrorInfo with categorization
|
||||
*/
|
||||
private func categorizeError(_ error: Error) -> ErrorInfo {
|
||||
do {
|
||||
let category = determineCategory(error)
|
||||
let errorCode = determineErrorCode(error)
|
||||
let severity = determineSeverity(error, category: category)
|
||||
|
||||
let errorInfo = ErrorInfo(
|
||||
error: error,
|
||||
category: category,
|
||||
errorCode: errorCode,
|
||||
severity: severity,
|
||||
timestamp: Date()
|
||||
)
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Error categorized: \(errorInfo)")
|
||||
return errorInfo
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error during categorization: \(error)")
|
||||
return ErrorInfo(
|
||||
error: error,
|
||||
category: .unknown,
|
||||
errorCode: "CATEGORIZATION_FAILED",
|
||||
severity: .high,
|
||||
timestamp: Date()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine error category based on error type
|
||||
*
|
||||
* @param error Error to analyze
|
||||
* @return ErrorCategory
|
||||
*/
|
||||
private func determineCategory(_ error: Error) -> ErrorCategory {
|
||||
let errorType = String(describing: type(of: error))
|
||||
let errorMessage = error.localizedDescription
|
||||
|
||||
// Network errors
|
||||
if errorType.contains("URLError") || errorType.contains("Network") ||
|
||||
errorType.contains("Connection") || errorType.contains("Timeout") {
|
||||
return .network
|
||||
}
|
||||
|
||||
// Storage errors
|
||||
if errorType.contains("SQLite") || errorType.contains("Database") ||
|
||||
errorType.contains("Storage") || errorType.contains("File") {
|
||||
return .storage
|
||||
}
|
||||
|
||||
// Permission errors
|
||||
if errorType.contains("Security") || errorType.contains("Permission") ||
|
||||
errorMessage.contains("permission") {
|
||||
return .permission
|
||||
}
|
||||
|
||||
// Configuration errors
|
||||
if errorType.contains("IllegalArgument") || errorType.contains("Configuration") ||
|
||||
errorMessage.contains("config") {
|
||||
return .configuration
|
||||
}
|
||||
|
||||
// System errors
|
||||
if errorType.contains("OutOfMemory") || errorType.contains("StackOverflow") ||
|
||||
errorType.contains("Runtime") {
|
||||
return .system
|
||||
}
|
||||
|
||||
return .unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine error code based on error details
|
||||
*
|
||||
* @param error Error to analyze
|
||||
* @return Error code string
|
||||
*/
|
||||
private func determineErrorCode(_ error: Error) -> String {
|
||||
let errorType = String(describing: type(of: error))
|
||||
let errorMessage = error.localizedDescription
|
||||
|
||||
// Generate error code based on type and message
|
||||
if !errorMessage.isEmpty {
|
||||
return "\(errorType)_\(errorMessage.hashValue)"
|
||||
} else {
|
||||
return "\(errorType)_\(Date().timeIntervalSince1970)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine error severity based on error and category
|
||||
*
|
||||
* @param error Error to analyze
|
||||
* @param category Error category
|
||||
* @return ErrorSeverity
|
||||
*/
|
||||
private func determineSeverity(_ error: Error, category: ErrorCategory) -> ErrorSeverity {
|
||||
let errorType = String(describing: type(of: error))
|
||||
|
||||
// Critical errors
|
||||
if errorType.contains("OutOfMemory") || errorType.contains("StackOverflow") {
|
||||
return .critical
|
||||
}
|
||||
|
||||
// High severity errors
|
||||
if category == .system || category == .storage {
|
||||
return .high
|
||||
}
|
||||
|
||||
// Medium severity errors
|
||||
if category == .network || category == .permission {
|
||||
return .medium
|
||||
}
|
||||
|
||||
// Low severity errors
|
||||
return .low
|
||||
}
|
||||
|
||||
// MARK: - Retry Logic
|
||||
|
||||
/**
|
||||
* Check if error should be retried
|
||||
*
|
||||
* @param operationId Operation identifier
|
||||
* @param errorInfo Error information
|
||||
* @return true if should retry
|
||||
*/
|
||||
private func shouldRetry(operationId: String, errorInfo: ErrorInfo) -> Bool {
|
||||
return shouldRetry(operationId: operationId, errorInfo: errorInfo, retryConfig: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error should be retried with custom config
|
||||
*
|
||||
* @param operationId Operation identifier
|
||||
* @param errorInfo Error information
|
||||
* @param retryConfig Custom retry configuration
|
||||
* @return true if should retry
|
||||
*/
|
||||
private func shouldRetry(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> Bool {
|
||||
do {
|
||||
// Get retry state
|
||||
var state: RetryState
|
||||
retryQueue.sync {
|
||||
if retryStates[operationId] == nil {
|
||||
retryStates[operationId] = RetryState()
|
||||
}
|
||||
state = retryStates[operationId]!
|
||||
}
|
||||
|
||||
// Check retry limits
|
||||
let maxRetries = retryConfig?.maxRetries ?? config.maxRetries
|
||||
if state.attemptCount >= maxRetries {
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Max retries exceeded for operation: \(operationId)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if error is retryable based on category
|
||||
let isRetryable = isErrorRetryable(errorInfo.category)
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Should retry: \(isRetryable) (attempt: \(state.attemptCount)/\(maxRetries))")
|
||||
return isRetryable
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error checking retry eligibility: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error category is retryable
|
||||
*
|
||||
* @param category Error category
|
||||
* @return true if retryable
|
||||
*/
|
||||
private func isErrorRetryable(_ category: ErrorCategory) -> Bool {
|
||||
switch category {
|
||||
case .network, .storage:
|
||||
return true
|
||||
case .permission, .configuration, .system, .unknown:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle retryable error
|
||||
*
|
||||
* @param operationId Operation identifier
|
||||
* @param errorInfo Error information
|
||||
* @return ErrorResult with retry information
|
||||
*/
|
||||
private func handleRetryableError(operationId: String, errorInfo: ErrorInfo) -> ErrorResult {
|
||||
return handleRetryableError(operationId: operationId, errorInfo: errorInfo, retryConfig: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle retryable error with custom config
|
||||
*
|
||||
* @param operationId Operation identifier
|
||||
* @param errorInfo Error information
|
||||
* @param retryConfig Custom retry configuration
|
||||
* @return ErrorResult with retry information
|
||||
*/
|
||||
private func handleRetryableError(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> ErrorResult {
|
||||
do {
|
||||
var state: RetryState
|
||||
retryQueue.sync {
|
||||
state = retryStates[operationId]!
|
||||
state.attemptCount += 1
|
||||
}
|
||||
|
||||
// Calculate delay with exponential backoff
|
||||
let delay = calculateRetryDelay(attemptCount: state.attemptCount, retryConfig: retryConfig)
|
||||
state.nextRetryTime = Date().addingTimeInterval(delay)
|
||||
|
||||
logger.info(DailyNotificationErrorHandler.TAG, "Retryable error handled - retry in \(delay)s (attempt \(state.attemptCount))")
|
||||
|
||||
return ErrorResult.retryable(errorInfo: errorInfo, retryDelaySeconds: delay, attemptCount: state.attemptCount)
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error handling retryable error: \(error)")
|
||||
return ErrorResult.fatal(message: "Retry handling failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle non-retryable error
|
||||
*
|
||||
* @param operationId Operation identifier
|
||||
* @param errorInfo Error information
|
||||
* @return ErrorResult with failure information
|
||||
*/
|
||||
private func handleNonRetryableError(operationId: String, errorInfo: ErrorInfo) -> ErrorResult {
|
||||
do {
|
||||
logger.warning(DailyNotificationErrorHandler.TAG, "Non-retryable error handled for operation: \(operationId)")
|
||||
|
||||
// Clean up retry state
|
||||
retryQueue.async(flags: .barrier) {
|
||||
self.retryStates.removeValue(forKey: operationId)
|
||||
}
|
||||
|
||||
return ErrorResult.fatal(errorInfo: errorInfo)
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error handling non-retryable error: \(error)")
|
||||
return ErrorResult.fatal(message: "Non-retryable error handling failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate retry delay with exponential backoff
|
||||
*
|
||||
* @param attemptCount Current attempt number
|
||||
* @param retryConfig Custom retry configuration
|
||||
* @return Delay in seconds
|
||||
*/
|
||||
private func calculateRetryDelay(attemptCount: Int, retryConfig: RetryConfiguration?) -> TimeInterval {
|
||||
do {
|
||||
let baseDelay = retryConfig?.baseDelaySeconds ?? config.baseDelaySeconds
|
||||
let multiplier = retryConfig?.backoffMultiplier ?? config.backoffMultiplier
|
||||
let maxDelay = retryConfig?.maxDelaySeconds ?? config.maxDelaySeconds
|
||||
|
||||
// Calculate exponential backoff: baseDelay * (multiplier ^ (attemptCount - 1))
|
||||
var delay = baseDelay * pow(multiplier, Double(attemptCount - 1))
|
||||
|
||||
// Cap at maximum delay
|
||||
delay = min(delay, maxDelay)
|
||||
|
||||
// Add jitter to prevent thundering herd
|
||||
let jitter = delay * 0.1 * Double.random(in: 0...1)
|
||||
delay += jitter
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Calculated retry delay: \(delay)s (attempt \(attemptCount))")
|
||||
return delay
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error calculating retry delay: \(error)")
|
||||
return config.baseDelaySeconds
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Metrics and Telemetry
|
||||
|
||||
/**
|
||||
* Get error metrics
|
||||
*
|
||||
* @return ErrorMetrics with current statistics
|
||||
*/
|
||||
func getMetrics() -> ErrorMetrics {
|
||||
return metrics
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset error metrics
|
||||
*/
|
||||
func resetMetrics() {
|
||||
metrics.reset()
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Error metrics reset")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get retry statistics
|
||||
*
|
||||
* @return RetryStatistics with retry information
|
||||
*/
|
||||
func getRetryStatistics() -> RetryStatistics {
|
||||
var totalOperations = 0
|
||||
var activeRetries = 0
|
||||
var totalRetries = 0
|
||||
|
||||
retryQueue.sync {
|
||||
totalOperations = retryStates.count
|
||||
for state in retryStates.values {
|
||||
if state.attemptCount > 0 {
|
||||
activeRetries += 1
|
||||
totalRetries += state.attemptCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RetryStatistics(totalOperations: totalOperations, activeRetries: activeRetries, totalRetries: totalRetries)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear retry states
|
||||
*/
|
||||
func clearRetryStates() {
|
||||
retryQueue.async(flags: .barrier) {
|
||||
self.retryStates.removeAll()
|
||||
}
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Retry states cleared")
|
||||
}
|
||||
|
||||
// MARK: - Data Classes
|
||||
|
||||
/**
|
||||
* Error information
|
||||
*/
|
||||
struct ErrorInfo {
|
||||
let error: Error
|
||||
let category: ErrorCategory
|
||||
let errorCode: String
|
||||
let severity: ErrorSeverity
|
||||
let timestamp: Date
|
||||
|
||||
var description: String {
|
||||
return "ErrorInfo{category=\(category), code=\(errorCode), severity=\(severity), error=\(String(describing: type(of: error)))}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry state for an operation
|
||||
*/
|
||||
private class RetryState {
|
||||
var attemptCount = 0
|
||||
var nextRetryTime = Date()
|
||||
}
|
||||
|
||||
/**
|
||||
* Error result
|
||||
*/
|
||||
struct ErrorResult {
|
||||
let success: Bool
|
||||
let retryable: Bool
|
||||
let errorInfo: ErrorInfo?
|
||||
let retryDelaySeconds: TimeInterval
|
||||
let attemptCount: Int
|
||||
let message: String
|
||||
|
||||
static func retryable(errorInfo: ErrorInfo, retryDelaySeconds: TimeInterval, attemptCount: Int) -> ErrorResult {
|
||||
return ErrorResult(success: false, retryable: true, errorInfo: errorInfo, retryDelaySeconds: retryDelaySeconds, attemptCount: attemptCount, message: "Retryable error")
|
||||
}
|
||||
|
||||
static func fatal(errorInfo: ErrorInfo) -> ErrorResult {
|
||||
return ErrorResult(success: false, retryable: false, errorInfo: errorInfo, retryDelaySeconds: 0, attemptCount: 0, message: "Fatal error")
|
||||
}
|
||||
|
||||
static func fatal(message: String) -> ErrorResult {
|
||||
return ErrorResult(success: false, retryable: false, errorInfo: nil, retryDelaySeconds: 0, attemptCount: 0, message: message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error configuration
|
||||
*/
|
||||
struct ErrorConfiguration {
|
||||
let maxRetries: Int
|
||||
let baseDelaySeconds: TimeInterval
|
||||
let maxDelaySeconds: TimeInterval
|
||||
let backoffMultiplier: Double
|
||||
|
||||
init() {
|
||||
self.maxRetries = DailyNotificationErrorHandler.DEFAULT_MAX_RETRIES
|
||||
self.baseDelaySeconds = DailyNotificationErrorHandler.DEFAULT_BASE_DELAY_SECONDS
|
||||
self.maxDelaySeconds = DailyNotificationErrorHandler.DEFAULT_MAX_DELAY_SECONDS
|
||||
self.backoffMultiplier = DailyNotificationErrorHandler.DEFAULT_BACKOFF_MULTIPLIER
|
||||
}
|
||||
|
||||
init(maxRetries: Int, baseDelaySeconds: TimeInterval, maxDelaySeconds: TimeInterval, backoffMultiplier: Double) {
|
||||
self.maxRetries = maxRetries
|
||||
self.baseDelaySeconds = baseDelaySeconds
|
||||
self.maxDelaySeconds = maxDelaySeconds
|
||||
self.backoffMultiplier = backoffMultiplier
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry configuration
|
||||
*/
|
||||
struct RetryConfiguration {
|
||||
let maxRetries: Int
|
||||
let baseDelaySeconds: TimeInterval
|
||||
let maxDelaySeconds: TimeInterval
|
||||
let backoffMultiplier: Double
|
||||
|
||||
init(maxRetries: Int, baseDelaySeconds: TimeInterval, maxDelaySeconds: TimeInterval, backoffMultiplier: Double) {
|
||||
self.maxRetries = maxRetries
|
||||
self.baseDelaySeconds = baseDelaySeconds
|
||||
self.maxDelaySeconds = maxDelaySeconds
|
||||
self.backoffMultiplier = backoffMultiplier
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error metrics
|
||||
*/
|
||||
class ErrorMetrics {
|
||||
private var totalErrors = 0
|
||||
private var networkErrors = 0
|
||||
private var storageErrors = 0
|
||||
private var schedulingErrors = 0
|
||||
private var permissionErrors = 0
|
||||
private var configurationErrors = 0
|
||||
private var systemErrors = 0
|
||||
private var unknownErrors = 0
|
||||
|
||||
func recordError(_ errorInfo: ErrorInfo) {
|
||||
totalErrors += 1
|
||||
|
||||
switch errorInfo.category {
|
||||
case .network:
|
||||
networkErrors += 1
|
||||
case .storage:
|
||||
storageErrors += 1
|
||||
case .scheduling:
|
||||
schedulingErrors += 1
|
||||
case .permission:
|
||||
permissionErrors += 1
|
||||
case .configuration:
|
||||
configurationErrors += 1
|
||||
case .system:
|
||||
systemErrors += 1
|
||||
case .unknown:
|
||||
unknownErrors += 1
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
totalErrors = 0
|
||||
networkErrors = 0
|
||||
storageErrors = 0
|
||||
schedulingErrors = 0
|
||||
permissionErrors = 0
|
||||
configurationErrors = 0
|
||||
systemErrors = 0
|
||||
unknownErrors = 0
|
||||
}
|
||||
|
||||
var totalErrorsCount: Int { return totalErrors }
|
||||
var networkErrorsCount: Int { return networkErrors }
|
||||
var storageErrorsCount: Int { return storageErrors }
|
||||
var schedulingErrorsCount: Int { return schedulingErrors }
|
||||
var permissionErrorsCount: Int { return permissionErrors }
|
||||
var configurationErrorsCount: Int { return configurationErrors }
|
||||
var systemErrorsCount: Int { return systemErrors }
|
||||
var unknownErrorsCount: Int { return unknownErrors }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry statistics
|
||||
*/
|
||||
struct RetryStatistics {
|
||||
let totalOperations: Int
|
||||
let activeRetries: Int
|
||||
let totalRetries: Int
|
||||
|
||||
var description: String {
|
||||
return "RetryStatistics{totalOps=\(totalOperations), activeRetries=\(activeRetries), totalRetries=\(totalRetries)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user