Skip to content

Commit

Permalink
[Sorbet] - Add Typings for `updater/lib/dependabot/updater/error_hand…
Browse files Browse the repository at this point in the history
…ler.rb` (#11429)
  • Loading branch information
kbukum1 authored and sachin-sandhu committed Jan 31, 2025
1 parent 568a4da commit d3e114f
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 12 deletions.
72 changes: 62 additions & 10 deletions updater/lib/dependabot/updater/error_handler.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "dependabot/errors"
Expand All @@ -14,32 +14,42 @@
# against it from Rubocop we aren't addressing right now.
#
# It feels like this concern could be slimmed down if each Dependabot::Error
# class implemented a "presenter" method to generate it's own `error-type` and
# class implemented a "presenter" method to generate its own `error-type` and
# `error-detail` since this never draws attributes from the Updater context.
#
# For now, let's just extract it and set it aside as a tangent from the critical
# path.
module Dependabot
class Updater
class ErrorHandler
extend T::Sig

# These are errors that halt the update run and are handled in the main
# backend. They do *not* raise a sentry.
RUN_HALTING_ERRORS = {
RUN_HALTING_ERRORS = T.let({
Dependabot::OutOfDisk => "out_of_disk",
Dependabot::OutOfMemory => "out_of_memory",
Dependabot::AllVersionsIgnored => "all_versions_ignored",
Dependabot::UnexpectedExternalCode => "unexpected_external_code",
Errno::ENOSPC => "out_of_disk",
Octokit::Unauthorized => "octokit_unauthorized"
}.freeze
}.freeze, T::Hash[Module, String])

sig { params(service: Service, job: Job).void }
def initialize(service:, job:)
@service = service
@job = job
@service = T.let(service, Service)
@job = T.let(job, Job)
end

# This method handles errors where there is a dependency in the current
# context. This should be used by preference where possible.
sig do
params(
error: StandardError,
dependency: T.nilable(Dependabot::Dependency),
dependency_group: T.nilable(Dependabot::DependencyGroup)
).void
end
def handle_dependency_error(error:, dependency:, dependency_group: nil)
# If the error is fatal for the run, we should re-raise it rather than
# pass it back to the service.
Expand All @@ -66,11 +76,19 @@ def handle_dependency_error(error:, dependency:, dependency_group: nil)
end

# Provides logging for errors that occur when processing a dependency
sig do
params(
dependency: T.untyped,
error: StandardError,
error_type: String,
error_detail: T.nilable(T.any(T::Hash[Symbol, T.untyped], String))
).void
end
def log_dependency_error(dependency:, error:, error_type:, error_detail: nil)
if error_type == "unknown_error"
Dependabot.logger.error "Error processing #{dependency.name} (#{error.class.name})"
Dependabot.logger.error error.message
error.backtrace.each { |line| Dependabot.logger.error line }
error.backtrace&.each { |line| Dependabot.logger.error line }
else
Dependabot.logger.info(
"Handled error whilst updating #{dependency.name}: #{error_type} #{error_detail}"
Expand All @@ -80,6 +98,12 @@ def log_dependency_error(dependency:, error:, error_type:, error_detail: nil)

# This method handles errors where there is no dependency in the current
# context.
sig do
params(
error: StandardError,
dependency_group: T.nilable(Dependabot::DependencyGroup)
).void
end
def handle_job_error(error:, dependency_group: nil)
# If the error is fatal for the run, we should re-raise it rather than
# pass it back to the service.
Expand All @@ -104,11 +128,18 @@ def handle_job_error(error:, dependency_group: nil)
end

# Provides logging for errors that occur outside of a dependency context
sig do
params(
error: StandardError,
error_type: String,
error_detail: T.nilable(T.any(T::Hash[Symbol, T.untyped], String))
).void
end
def log_job_error(error:, error_type:, error_detail: nil)
if error_type == "unknown_error"
Dependabot.logger.error "Error processing job (#{error.class.name})"
Dependabot.logger.error error.message
error.backtrace.each { |line| Dependabot.logger.error line }
error.backtrace&.each { |line| Dependabot.logger.error line }
else
Dependabot.logger.info(
"Handled error whilst processing job: #{error_type} #{error_detail}"
Expand All @@ -118,7 +149,10 @@ def log_job_error(error:, error_type:, error_detail: nil)

private

sig { returns(Service) }
attr_reader :service

sig { returns(Job) }
attr_reader :job

# This method accepts an error class and returns an appropriate `error_details` hash
Expand All @@ -127,6 +161,13 @@ def log_job_error(error:, error_type:, error_detail: nil)
# For some specific errors, it also passes additional information to the
# exception service to aid in debugging, the optional arguments provide
# context to pass through in these cases.
sig do
params(
error: StandardError,
dependency: T.nilable(Dependabot::Dependency),
dependency_group: T.nilable(Dependabot::DependencyGroup)
).returns(T::Hash[Symbol, T.untyped])
end
def error_details_for(error, dependency: nil, dependency_group: nil)
error_details = Dependabot.updater_error_details(error)
return error_details if error_details
Expand All @@ -153,12 +194,13 @@ def error_details_for(error, dependency: nil, dependency_group: nil)
{ "error-type": "unknown_error" }
end

sig { params(error: StandardError).void }
def log_unknown_error_with_backtrace(error)
error_details = {
ErrorAttributes::CLASS => error.class.to_s,
ErrorAttributes::MESSAGE => error.message,
ErrorAttributes::BACKTRACE => error.backtrace.join("\n"),
ErrorAttributes::FINGERPRINT => error.respond_to?(:sentry_context) ? error.sentry_context[:fingerprint] : nil,
ErrorAttributes::BACKTRACE => error.backtrace&.join("\n"),
ErrorAttributes::FINGERPRINT => extract_fingerprint(error),
ErrorAttributes::PACKAGE_MANAGER => job.package_manager,
ErrorAttributes::JOB_ID => job.id,
ErrorAttributes::DEPENDENCIES => job.dependencies,
Expand All @@ -171,6 +213,16 @@ def log_unknown_error_with_backtrace(error)
})
service.record_update_job_unknown_error(error_type: "unknown_error", error_details: error_details)
end

sig { params(error: StandardError).returns(T.nilable(T::Array[String])) }
def extract_fingerprint(error)
if error.respond_to?(:sentry_context)
context = T.unsafe(error).sentry_context
return context[:fingerprint] if context.is_a?(Hash)
end

nil
end
end
end
end
8 changes: 6 additions & 2 deletions updater/lib/dependabot/updater/errors.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
# typed: true
# typed: strong
# frozen_string_literal: true

module Dependabot
class Updater
class SubprocessFailed < StandardError
extend T::Sig

sig { returns(T::Hash[Symbol, T.untyped]) }
attr_reader :sentry_context

sig { params(message: String, sentry_context: T::Hash[Symbol, T.untyped]).void }
def initialize(message, sentry_context:)
super(message)

@sentry_context = sentry_context
@sentry_context = T.let(sentry_context, T::Hash[Symbol, T.untyped])
end
end
end
Expand Down

0 comments on commit d3e114f

Please sign in to comment.