diff --git a/temporalio/lib/temporalio/converters/failure_converter.rb b/temporalio/lib/temporalio/converters/failure_converter.rb index b2c6d7b6..665ef244 100644 --- a/temporalio/lib/temporalio/converters/failure_converter.rb +++ b/temporalio/lib/temporalio/converters/failure_converter.rb @@ -45,7 +45,8 @@ def to_failure(error, converter) type: error.type, non_retryable: error.non_retryable, details: converter.to_payloads(error.details), - next_retry_delay: Internal::ProtoUtils.seconds_to_duration(error.next_retry_delay) + next_retry_delay: Internal::ProtoUtils.seconds_to_duration(error.next_retry_delay), + category: error.category ) when Error::TimeoutError failure.timeout_failure_info = Api::Failure::V1::TimeoutFailureInfo.new( @@ -132,7 +133,9 @@ def from_failure(failure, converter) non_retryable: failure.application_failure_info.non_retryable, next_retry_delay: Internal::ProtoUtils.duration_to_seconds( failure.application_failure_info.next_retry_delay - ) + ), + category: Internal::ProtoUtils.enum_to_int(Api::Enums::V1::ApplicationErrorCategory, + failure.application_failure_info.category) ) elsif failure.timeout_failure_info Error::TimeoutError.new( diff --git a/temporalio/lib/temporalio/error/failure.rb b/temporalio/lib/temporalio/error/failure.rb index 82d40799..fc69bd74 100644 --- a/temporalio/lib/temporalio/error/failure.rb +++ b/temporalio/lib/temporalio/error/failure.rb @@ -46,6 +46,9 @@ class ApplicationError < Failure # @return [Float, nil] Delay in seconds before the next activity retry attempt. attr_reader :next_retry_delay + # @return [Category] Error category. + attr_reader :category + # Create an application error. # # @param message [String] Error message. @@ -53,18 +56,33 @@ class ApplicationError < Failure # @param type [String, nil] Error type. # @param non_retryable [Boolean] Whether this error should be considered non-retryable. # @param next_retry_delay [Float, nil] Specific amount of time to delay before next retry. - def initialize(message, *details, type: nil, non_retryable: false, next_retry_delay: nil) + # @param category [Category] Error category. + def initialize( + message, + *details, + type: nil, + non_retryable: false, + next_retry_delay: nil, + category: Category::UNSPECIFIED + ) super(message) @details = details @type = type @non_retryable = non_retryable @next_retry_delay = next_retry_delay + @category = category end # @return [Boolean] Inverse of {non_retryable}. def retryable? !@non_retryable end + + # Error category. + module Category + UNSPECIFIED = Api::Enums::V1::ApplicationErrorCategory::APPLICATION_ERROR_CATEGORY_UNSPECIFIED + BENIGN = Api::Enums::V1::ApplicationErrorCategory::APPLICATION_ERROR_CATEGORY_BENIGN + end end # Error raised on workflow/activity cancellation. diff --git a/temporalio/lib/temporalio/internal/worker/activity_worker.rb b/temporalio/lib/temporalio/internal/worker/activity_worker.rb index f973c82c..4d97e737 100644 --- a/temporalio/lib/temporalio/internal/worker/activity_worker.rb +++ b/temporalio/lib/temporalio/internal/worker/activity_worker.rb @@ -279,8 +279,13 @@ def run_activity(defn, activity, input) ) else # General failure - @scoped_logger.warn('Completing activity as failed') - @scoped_logger.warn(e) + log_level = if e.is_a?(Error::ApplicationError) && e.category == Error::ApplicationError::Category::BENIGN + Logger::DEBUG + else + Logger::WARN + end + @scoped_logger.add(log_level, 'Completing activity as failed') + @scoped_logger.add(log_level, e) Bridge::Api::ActivityResult::ActivityExecutionResult.new( failed: Bridge::Api::ActivityResult::Failure.new( failure: @worker.options.client.data_converter.to_failure(e) diff --git a/temporalio/sig/temporalio/error/failure.rbs b/temporalio/sig/temporalio/error/failure.rbs index 6cbcdc0b..cc59607c 100644 --- a/temporalio/sig/temporalio/error/failure.rbs +++ b/temporalio/sig/temporalio/error/failure.rbs @@ -16,16 +16,25 @@ module Temporalio attr_reader type: String? attr_reader non_retryable: bool attr_reader next_retry_delay: duration? + attr_reader category: Category::enum def initialize: ( String message, *Object? details, ?type: String?, ?non_retryable: bool, - ?next_retry_delay: duration? + ?next_retry_delay: duration?, + ?category: Category::enum ) -> void def retryable?: -> bool + + module Category + type enum = Integer + + UNSPECIFIED: enum + BENIGN: enum + end end class CanceledError < Failure diff --git a/temporalio/test/worker_activity_test.rb b/temporalio/test/worker_activity_test.rb index 0aa0fa7d..65af2252 100644 --- a/temporalio/test/worker_activity_test.rb +++ b/temporalio/test/worker_activity_test.rb @@ -178,6 +178,12 @@ def execute(form) non_retryable: true, next_retry_delay: 1.23 ) + when 'application-benign' + raise Temporalio::Error::ApplicationError.new( + 'application-error-benign', + type: 'some-error-type', + category: Temporalio::Error::ApplicationError::Category::BENIGN + ) end end end @@ -203,6 +209,14 @@ def test_failure assert_equal 'some-error-type', error.cause.cause.type assert error.cause.cause.non_retryable assert_equal 1.23, error.cause.cause.next_retry_delay + + # Check that benign application error category is set + error = assert_raises(Temporalio::Error::WorkflowFailedError) do + execute_activity(FailureActivity, 'application-benign') + end + assert_equal 'application-error-benign', error.cause.cause.message + assert_equal 'some-error-type', error.cause.cause.type + assert_equal Temporalio::Error::ApplicationError::Category::BENIGN, error.cause.cause.category end class UnimplementedExecuteActivity < Temporalio::Activity::Definition