Skip to content
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
8 changes: 5 additions & 3 deletions sentry-rails/examples/rails-6.0/config/initializers/sentry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
config.traces_sample_rate = 1.0 # set a float between 0.0 and 1.0 to enable performance monitoring
config.dsn = 'https://2fb45f003d054a7ea47feb45898f7649@o447951.ingest.sentry.io/5434472'
config.release = `git branch --show-current`
config.async = lambda do |event, hint|
Sentry::SendEventJob.perform_later(event, hint)
end
# you can use the pre-defined job for the async callback
#
# config.async = lambda do |event, hint|
# Sentry::SendEventJob.perform_later(event, hint)
# end
end
4 changes: 4 additions & 0 deletions sentry-ruby/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Refactor interface construction [#1296](https://github.com/getsentry/sentry-ruby/pull/1296)

## 4.2.2

- Add thread_id to Exception interface [#1291](https://github.com/getsentry/sentry-ruby/pull/1291)
Expand Down
12 changes: 12 additions & 0 deletions sentry-ruby/lib/sentry/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "sentry/dsn"
require "sentry/transport/configuration"
require "sentry/linecache"
require "sentry/interfaces/stacktrace_builder"

module Sentry
class Configuration
Expand Down Expand Up @@ -195,6 +196,7 @@ def initialize

@transport = Transport::Configuration.new
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)

run_post_initialization_callbacks
end

Expand Down Expand Up @@ -290,6 +292,16 @@ def tracing_enabled?
!!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
end

def stacktrace_builder
@stacktrace_builder ||= StacktraceBuilder.new(
project_root: @project_root.to_s,
app_dirs_pattern: @app_dirs_pattern,
linecache: @linecache,
context_lines: @context_lines,
backtrace_cleanup_callback: @backtrace_cleanup_callback
)
end

private

def detect_release
Expand Down
48 changes: 11 additions & 37 deletions sentry-ruby/lib/sentry/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Event
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8

attr_accessor(*ATTRIBUTES)
attr_reader :configuration, :request, :exception, :stacktrace, :threads
attr_reader :configuration, :request, :exception, :threads

def initialize(configuration:, integration_meta: nil, message: nil)
# this needs to go first because some setters rely on configuration
Expand Down Expand Up @@ -99,7 +99,6 @@ def type
def to_hash
data = serialize_attributes
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
data[:stacktrace] = stacktrace.to_hash if stacktrace
data[:request] = request.to_hash if request
data[:exception] = exception.to_hash if exception
data[:threads] = threads.to_hash if threads
Expand All @@ -112,48 +111,23 @@ def to_json_compatible
end

def add_request_interface(env)
@request = Sentry::RequestInterface.from_rack(env)
@request = Sentry::RequestInterface.build(env: env)
end

def add_threads_interface(backtrace: nil, **options)
@threads = ThreadsInterface.new(**options)
@threads.stacktrace = initialize_stacktrace_interface(backtrace) if backtrace
@threads = ThreadsInterface.build(
backtrace: backtrace,
stacktrace_builder: configuration.stacktrace_builder,
**options
)
end

def add_exception_interface(exc)
if exc.respond_to?(:sentry_context)
@extra.merge!(exc.sentry_context)
end

@exception = Sentry::ExceptionInterface.new.tap do |exc_int|
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
backtraces = Set.new
exc_int.values = exceptions.map do |e|
SingleExceptionInterface.new.tap do |int|
int.type = e.class.to_s
int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
int.module = e.class.to_s.split('::')[0...-1].join('::')
int.thread_id = Thread.current.object_id

int.stacktrace =
if e.backtrace && !backtraces.include?(e.backtrace.object_id)
backtraces << e.backtrace.object_id
initialize_stacktrace_interface(e.backtrace)
end
end
end
def add_exception_interface(exception)
if exception.respond_to?(:sentry_context)
@extra.merge!(exception.sentry_context)
end
end

def initialize_stacktrace_interface(backtrace)
StacktraceInterface.new(
backtrace: backtrace,
project_root: configuration.project_root.to_s,
app_dirs_pattern: configuration.app_dirs_pattern,
linecache: configuration.linecache,
context_lines: configuration.context_lines,
backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
)
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
end

private
Expand Down
20 changes: 19 additions & 1 deletion sentry-ruby/lib/sentry/interfaces/exception.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
module Sentry
class ExceptionInterface < Interface
attr_accessor :values
def initialize(values:)
@values = values
end

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

def self.build(exception:, stacktrace_builder:)
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
processed_backtrace_ids = Set.new

exceptions = exceptions.map do |e|
if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
processed_backtrace_ids << e.backtrace.object_id
SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
else
SingleExceptionInterface.new(exception: exception)
end
end

new(values: exceptions)
end
end
end
20 changes: 10 additions & 10 deletions sentry-ruby/lib/sentry/interfaces/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class RequestInterface < Interface

attr_accessor :url, :method, :data, :query_string, :cookies, :headers, :env

def self.from_rack(env)
def self.build(env:)
env = clean_env(env)
req = ::Rack::Request.new(env)
self.new(req)
request = ::Rack::Request.new(env)
self.new(request: request)
end

def self.clean_env(env)
Expand All @@ -34,17 +34,17 @@ def self.clean_env(env)
env
end

def initialize(req)
env = req.env
def initialize(request:)
env = request.env

if Sentry.configuration.send_default_pii
self.data = read_data_from(req)
self.cookies = req.cookies
self.data = read_data_from(request)
self.cookies = request.cookies
end

self.url = req.scheme && req.url.split('?').first
self.method = req.request_method
self.query_string = req.query_string
self.url = request.scheme && request.url.split('?').first
self.method = request.request_method
self.query_string = request.query_string

self.headers = filter_and_format_headers(env)
self.env = filter_and_format_env(env)
Expand Down
21 changes: 16 additions & 5 deletions sentry-ruby/lib/sentry/interfaces/single_exception.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
module Sentry
class SingleExceptionInterface < Interface
attr_accessor :type
attr_accessor :value
attr_accessor :module
attr_accessor :thread_id
attr_accessor :stacktrace
attr_reader :type, :value, :module, :thread_id, :stacktrace

def initialize(exception:, stacktrace: nil)
@type = exception.class.to_s
@value = exception.message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
@module = exception.class.to_s.split('::')[0...-1].join('::')
@thread_id = Thread.current.object_id
@stacktrace = stacktrace
end

def to_hash
data = super
data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
data
end

# patch this method if you want to change an exception's stacktrace frames
# also see `StacktraceBuilder.build`.
def self.build_with_stacktrace(exception:, stacktrace_builder:)
stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
new(exception: exception, stacktrace: stacktrace)
end
end
end
35 changes: 9 additions & 26 deletions sentry-ruby/lib/sentry/interfaces/stacktrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@ module Sentry
class StacktraceInterface
attr_reader :frames

def initialize(backtrace:, project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
@project_root = project_root
@frames = []

parsed_backtrace_lines = Backtrace.parse(
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
).lines

parsed_backtrace_lines.reverse.each_with_object(@frames) do |line, frames|
frame = convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
frames << frame if frame.filename
end
def initialize(frames:)
@frames = frames
end

def to_hash
Expand All @@ -22,30 +12,24 @@ def to_hash

private

def convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
frame = StacktraceInterface::Frame.new(@project_root, line)
frame.set_context(linecache, context_lines) if context_lines
frame
end

# Not actually an interface, but I want to use the same style
class Frame < Interface
attr_accessor :abs_path, :context_line, :function, :in_app,
:lineno, :module, :pre_context, :post_context, :vars
attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
:lineno, :module, :pre_context, :post_context, :vars

def initialize(project_root, line)
@project_root = project_root

@abs_path = line.file if line.file
@abs_path = line.file
@function = line.method if line.method
@lineno = line.number
@in_app = line.in_app
@module = line.module_name if line.module_name
@filename = compute_filename
end

def filename
def compute_filename
return if abs_path.nil?
return @filename if instance_variable_defined?(:@filename)

prefix =
if under_project_root? && in_app
Expand All @@ -56,19 +40,18 @@ def filename
longest_load_path
end

@filename = prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
end

def set_context(linecache, context_lines)
return unless abs_path

self.pre_context, self.context_line, self.post_context = \
@pre_context, @context_line, @post_context = \
linecache.get_file_context(abs_path, lineno, context_lines)
end

def to_hash(*args)
data = super(*args)
data[:filename] = filename
data.delete(:vars) unless vars && !vars.empty?
data.delete(:pre_context) unless pre_context && !pre_context.empty?
data.delete(:post_context) unless post_context && !post_context.empty?
Expand Down
50 changes: 50 additions & 0 deletions sentry-ruby/lib/sentry/interfaces/stacktrace_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Sentry
class StacktraceBuilder
attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :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
@linecache = linecache
@context_lines = context_lines
@backtrace_cleanup_callback = backtrace_cleanup_callback
end

# you can pass a block to customize/exclude frames:
#
# ```ruby
# builder.build(backtrace) do |frame|
# if frame.module.match?(/a_gem/)
# nil
# else
# frame
# end
# end
# ```
def build(backtrace:, &frame_callback)
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)

frames = parsed_lines.reverse.map do |line|
frame = convert_parsed_line_into_frame(line)
frame = frame_callback.call(frame) if frame_callback
frame
end.compact

StacktraceInterface.new(frames: frames)
end

private

def convert_parsed_line_into_frame(line)
frame = StacktraceInterface::Frame.new(project_root, line)
frame.set_context(linecache, context_lines) if context_lines
frame
end

def parse_backtrace_lines(backtrace)
Backtrace.parse(
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
).lines
end
end
end
12 changes: 9 additions & 3 deletions sentry-ruby/lib/sentry/interfaces/threads.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
module Sentry
class ThreadsInterface
attr_accessor :stacktrace

def initialize(crashed: false)
def initialize(crashed: false, stacktrace: nil)
@id = Thread.current.object_id
@name = Thread.current.name
@current = true
@crashed = crashed
@stacktrace = stacktrace
end

def to_hash
Expand All @@ -22,5 +21,12 @@ def to_hash
]
}
end

# patch this method if you want to change a threads interface's stacktrace frames
# also see `StacktraceBuilder.build`.
def self.build(backtrace:, stacktrace_builder:, **options)
stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
new(**options, stacktrace: stacktrace)
end
end
end
Loading