sentry-ruby 5.25.0 → 5.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -3
- data/Rakefile +6 -9
- data/lib/sentry/client.rb +4 -2
- data/lib/sentry/configuration.rb +26 -0
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +32 -0
- data/lib/sentry/hub.rb +3 -1
- data/lib/sentry/log_event.rb +1 -1
- data/lib/sentry/propagation_context.rb +55 -18
- data/lib/sentry/std_lib_logger.rb +50 -0
- data/lib/sentry/test_helper.rb +22 -0
- data/lib/sentry/transaction.rb +29 -1
- data/lib/sentry/transport/debug_transport.rb +70 -0
- data/lib/sentry/transport/dummy_transport.rb +1 -0
- data/lib/sentry/transport/http_transport.rb +9 -5
- data/lib/sentry/transport.rb +1 -0
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +7 -2
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b489d374f24123e3e62626462b08bfa3ea1d0a8c03f7332dd8d353860b065bdf
|
4
|
+
data.tar.gz: f6548949011e59234ce7b90c04f6a3cc95fb0876e93ac598907f6fac7ccd4560
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f02b67848b924a4fe9827ca5abfb3010035ebe41194d0859bd412b1effce07018c261fd59b1eb28efb9721452985ef6895efb4ba5fa297d82fc58c880d1c5ae
|
7
|
+
data.tar.gz: 1a8f620b58693aa0b30a81b9fdbdd94f78761c519fb49bbb9a57f6bb82296de8d248f3e537718ebb5325f730abac55da0ebd446db86d9e196f56532db1414097
|
data/Gemfile
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
git_source(:github) { |name| "https://github.com/#{name}.git" }
|
5
5
|
|
6
|
-
eval_gemfile "../Gemfile"
|
6
|
+
eval_gemfile "../Gemfile.dev"
|
7
7
|
|
8
8
|
gem "sentry-ruby", path: "./"
|
9
9
|
|
@@ -11,8 +11,6 @@ rack_version = ENV["RACK_VERSION"]
|
|
11
11
|
rack_version = "3.0.0" if rack_version.nil?
|
12
12
|
gem "rack", "~> #{Gem::Version.new(rack_version)}" unless rack_version == "0"
|
13
13
|
|
14
|
-
gem "ostruct" if RUBY_VERSION >= "3.4"
|
15
|
-
|
16
14
|
redis_rb_version = ENV.fetch("REDIS_RB_VERSION", "5.0")
|
17
15
|
gem "redis", "~> #{redis_rb_version}"
|
18
16
|
|
data/Rakefile
CHANGED
@@ -6,16 +6,13 @@ CLOBBER.include "pkg"
|
|
6
6
|
require "bundler/gem_helper"
|
7
7
|
Bundler::GemHelper.install_tasks(name: "sentry-ruby")
|
8
8
|
|
9
|
-
|
9
|
+
require_relative "../lib/sentry/test/rake_tasks"
|
10
10
|
|
11
11
|
ISOLATED_SPECS = "spec/isolated/**/*_spec.rb"
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Sentry::Test::RakeTasks.define_spec_tasks(
|
14
|
+
isolated_specs_pattern: ISOLATED_SPECS,
|
15
|
+
spec_exclude_pattern: ISOLATED_SPECS
|
16
|
+
)
|
16
17
|
|
17
|
-
|
18
|
-
task.pattern = ISOLATED_SPECS
|
19
|
-
end
|
20
|
-
|
21
|
-
task default: [:spec, :isolated_specs]
|
18
|
+
task default: [:spec, :"spec:isolated"]
|
data/lib/sentry/client.rb
CHANGED
@@ -42,7 +42,9 @@ module Sentry
|
|
42
42
|
|
43
43
|
@spotlight_transport = SpotlightTransport.new(configuration) if configuration.spotlight
|
44
44
|
|
45
|
-
|
45
|
+
if configuration.enable_logs
|
46
|
+
@log_event_buffer = LogEventBuffer.new(configuration, self).start
|
47
|
+
end
|
46
48
|
end
|
47
49
|
|
48
50
|
# Applies the given scope's data to the event and sends it to Sentry.
|
@@ -114,7 +116,7 @@ module Sentry
|
|
114
116
|
def flush
|
115
117
|
transport.flush if configuration.sending_to_dsn_allowed?
|
116
118
|
spotlight_transport.flush if spotlight_transport
|
117
|
-
@log_event_buffer
|
119
|
+
@log_event_buffer&.flush
|
118
120
|
end
|
119
121
|
|
120
122
|
# Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
|
data/lib/sentry/configuration.rb
CHANGED
@@ -13,6 +13,7 @@ require "sentry/metrics/configuration"
|
|
13
13
|
require "sentry/linecache"
|
14
14
|
require "sentry/interfaces/stacktrace_builder"
|
15
15
|
require "sentry/logger"
|
16
|
+
require "sentry/structured_logger"
|
16
17
|
require "sentry/log_event_buffer"
|
17
18
|
|
18
19
|
module Sentry
|
@@ -196,6 +197,11 @@ module Sentry
|
|
196
197
|
# @return [Logger]
|
197
198
|
attr_accessor :sdk_logger
|
198
199
|
|
200
|
+
# File path for DebugTransport to log events to. If not set, defaults to a temporary file.
|
201
|
+
# This is useful for debugging and testing purposes.
|
202
|
+
# @return [String, nil]
|
203
|
+
attr_accessor :sdk_debug_transport_log_file
|
204
|
+
|
199
205
|
# @deprecated Use {#sdk_logger=} instead.
|
200
206
|
def logger=(logger)
|
201
207
|
warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."
|
@@ -289,6 +295,10 @@ module Sentry
|
|
289
295
|
# @return [Boolean]
|
290
296
|
attr_accessor :enable_logs
|
291
297
|
|
298
|
+
# Structured logging configuration.
|
299
|
+
# @return [StructuredLoggingConfiguration]
|
300
|
+
attr_reader :structured_logging
|
301
|
+
|
292
302
|
# Easier way to use performance tracing
|
293
303
|
# If set to true, will set traces_sample_rate to 1.0
|
294
304
|
# @deprecated It will be removed in the next major release.
|
@@ -485,6 +495,7 @@ module Sentry
|
|
485
495
|
@transport = Transport::Configuration.new
|
486
496
|
@cron = Cron::Configuration.new
|
487
497
|
@metrics = Metrics::Configuration.new
|
498
|
+
@structured_logging = StructuredLoggingConfiguration.new
|
488
499
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
489
500
|
|
490
501
|
run_post_initialization_callbacks
|
@@ -784,4 +795,19 @@ module Sentry
|
|
784
795
|
available_processor_count || Concurrent.processor_count
|
785
796
|
end
|
786
797
|
end
|
798
|
+
|
799
|
+
class StructuredLoggingConfiguration
|
800
|
+
# File path for DebugStructuredLogger to log events to
|
801
|
+
# @return [String, Pathname, nil]
|
802
|
+
attr_accessor :file_path
|
803
|
+
|
804
|
+
# The class to use as a structured logger.
|
805
|
+
# @return [Class]
|
806
|
+
attr_accessor :logger_class
|
807
|
+
|
808
|
+
def initialize
|
809
|
+
@file_path = nil
|
810
|
+
@logger_class = Sentry::StructuredLogger
|
811
|
+
end
|
812
|
+
end
|
787
813
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fileutils"
|
5
|
+
require "pathname"
|
6
|
+
require "delegate"
|
7
|
+
|
8
|
+
module Sentry
|
9
|
+
# DebugStructuredLogger is a logger that captures structured log events to a file for debugging purposes.
|
10
|
+
#
|
11
|
+
# It can optionally also send log events to Sentry via the normal structured logger if logging
|
12
|
+
# is enabled.
|
13
|
+
class DebugStructuredLogger < SimpleDelegator
|
14
|
+
DEFAULT_LOG_FILE_PATH = File.join("log", "sentry_debug_logs.log")
|
15
|
+
|
16
|
+
attr_reader :log_file, :backend
|
17
|
+
|
18
|
+
def initialize(configuration)
|
19
|
+
@log_file = initialize_log_file(
|
20
|
+
configuration.structured_logging.file_path || DEFAULT_LOG_FILE_PATH
|
21
|
+
)
|
22
|
+
@backend = initialize_backend(configuration)
|
23
|
+
|
24
|
+
super(@backend)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Override all log level methods to capture events
|
28
|
+
%i[trace debug info warn error fatal].each do |level|
|
29
|
+
define_method(level) do |message, parameters = [], **attributes|
|
30
|
+
log_event = capture_log_event(level, message, parameters, **attributes)
|
31
|
+
backend.public_send(level, message, parameters, **attributes)
|
32
|
+
log_event
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def log(level, message, parameters:, **attributes)
|
37
|
+
log_event = capture_log_event(level, message, parameters, **attributes)
|
38
|
+
backend.log(level, message, parameters: parameters, **attributes)
|
39
|
+
log_event
|
40
|
+
end
|
41
|
+
|
42
|
+
def capture_log_event(level, message, parameters, **attributes)
|
43
|
+
log_event_json = {
|
44
|
+
timestamp: Time.now.utc.iso8601,
|
45
|
+
level: level.to_s,
|
46
|
+
message: message,
|
47
|
+
parameters: parameters,
|
48
|
+
attributes: attributes
|
49
|
+
}
|
50
|
+
|
51
|
+
File.open(log_file, "a") { |file| file << JSON.dump(log_event_json) << "\n" }
|
52
|
+
log_event_json
|
53
|
+
end
|
54
|
+
|
55
|
+
def logged_events
|
56
|
+
File.readlines(log_file).map do |line|
|
57
|
+
JSON.parse(line)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def clear
|
62
|
+
File.write(log_file, "")
|
63
|
+
if backend.respond_to?(:config)
|
64
|
+
backend.config.sdk_logger.debug("DebugStructuredLogger: Cleared events from #{log_file}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def initialize_backend(configuration)
|
71
|
+
if configuration.enable_logs
|
72
|
+
StructuredLogger.new(configuration)
|
73
|
+
else
|
74
|
+
# Create a no-op logger if logging is disabled
|
75
|
+
NoOpLogger.new
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize_log_file(log_file_path)
|
80
|
+
log_file = Pathname(log_file_path)
|
81
|
+
|
82
|
+
FileUtils.mkdir_p(log_file.dirname) unless log_file.dirname.exist?
|
83
|
+
|
84
|
+
log_file
|
85
|
+
end
|
86
|
+
|
87
|
+
# No-op logger for when structured logging is disabled
|
88
|
+
class NoOpLogger
|
89
|
+
%i[trace debug info warn error fatal log].each do |method|
|
90
|
+
define_method(method) { |*args, **kwargs| nil }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/sentry/dsn.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "uri"
|
4
|
+
require "ipaddr"
|
5
|
+
require "resolv"
|
4
6
|
|
5
7
|
module Sentry
|
6
8
|
class DSN
|
7
9
|
PORT_MAP = { "http" => 80, "https" => 443 }.freeze
|
8
10
|
REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
|
11
|
+
LOCALHOST_NAMES = %w[localhost 127.0.0.1 ::1 [::1]].freeze
|
12
|
+
LOCALHOST_PATTERN = /\.local(host|domain)?$/i
|
9
13
|
|
10
14
|
attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
|
11
15
|
|
@@ -49,5 +53,33 @@ module Sentry
|
|
49
53
|
def envelope_endpoint
|
50
54
|
"#{path}/api/#{project_id}/envelope/"
|
51
55
|
end
|
56
|
+
|
57
|
+
def local?
|
58
|
+
@local ||= (localhost? || private_ip? || resolved_ips_private?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def localhost?
|
62
|
+
LOCALHOST_NAMES.include?(host.downcase) || LOCALHOST_PATTERN.match?(host)
|
63
|
+
end
|
64
|
+
|
65
|
+
def private_ip?
|
66
|
+
@private_ip ||= begin
|
67
|
+
begin
|
68
|
+
IPAddr.new(host).private?
|
69
|
+
rescue IPAddr::InvalidAddressError
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def resolved_ips_private?
|
76
|
+
@resolved_ips_private ||= begin
|
77
|
+
begin
|
78
|
+
Resolv.getaddresses(host).any? { |ip| IPAddr.new(ip).private? }
|
79
|
+
rescue Resolv::ResolvError, IPAddr::InvalidAddressError
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
52
84
|
end
|
53
85
|
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -120,7 +120,8 @@ module Sentry
|
|
120
120
|
|
121
121
|
sampling_context = {
|
122
122
|
transaction_context: transaction.to_hash,
|
123
|
-
parent_sampled: transaction.parent_sampled
|
123
|
+
parent_sampled: transaction.parent_sampled,
|
124
|
+
parent_sample_rate: transaction.parent_sample_rate
|
124
125
|
}
|
125
126
|
|
126
127
|
sampling_context.merge!(custom_sampling_context)
|
@@ -357,6 +358,7 @@ module Sentry
|
|
357
358
|
parent_span_id: propagation_context.parent_span_id,
|
358
359
|
parent_sampled: propagation_context.parent_sampled,
|
359
360
|
baggage: propagation_context.baggage,
|
361
|
+
sample_rand: propagation_context.sample_rand,
|
360
362
|
**options
|
361
363
|
)
|
362
364
|
end
|
data/lib/sentry/log_event.rb
CHANGED
@@ -42,7 +42,7 @@ module Sentry
|
|
42
42
|
|
43
43
|
attr_accessor :level, :body, :template, :attributes, :user
|
44
44
|
|
45
|
-
attr_reader :configuration, *SERIALIZEABLE_ATTRIBUTES
|
45
|
+
attr_reader :configuration, *(SERIALIZEABLE_ATTRIBUTES - %i[level body attributes])
|
46
46
|
|
47
47
|
SERIALIZERS = %i[
|
48
48
|
attributes
|
@@ -3,15 +3,14 @@
|
|
3
3
|
require "securerandom"
|
4
4
|
require "sentry/baggage"
|
5
5
|
require "sentry/utils/uuid"
|
6
|
+
require "sentry/utils/sample_rand"
|
6
7
|
|
7
8
|
module Sentry
|
8
9
|
class PropagationContext
|
9
10
|
SENTRY_TRACE_REGEXP = Regexp.new(
|
10
|
-
"
|
11
|
-
"([0-9a-f]{32})?" + # trace_id
|
11
|
+
"\\A([0-9a-f]{32})?" + # trace_id
|
12
12
|
"-?([0-9a-f]{16})?" + # span_id
|
13
|
-
"-?([01])
|
14
|
-
"[ \t]*$" # whitespace
|
13
|
+
"-?([01])?\\z" # sampled
|
15
14
|
)
|
16
15
|
|
17
16
|
# An uuid that can be used to identify a trace.
|
@@ -33,6 +32,53 @@ module Sentry
|
|
33
32
|
# Please use the #get_baggage method for interfacing outside this class.
|
34
33
|
# @return [Baggage, nil]
|
35
34
|
attr_reader :baggage
|
35
|
+
# The propagated random value used for sampling decisions.
|
36
|
+
# @return [Float, nil]
|
37
|
+
attr_reader :sample_rand
|
38
|
+
|
39
|
+
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
40
|
+
#
|
41
|
+
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
42
|
+
# @return [Array, nil]
|
43
|
+
def self.extract_sentry_trace(sentry_trace)
|
44
|
+
value = sentry_trace.to_s.strip
|
45
|
+
return if value.empty?
|
46
|
+
|
47
|
+
match = SENTRY_TRACE_REGEXP.match(value)
|
48
|
+
return if match.nil?
|
49
|
+
|
50
|
+
trace_id, parent_span_id, sampled_flag = match[1..3]
|
51
|
+
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
52
|
+
|
53
|
+
[trace_id, parent_span_id, parent_sampled]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
|
57
|
+
return unless baggage&.items
|
58
|
+
|
59
|
+
sample_rand_str = baggage.items["sample_rand"]
|
60
|
+
return unless sample_rand_str
|
61
|
+
|
62
|
+
generator = Utils::SampleRand.new(trace_id: trace_id)
|
63
|
+
generator.generate_from_value(sample_rand_str)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.generate_sample_rand(baggage, trace_id, parent_sampled)
|
67
|
+
generator = Utils::SampleRand.new(trace_id: trace_id)
|
68
|
+
|
69
|
+
if baggage&.items && !parent_sampled.nil?
|
70
|
+
sample_rate_str = baggage.items["sample_rate"]
|
71
|
+
sample_rate = sample_rate_str&.to_f
|
72
|
+
|
73
|
+
if sample_rate && !parent_sampled.nil?
|
74
|
+
generator.generate_from_sampling_decision(parent_sampled, sample_rate)
|
75
|
+
else
|
76
|
+
generator.generate_from_trace_id
|
77
|
+
end
|
78
|
+
else
|
79
|
+
generator.generate_from_trace_id
|
80
|
+
end
|
81
|
+
end
|
36
82
|
|
37
83
|
def initialize(scope, env = nil)
|
38
84
|
@scope = scope
|
@@ -40,6 +86,7 @@ module Sentry
|
|
40
86
|
@parent_sampled = nil
|
41
87
|
@baggage = nil
|
42
88
|
@incoming_trace = false
|
89
|
+
@sample_rand = nil
|
43
90
|
|
44
91
|
if env
|
45
92
|
sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
|
@@ -61,6 +108,8 @@ module Sentry
|
|
61
108
|
Baggage.new({})
|
62
109
|
end
|
63
110
|
|
111
|
+
@sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
|
112
|
+
|
64
113
|
@baggage.freeze!
|
65
114
|
@incoming_trace = true
|
66
115
|
end
|
@@ -69,20 +118,7 @@ module Sentry
|
|
69
118
|
|
70
119
|
@trace_id ||= Utils.uuid
|
71
120
|
@span_id = Utils.uuid.slice(0, 16)
|
72
|
-
|
73
|
-
|
74
|
-
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
75
|
-
#
|
76
|
-
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
77
|
-
# @return [Array, nil]
|
78
|
-
def self.extract_sentry_trace(sentry_trace)
|
79
|
-
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
80
|
-
return nil if match.nil?
|
81
|
-
|
82
|
-
trace_id, parent_span_id, sampled_flag = match[1..3]
|
83
|
-
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
84
|
-
|
85
|
-
[trace_id, parent_span_id, parent_sampled]
|
121
|
+
@sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
|
86
122
|
end
|
87
123
|
|
88
124
|
# Returns the trace context that can be used to embed in an Event.
|
@@ -123,6 +159,7 @@ module Sentry
|
|
123
159
|
|
124
160
|
items = {
|
125
161
|
"trace_id" => trace_id,
|
162
|
+
"sample_rand" => Utils::SampleRand.format(@sample_rand),
|
126
163
|
"environment" => configuration.environment,
|
127
164
|
"release" => configuration.release,
|
128
165
|
"public_key" => configuration.dsn&.public_key
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
# Ruby Logger support Add commentMore actions
|
5
|
+
# intercepts any logger instance and send the log to Sentry too.
|
6
|
+
module StdLibLogger
|
7
|
+
SEVERITY_MAP = {
|
8
|
+
0 => :debug,
|
9
|
+
1 => :info,
|
10
|
+
2 => :warn,
|
11
|
+
3 => :error,
|
12
|
+
4 => :fatal
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def add(severity, message = nil, progname = nil, &block)
|
16
|
+
result = super
|
17
|
+
|
18
|
+
return unless Sentry.initialized? && Sentry.get_current_hub
|
19
|
+
|
20
|
+
# exclude sentry SDK logs -- to prevent recursive log action,
|
21
|
+
# do not process internal logs again
|
22
|
+
if message.nil? && progname != Sentry::Logger::PROGNAME
|
23
|
+
|
24
|
+
# handle different nature of Ruby Logger class:
|
25
|
+
# inspo from Sentry::Breadcrumb::SentryLogger
|
26
|
+
if block_given?
|
27
|
+
message = yield
|
28
|
+
else
|
29
|
+
message = progname
|
30
|
+
end
|
31
|
+
|
32
|
+
message = message.to_s.strip
|
33
|
+
|
34
|
+
if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
|
35
|
+
Sentry.logger.send(method, message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Sentry.register_patch(:logger) do |config|
|
45
|
+
if config.enable_logs
|
46
|
+
::Logger.prepend(Sentry::StdLibLogger)
|
47
|
+
else
|
48
|
+
config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch")
|
49
|
+
end
|
50
|
+
end
|
data/lib/sentry/test_helper.rb
CHANGED
@@ -2,8 +2,13 @@
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
module TestHelper
|
5
|
+
module_function
|
6
|
+
|
5
7
|
DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
|
6
8
|
|
9
|
+
# Not really real, but it will be resolved as a non-local for testing needs
|
10
|
+
REAL_DSN = "https://user:pass@getsentry.io/project/42"
|
11
|
+
|
7
12
|
# Alters the existing SDK configuration with test-suitable options. Mainly:
|
8
13
|
# - Sets a dummy DSN instead of `nil` or an actual DSN.
|
9
14
|
# - Sets the transport to DummyTransport, which allows easy access to the captured events.
|
@@ -46,6 +51,8 @@ module Sentry
|
|
46
51
|
def teardown_sentry_test
|
47
52
|
return unless Sentry.initialized?
|
48
53
|
|
54
|
+
clear_sentry_events
|
55
|
+
|
49
56
|
# pop testing layer created by `setup_sentry_test`
|
50
57
|
# but keep the base layer to avoid nil-pointer errors
|
51
58
|
# TODO: find a way to notify users if they somehow popped the test layer before calling this method
|
@@ -55,6 +62,21 @@ module Sentry
|
|
55
62
|
Sentry::Scope.global_event_processors.clear
|
56
63
|
end
|
57
64
|
|
65
|
+
def clear_sentry_events
|
66
|
+
return unless Sentry.initialized?
|
67
|
+
|
68
|
+
sentry_transport.clear if sentry_transport.respond_to?(:clear)
|
69
|
+
|
70
|
+
if Sentry.configuration.enable_logs && sentry_logger.respond_to?(:clear)
|
71
|
+
sentry_logger.clear
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Sentry::StructuredLogger, Sentry::DebugStructuredLogger]
|
76
|
+
def sentry_logger
|
77
|
+
Sentry.logger
|
78
|
+
end
|
79
|
+
|
58
80
|
# @return [Transport]
|
59
81
|
def sentry_transport
|
60
82
|
Sentry.get_current_client.transport
|
data/lib/sentry/transaction.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "sentry/baggage"
|
4
4
|
require "sentry/profiler"
|
5
|
+
require "sentry/utils/sample_rand"
|
5
6
|
require "sentry/propagation_context"
|
6
7
|
|
7
8
|
module Sentry
|
@@ -57,12 +58,17 @@ module Sentry
|
|
57
58
|
# @return [Profiler]
|
58
59
|
attr_reader :profiler
|
59
60
|
|
61
|
+
# Sample rand value generated from trace_id
|
62
|
+
# @return [String]
|
63
|
+
attr_reader :sample_rand
|
64
|
+
|
60
65
|
def initialize(
|
61
66
|
hub:,
|
62
67
|
name: nil,
|
63
68
|
source: :custom,
|
64
69
|
parent_sampled: nil,
|
65
70
|
baggage: nil,
|
71
|
+
sample_rand: nil,
|
66
72
|
**options
|
67
73
|
)
|
68
74
|
super(transaction: self, **options)
|
@@ -82,12 +88,18 @@ module Sentry
|
|
82
88
|
@effective_sample_rate = nil
|
83
89
|
@contexts = {}
|
84
90
|
@measurements = {}
|
91
|
+
@sample_rand = sample_rand
|
85
92
|
|
86
93
|
unless @hub.profiler_running?
|
87
94
|
@profiler = @configuration.profiler_class.new(@configuration)
|
88
95
|
end
|
89
96
|
|
90
97
|
init_span_recorder
|
98
|
+
|
99
|
+
unless @sample_rand
|
100
|
+
generator = Utils::SampleRand.new(trace_id: @trace_id)
|
101
|
+
@sample_rand = generator.generate_from_trace_id
|
102
|
+
end
|
91
103
|
end
|
92
104
|
|
93
105
|
# @deprecated use Sentry.continue_trace instead.
|
@@ -123,12 +135,15 @@ module Sentry
|
|
123
135
|
|
124
136
|
baggage.freeze!
|
125
137
|
|
138
|
+
sample_rand = extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
|
139
|
+
|
126
140
|
new(
|
127
141
|
trace_id: trace_id,
|
128
142
|
parent_span_id: parent_span_id,
|
129
143
|
parent_sampled: parent_sampled,
|
130
144
|
hub: hub,
|
131
145
|
baggage: baggage,
|
146
|
+
sample_rand: sample_rand,
|
132
147
|
**options
|
133
148
|
)
|
134
149
|
end
|
@@ -139,6 +154,11 @@ module Sentry
|
|
139
154
|
PropagationContext.extract_sentry_trace(sentry_trace)
|
140
155
|
end
|
141
156
|
|
157
|
+
def self.extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
|
158
|
+
PropagationContext.extract_sample_rand_from_baggage(baggage, trace_id) ||
|
159
|
+
PropagationContext.generate_sample_rand(baggage, trace_id, parent_sampled)
|
160
|
+
end
|
161
|
+
|
142
162
|
# @return [Hash]
|
143
163
|
def to_hash
|
144
164
|
hash = super
|
@@ -153,6 +173,13 @@ module Sentry
|
|
153
173
|
hash
|
154
174
|
end
|
155
175
|
|
176
|
+
def parent_sample_rate
|
177
|
+
return unless @baggage&.items
|
178
|
+
|
179
|
+
sample_rate_str = @baggage.items["sample_rate"]
|
180
|
+
sample_rate_str&.to_f
|
181
|
+
end
|
182
|
+
|
156
183
|
# @return [Transaction]
|
157
184
|
def deep_dup
|
158
185
|
copy = super
|
@@ -225,7 +252,7 @@ module Sentry
|
|
225
252
|
@effective_sample_rate /= 2**factor
|
226
253
|
end
|
227
254
|
|
228
|
-
@sampled =
|
255
|
+
@sampled = @sample_rand < @effective_sample_rate
|
229
256
|
end
|
230
257
|
|
231
258
|
if @sampled
|
@@ -331,6 +358,7 @@ module Sentry
|
|
331
358
|
items = {
|
332
359
|
"trace_id" => trace_id,
|
333
360
|
"sample_rate" => effective_sample_rate&.to_s,
|
361
|
+
"sample_rand" => Utils::SampleRand.format(@sample_rand),
|
334
362
|
"sampled" => sampled&.to_s,
|
335
363
|
"environment" => @environment,
|
336
364
|
"release" => @release,
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fileutils"
|
5
|
+
require "pathname"
|
6
|
+
require "delegate"
|
7
|
+
|
8
|
+
module Sentry
|
9
|
+
# DebugTransport is a transport that logs events to a file for debugging purposes.
|
10
|
+
#
|
11
|
+
# It can optionally also send events to Sentry via HTTP transport if a real DSN
|
12
|
+
# is provided.
|
13
|
+
class DebugTransport < SimpleDelegator
|
14
|
+
DEFAULT_LOG_FILE_PATH = File.join("log", "sentry_debug_events.log")
|
15
|
+
|
16
|
+
attr_reader :log_file, :backend
|
17
|
+
|
18
|
+
def initialize(configuration)
|
19
|
+
@log_file = initialize_log_file(configuration)
|
20
|
+
@backend = initialize_backend(configuration)
|
21
|
+
|
22
|
+
super(@backend)
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_event(event)
|
26
|
+
log_envelope(envelope_from_event(event))
|
27
|
+
backend.send_event(event)
|
28
|
+
end
|
29
|
+
|
30
|
+
def log_envelope(envelope)
|
31
|
+
envelope_json = {
|
32
|
+
timestamp: Time.now.utc.iso8601,
|
33
|
+
envelope_headers: envelope.headers,
|
34
|
+
items: envelope.items.map do |item|
|
35
|
+
{ headers: item.headers, payload: item.payload }
|
36
|
+
end
|
37
|
+
}
|
38
|
+
|
39
|
+
File.open(log_file, "a") { |file| file << JSON.dump(envelope_json) << "\n" }
|
40
|
+
end
|
41
|
+
|
42
|
+
def logged_envelopes
|
43
|
+
return [] unless File.exist?(log_file)
|
44
|
+
|
45
|
+
File.readlines(log_file).map do |line|
|
46
|
+
JSON.parse(line)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear
|
51
|
+
File.write(log_file, "")
|
52
|
+
log_debug("DebugTransport: Cleared events from #{log_file}")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def initialize_backend(configuration)
|
58
|
+
backend = configuration.dsn.local? ? DummyTransport : HTTPTransport
|
59
|
+
backend.new(configuration)
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize_log_file(configuration)
|
63
|
+
log_file = Pathname(configuration.sdk_debug_transport_log_file || DEFAULT_LOG_FILE_PATH)
|
64
|
+
|
65
|
+
FileUtils.mkdir_p(log_file.dirname) unless log_file.dirname.exist?
|
66
|
+
|
67
|
+
log_file
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -45,11 +45,7 @@ module Sentry
|
|
45
45
|
auth_header = generate_auth_header
|
46
46
|
headers["X-Sentry-Auth"] = auth_header if auth_header
|
47
47
|
|
48
|
-
response =
|
49
|
-
request = ::Net::HTTP::Post.new(endpoint, headers)
|
50
|
-
request.body = data
|
51
|
-
http.request(request)
|
52
|
-
end
|
48
|
+
response = do_request(endpoint, headers, data)
|
53
49
|
|
54
50
|
if response.code.match?(/\A2\d{2}/)
|
55
51
|
handle_rate_limited_response(response) if has_rate_limited_header?(response)
|
@@ -111,6 +107,14 @@ module Sentry
|
|
111
107
|
connection
|
112
108
|
end
|
113
109
|
|
110
|
+
def do_request(endpoint, headers, body)
|
111
|
+
conn.start do |http|
|
112
|
+
request = ::Net::HTTP::Post.new(endpoint, headers)
|
113
|
+
request.body = body
|
114
|
+
http.request(request)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
114
118
|
private
|
115
119
|
|
116
120
|
def has_rate_limited_header?(headers)
|
data/lib/sentry/transport.rb
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Utils
|
5
|
+
class SampleRand
|
6
|
+
PRECISION = 1_000_000.0
|
7
|
+
FORMAT_PRECISION = 6
|
8
|
+
|
9
|
+
attr_reader :trace_id
|
10
|
+
|
11
|
+
def self.valid?(value)
|
12
|
+
return false unless value
|
13
|
+
value >= 0.0 && value < 1.0
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.format(value)
|
17
|
+
return unless value
|
18
|
+
|
19
|
+
truncated = (value * PRECISION).floor / PRECISION
|
20
|
+
"%.#{FORMAT_PRECISION}f" % truncated
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(trace_id: nil)
|
24
|
+
@trace_id = trace_id
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_from_trace_id
|
28
|
+
(random_from_trace_id * PRECISION).floor / PRECISION
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_from_sampling_decision(sampled, sample_rate)
|
32
|
+
if invalid_sample_rate?(sample_rate)
|
33
|
+
fallback_generation
|
34
|
+
else
|
35
|
+
generate_based_on_sampling(sampled, sample_rate)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_from_value(sample_rand_value)
|
40
|
+
parsed_value = parse_value(sample_rand_value)
|
41
|
+
|
42
|
+
if self.class.valid?(parsed_value)
|
43
|
+
parsed_value
|
44
|
+
else
|
45
|
+
fallback_generation
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def random_from_trace_id
|
52
|
+
if @trace_id
|
53
|
+
Random.new(@trace_id[0, 16].to_i(16))
|
54
|
+
else
|
55
|
+
Random.new
|
56
|
+
end.rand(1.0)
|
57
|
+
end
|
58
|
+
|
59
|
+
def invalid_sample_rate?(sample_rate)
|
60
|
+
sample_rate.nil? || sample_rate <= 0.0 || sample_rate > 1.0
|
61
|
+
end
|
62
|
+
|
63
|
+
def fallback_generation
|
64
|
+
if @trace_id
|
65
|
+
(random_from_trace_id * PRECISION).floor / PRECISION
|
66
|
+
else
|
67
|
+
format_random(Random.rand(1.0))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_based_on_sampling(sampled, sample_rate)
|
72
|
+
random = random_from_trace_id
|
73
|
+
|
74
|
+
result = if sampled
|
75
|
+
random * sample_rate
|
76
|
+
elsif sample_rate == 1.0
|
77
|
+
random
|
78
|
+
else
|
79
|
+
sample_rate + random * (1.0 - sample_rate)
|
80
|
+
end
|
81
|
+
|
82
|
+
format_random(result)
|
83
|
+
end
|
84
|
+
|
85
|
+
def format_random(value)
|
86
|
+
truncated = (value * PRECISION).floor / PRECISION
|
87
|
+
("%.#{FORMAT_PRECISION}f" % truncated).to_f
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_value(sample_rand_value)
|
91
|
+
Float(sample_rand_value)
|
92
|
+
rescue ArgumentError
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -10,8 +10,10 @@ require "sentry/core_ext/object/deep_dup"
|
|
10
10
|
require "sentry/utils/argument_checking_helper"
|
11
11
|
require "sentry/utils/encoding_helper"
|
12
12
|
require "sentry/utils/logging_helper"
|
13
|
+
require "sentry/utils/sample_rand"
|
13
14
|
require "sentry/configuration"
|
14
15
|
require "sentry/structured_logger"
|
16
|
+
require "sentry/debug_structured_logger"
|
15
17
|
require "sentry/event"
|
16
18
|
require "sentry/error_event"
|
17
19
|
require "sentry/transaction_event"
|
@@ -638,13 +640,15 @@ module Sentry
|
|
638
640
|
@logger ||=
|
639
641
|
if configuration.enable_logs
|
640
642
|
# Initialize the public-facing Structured Logger if logs are enabled
|
641
|
-
#
|
643
|
+
# Use configured structured logger class or default to StructuredLogger
|
642
644
|
# @see https://develop.sentry.dev/sdk/telemetry/logs/
|
643
|
-
|
645
|
+
configuration.structured_logging.logger_class.new(configuration)
|
644
646
|
else
|
645
647
|
warn <<~STR
|
646
648
|
[sentry] `Sentry.logger` will no longer be used as internal SDK logger when `enable_logs` feature is turned on.
|
647
649
|
Use Sentry.configuration.sdk_logger for SDK-specific logging needs."
|
650
|
+
|
651
|
+
Caller: #{caller.first}
|
648
652
|
STR
|
649
653
|
|
650
654
|
configuration.sdk_logger
|
@@ -690,3 +694,4 @@ require "sentry/puma"
|
|
690
694
|
require "sentry/graphql"
|
691
695
|
require "sentry/faraday"
|
692
696
|
require "sentry/excon"
|
697
|
+
require "sentry/std_lib_logger"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.27.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- lib/sentry/cron/monitor_check_ins.rb
|
81
81
|
- lib/sentry/cron/monitor_config.rb
|
82
82
|
- lib/sentry/cron/monitor_schedule.rb
|
83
|
+
- lib/sentry/debug_structured_logger.rb
|
83
84
|
- lib/sentry/dsn.rb
|
84
85
|
- lib/sentry/envelope.rb
|
85
86
|
- lib/sentry/envelope/item.rb
|
@@ -129,6 +130,7 @@ files:
|
|
129
130
|
- lib/sentry/session.rb
|
130
131
|
- lib/sentry/session_flusher.rb
|
131
132
|
- lib/sentry/span.rb
|
133
|
+
- lib/sentry/std_lib_logger.rb
|
132
134
|
- lib/sentry/structured_logger.rb
|
133
135
|
- lib/sentry/test_helper.rb
|
134
136
|
- lib/sentry/threaded_periodic_worker.rb
|
@@ -136,6 +138,7 @@ files:
|
|
136
138
|
- lib/sentry/transaction_event.rb
|
137
139
|
- lib/sentry/transport.rb
|
138
140
|
- lib/sentry/transport/configuration.rb
|
141
|
+
- lib/sentry/transport/debug_transport.rb
|
139
142
|
- lib/sentry/transport/dummy_transport.rb
|
140
143
|
- lib/sentry/transport/http_transport.rb
|
141
144
|
- lib/sentry/transport/spotlight_transport.rb
|
@@ -148,21 +151,22 @@ files:
|
|
148
151
|
- lib/sentry/utils/logging_helper.rb
|
149
152
|
- lib/sentry/utils/real_ip.rb
|
150
153
|
- lib/sentry/utils/request_id.rb
|
154
|
+
- lib/sentry/utils/sample_rand.rb
|
151
155
|
- lib/sentry/utils/uuid.rb
|
152
156
|
- lib/sentry/vernier/output.rb
|
153
157
|
- lib/sentry/vernier/profiler.rb
|
154
158
|
- lib/sentry/version.rb
|
155
159
|
- sentry-ruby-core.gemspec
|
156
160
|
- sentry-ruby.gemspec
|
157
|
-
homepage: https://github.com/getsentry/sentry-ruby/tree/5.
|
161
|
+
homepage: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
|
158
162
|
licenses:
|
159
163
|
- MIT
|
160
164
|
metadata:
|
161
|
-
homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.
|
162
|
-
source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.
|
163
|
-
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.
|
165
|
+
homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
|
166
|
+
source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
|
167
|
+
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.27.0/CHANGELOG.md
|
164
168
|
bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
|
165
|
-
documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.
|
169
|
+
documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.27.0
|
166
170
|
rdoc_options: []
|
167
171
|
require_paths:
|
168
172
|
- lib
|
@@ -177,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
181
|
- !ruby/object:Gem::Version
|
178
182
|
version: '0'
|
179
183
|
requirements: []
|
180
|
-
rubygems_version: 3.6.
|
184
|
+
rubygems_version: 3.6.9
|
181
185
|
specification_version: 4
|
182
186
|
summary: A gem that provides a client interface for the Sentry error logger
|
183
187
|
test_files: []
|