99 lines
4.2 KiB
Ruby
99 lines
4.2 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require 'google/protobuf/well_known_types'
|
|
require 'google/rpc/error_details_pb'
|
|
|
|
module VagrantPlugins
|
|
module CommandServe
|
|
module Util
|
|
# Adds exception logging to all public instance methods
|
|
module ExceptionTransformer
|
|
def self.included(klass)
|
|
# Get all the public instance methods. Need to search ancestors as well
|
|
# for modules like the Guest service which includes the CapabilityPlatform
|
|
# module
|
|
klass_public_instance_methods = klass.public_instance_methods
|
|
# Remove all generic instance methods from the list of ones to modify
|
|
logged_methods = klass_public_instance_methods - Object.public_instance_methods
|
|
logged_methods.each do |m_name|
|
|
klass.define_method(m_name) do |*args, **opts, &block|
|
|
begin
|
|
super(*args, **opts, &block)
|
|
rescue => err
|
|
# Since we are a generated wrapper method, it's common for this
|
|
# transformer to be hit multiple times in a given callstack.
|
|
# That means we need this check to avoid double-wrapping an
|
|
# error.
|
|
if err.is_a?(GRPC::BadStatus)
|
|
raise
|
|
end
|
|
|
|
# Here we build a gRPC-friendly version of the error so that it
|
|
# can be unpacked on the client side.
|
|
#
|
|
# This is using the error model introduced here:
|
|
# https://grpc.io/docs/guides/error/#richer-error-model
|
|
#
|
|
# And detailed here:
|
|
# https://cloud.google.com/apis/design/errors#error_model
|
|
#
|
|
# IMPORTANT: As mentioned in both those links, gRPC error
|
|
# details are returned in headers, and total headers are
|
|
# limited to 8KB. That means we need to be careful not to go
|
|
# over that limit in our messages here (which is easy to do
|
|
# when big backtraces are involved).
|
|
#
|
|
# If we go over that limit, we'll get opaque "RST_STREAM with
|
|
# error code 2" messages from clients, as discussed here:
|
|
# https://github.com/grpc/grpc-go/issues/4265
|
|
#
|
|
# Therefore, here we truncate both message and backtrace to
|
|
# 1024 characters. The message is used in three places and the
|
|
# backtrace in one, so this should hopfully keep the total
|
|
# headers below the limit in most cases.
|
|
message = ExceptionTransformer.truncate_to(err.message, 1024)
|
|
backtrace = ExceptionTransformer.truncate_to(err.backtrace.join("\n"), 1024)
|
|
metadata = {}
|
|
|
|
# VagrantErrors are user-facing and so get their message packed
|
|
# into the details.
|
|
if err.is_a? Vagrant::Errors::VagrantError
|
|
localized_msg_details_any = Google::Protobuf::Any.new
|
|
localized_msg_details_any.pack(
|
|
Google::Rpc::LocalizedMessage.new(locale: "en-US", message: message.gsub("\n", " "))
|
|
)
|
|
proto = Google::Rpc::Status.new(
|
|
code: GRPC::Core::StatusCodes::UNKNOWN,
|
|
details: [localized_msg_details_any],
|
|
message: message,
|
|
)
|
|
metadata[GRPC_DETAILS_METADATA_KEY] = Google::Rpc::Status.encode(proto)
|
|
end
|
|
grpc_error = GRPC::BadStatus.new(
|
|
GRPC::Core::StatusCodes::UNKNOWN,
|
|
"#{message}\n#{backtrace}",
|
|
metadata,
|
|
)
|
|
raise grpc_error
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Truncates a string to a given length if necessary, appending an
|
|
# ellipsis if it does
|
|
def self.truncate_to(str, len)
|
|
if str.length <= len
|
|
str
|
|
else
|
|
str[0, len-3] + "..."
|
|
end
|
|
end
|
|
|
|
GRPC_DETAILS_METADATA_KEY = "grpc-status-details-bin".freeze
|
|
end
|
|
end
|
|
end
|
|
end
|