Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document Event and interface classes #1675

Merged
merged 7 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions sentry-ruby/lib/sentry/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Configuration
# Rails.backtrace_cleaner.clean(backtrace)
# end
#
# @return [Proc]
# @return [Proc, nil]
attr_accessor :backtrace_cleanup_callback

# Optional Proc, called before adding the breadcrumb to the current scope
Expand Down Expand Up @@ -127,8 +127,9 @@ class Configuration
alias inspect_exception_causes_for_exclusion? inspect_exception_causes_for_exclusion

# You may provide your own LineCache for matching paths with source files.
# This may be useful if you need to get source code from places other than
# the disk. See Sentry::LineCache for the required interface you must implement.
# This may be useful if you need to get source code from places other than the disk.
# @see LineCache
# @return [LineCache]
attr_accessor :linecache

# Logger used by Sentry. In Rails, this is the Rails logger, otherwise
Expand Down
42 changes: 35 additions & 7 deletions sentry-ruby/lib/sentry/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

module Sentry
class Event
# These are readable attributes.
SERIALIZEABLE_ATTRIBUTES = %i(
event_id level timestamp
release environment server_name modules
Expand All @@ -18,6 +19,7 @@ class Event
platform sdk type
)

# These are writable attributes.
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)

MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
Expand All @@ -29,8 +31,18 @@ class Event
attr_writer(*WRITER_ATTRIBUTES)
attr_reader(*SERIALIZEABLE_ATTRIBUTES)

attr_reader :request, :exception, :threads
# @return [RequestInterface]
attr_reader :request

# @return [ExceptionInterface]
attr_reader :exception

# @return [ThreadsInterface]
attr_reader :threads

# @param configuration [Configuration]
# @param integration_meta [Hash, nil]
# @param message [String, nil]
def initialize(configuration:, integration_meta: nil, message: nil)
# Set some simple default values
@event_id = SecureRandom.uuid.delete("-")
Expand Down Expand Up @@ -63,6 +75,7 @@ def initialize(configuration:, integration_meta: nil, message: nil)
end

class << self
# @!visibility private
def get_log_message(event_hash)
message = event_hash[:message] || event_hash['message']

Expand All @@ -79,6 +92,7 @@ def get_log_message(event_hash)
'<no message value>'
end

# @!visibility private
def get_message_from_exception(event_hash)
if exception = event_hash.dig(:exception, :values, 0)
"#{exception[:type]}: #{exception[:value]}"
Expand All @@ -94,14 +108,24 @@ def configuration
Sentry.configuration
end

# Sets the event's timestamp.
# @param time [Time, Float]
# @return [void]
def timestamp=(time)
@timestamp = time.is_a?(Time) ? time.to_f : time
end

def level=(new_level) # needed to meet the Sentry spec
@level = new_level.to_s == "warn" ? :warning : new_level
# Sets the event's level.
# @param level [String, Symbol]
# @return [void]
def level=(level) # needed to meet the Sentry spec
@level = level.to_s == "warn" ? :warning : level
end

# Sets the event's request environment data with RequestInterface.
# @see RequestInterface
# @param env [Hash]
# @return [void]
def rack_env=(env)
unless request || env.empty?
add_request_interface(env)
Expand All @@ -116,6 +140,7 @@ def rack_env=(env)
end
end

# @return [Hash]
def to_hash
data = serialize_attributes
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
Expand All @@ -126,14 +151,12 @@ def to_hash
data
end

# @return [Hash]
def to_json_compatible
JSON.parse(JSON.generate(to_hash))
end

def add_request_interface(env)
@request = Sentry::RequestInterface.new(env: env, send_default_pii: @send_default_pii, rack_env_whitelist: @rack_env_whitelist)
end

# @!visibility private
def add_threads_interface(backtrace: nil, **options)
@threads = ThreadsInterface.build(
backtrace: backtrace,
Expand All @@ -142,6 +165,7 @@ def add_threads_interface(backtrace: nil, **options)
)
end

# @!visibility private
def add_exception_interface(exception)
if exception.respond_to?(:sentry_context)
@extra.merge!(exception.sentry_context)
Expand All @@ -152,6 +176,10 @@ def add_exception_interface(exception)

private

def add_request_interface(env)
@request = Sentry::RequestInterface.new(env: env, send_default_pii: @send_default_pii, rack_env_whitelist: @rack_env_whitelist)
end

def serialize_attributes
self.class::SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |att, memo|
if value = public_send(att)
Expand Down
11 changes: 1 addition & 10 deletions sentry-ruby/lib/sentry/interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@

module Sentry
class Interface
def self.inherited(klass)
name = klass.name.split("::").last.downcase.gsub("interface", "")
registered[name.to_sym] = klass
super
end

def self.registered
@@registered ||= {} # rubocop:disable Style/ClassVars
end

# @return [Hash]
def to_hash
Hash[instance_variables.map { |name| [name[1..-1].to_sym, instance_variable_get(name)] }]
end
Expand Down
14 changes: 11 additions & 3 deletions sentry-ruby/lib/sentry/interfaces/exception.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@

module Sentry
class ExceptionInterface < Interface
def initialize(values:)
@values = values
# @param exceptions [Array<SingleExceptionInterface>]
def initialize(exceptions:)
@values = exceptions
end

# @return [Hash]
def to_hash
data = super
data[:values] = data[:values].map(&:to_hash) if data[:values]
data
end

# Builds ExceptionInterface with given exception and stacktrace_builder.
# @param exception [Exception]
# @param stacktrace_builder [StacktraceBuilder]
# @see SingleExceptionInterface#build_with_stacktrace
# @see SingleExceptionInterface#initialize
# @return [ExceptionInterface]
def self.build(exception:, stacktrace_builder:)
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
processed_backtrace_ids = Set.new
Expand All @@ -25,7 +33,7 @@ def self.build(exception:, stacktrace_builder:)
end
end

new(values: exceptions)
new(exceptions: exceptions)
end
end
end
26 changes: 25 additions & 1 deletion sentry-ruby/lib/sentry/interfaces/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,32 @@ class RequestInterface < Interface
# https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
MAX_BODY_LIMIT = 4096 * 4

attr_accessor :url, :method, :data, :query_string, :cookies, :headers, :env
# @return [String]
attr_accessor :url

# @return [String]
attr_accessor :method

# @return [Hash]
attr_accessor :data

# @return [String]
attr_accessor :query_string

# @return [String]
attr_accessor :cookies

# @return [Hash]
attr_accessor :headers

# @return [Hash]
attr_accessor :env

# @param env [Hash]
# @param send_default_pii [Boolean]
# @param rack_env_whitelist [Array]
# @see Configuration#send_default_pii
# @see Configuration#rack_env_whitelist
def initialize(env:, send_default_pii:, rack_env_whitelist:)
env = env.dup

Expand Down
4 changes: 4 additions & 0 deletions sentry-ruby/lib/sentry/interfaces/stacktrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

module Sentry
class StacktraceInterface
# @return [<Array[Frame]>]
attr_reader :frames

# @param frames [<Array[Frame]>]
def initialize(frames:)
@frames = frames
end

# @return [Hash]
def to_hash
{ frames: @frames.map(&:to_hash) }
end

# @return [String]
def inspect
@frames.map(&:to_s)
end
Expand Down
47 changes: 37 additions & 10 deletions sentry-ruby/lib/sentry/interfaces/stacktrace_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,31 @@

module Sentry
class StacktraceBuilder
attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :backtrace_cleanup_callback
# @return [String]
attr_reader :project_root

# @return [Regexp, nil]
attr_reader :app_dirs_pattern

# @return [LineCache]
attr_reader :linecache

# @return [Integer, nil]
attr_reader :context_lines

# @return [Proc, nil]
attr_reader :backtrace_cleanup_callback

# @param project_root [String]
# @param app_dirs_pattern [Regexp, nil]
# @param linecache [LineCache]
# @param context_lines [Integer, nil]
# @param backtrace_cleanup_callback [Proc, nil]
# @see Configuration#project_root
# @see Configuration#app_dirs_pattern
# @see Configuration#linecache
# @see Configuration#context_lines
# @see Configuration#backtrace_cleanup_callback
def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
@project_root = project_root
@app_dirs_pattern = app_dirs_pattern
Expand All @@ -12,17 +35,21 @@ def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, bac
@backtrace_cleanup_callback = backtrace_cleanup_callback
end

# you can pass a block to customize/exclude frames:
# Generates a StacktraceInterface with the given backtrace.
# You can pass a block to customize/exclude frames:
#
# ```ruby
# builder.build(backtrace) do |frame|
# if frame.module.match?(/a_gem/)
# nil
# else
# frame
# @example
# builder.build(backtrace) do |frame|
# if frame.module.match?(/a_gem/)
# nil
# else
# frame
# end
# end
# end
# ```
# @param backtrace [Array<String>]
# @param frame_callback [Proc]
# @yieldparam frame [StacktraceInterface::Frame]
# @return [StacktraceInterface]
def build(backtrace:, &frame_callback)
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)

Expand Down
12 changes: 10 additions & 2 deletions sentry-ruby/lib/sentry/interfaces/threads.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module Sentry
class ThreadsInterface
# @param crashed [Boolean]
# @param stacktrace [Array]
def initialize(crashed: false, stacktrace: nil)
@id = Thread.current.object_id
@name = Thread.current.name
Expand All @@ -10,6 +12,7 @@ def initialize(crashed: false, stacktrace: nil)
@stacktrace = stacktrace
end

# @return [Hash]
def to_hash
{
values: [
Expand All @@ -24,8 +27,13 @@ def to_hash
}
end

# patch this method if you want to change a threads interface's stacktrace frames
# also see `StacktraceBuilder.build`.
# Builds the ThreadsInterface with given backtrace and stacktrace_builder.
# Patch this method if you want to change a threads interface's stacktrace frames.
# @see StacktraceBuilder.build
# @param backtrace [Array]
# @param stacktrace_builder [StacktraceBuilder]
# @param crashed [Hash]
# @return [ThreadsInterface]
def self.build(backtrace:, stacktrace_builder:, **options)
stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
new(**options, stacktrace: stacktrace)
Expand Down
4 changes: 0 additions & 4 deletions sentry-ruby/spec/sentry/interface_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ class TestInterface < Sentry::Interface
end

RSpec.describe Sentry::Interface do
it "should register an interface when a new class is defined" do
expect(Sentry::Interface.registered[:test]).to eq(TestInterface)
end

it "serializes to a Hash" do
interface = TestInterface.new
interface.some_attr = "test"
Expand Down