sentry-ruby 5.18.2 → 5.28.1
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/.rspec +3 -1
- data/Gemfile +9 -3
- data/README.md +12 -7
- data/Rakefile +9 -11
- data/bin/console +1 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +2 -3
- data/lib/sentry/backpressure_monitor.rb +1 -1
- data/lib/sentry/backtrace.rb +7 -8
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/breadcrumb.rb +5 -4
- data/lib/sentry/check_in_event.rb +5 -4
- data/lib/sentry/client.rb +121 -19
- data/lib/sentry/configuration.rb +207 -38
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/monitor_check_ins.rb +6 -4
- data/lib/sentry/cron/monitor_config.rb +1 -1
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +35 -3
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -85
- data/lib/sentry/event.rb +13 -8
- data/lib/sentry/excon/middleware.rb +77 -0
- data/lib/sentry/excon.rb +10 -0
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +1 -1
- data/lib/sentry/hub.rb +57 -4
- data/lib/sentry/interfaces/mechanism.rb +1 -1
- data/lib/sentry/interfaces/request.rb +6 -6
- data/lib/sentry/interfaces/single_exception.rb +3 -3
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
- data/lib/sentry/linecache.rb +3 -3
- data/lib/sentry/log_event.rb +219 -0
- data/lib/sentry/log_event_buffer.rb +75 -0
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +13 -13
- data/lib/sentry/metrics/configuration.rb +12 -2
- data/lib/sentry/metrics/set_metric.rb +2 -2
- data/lib/sentry/metrics/timing.rb +8 -0
- data/lib/sentry/metrics.rb +27 -15
- data/lib/sentry/net/http.rb +17 -38
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +27 -57
- data/lib/sentry/propagation_context.rb +60 -23
- data/lib/sentry/rack/capture_exceptions.rb +2 -2
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +2 -2
- data/lib/sentry/release_detector.rb +4 -4
- data/lib/sentry/rspec.rb +91 -0
- data/lib/sentry/scope.rb +27 -2
- data/lib/sentry/session_flusher.rb +10 -6
- data/lib/sentry/span.rb +17 -4
- data/lib/sentry/std_lib_logger.rb +55 -0
- data/lib/sentry/structured_logger.rb +138 -0
- data/lib/sentry/test_helper.rb +45 -1
- data/lib/sentry/threaded_periodic_worker.rb +3 -3
- data/lib/sentry/transaction.rb +41 -13
- data/lib/sentry/transaction_event.rb +5 -3
- 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 +21 -18
- data/lib/sentry/transport.rb +26 -11
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +74 -0
- data/lib/sentry/utils/logging_helper.rb +10 -3
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/utils/uuid.rb +13 -0
- data/lib/sentry/vernier/output.rb +89 -0
- data/lib/sentry/vernier/profiler.rb +132 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +106 -12
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +4 -2
- metadata +28 -12
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
ORIGIN = "auto.log.ruby.std_logger"
|
|
16
|
+
|
|
17
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
18
|
+
result = super
|
|
19
|
+
|
|
20
|
+
return unless Sentry.initialized? && Sentry.get_current_hub
|
|
21
|
+
|
|
22
|
+
# Only process logs that meet or exceed the logger's level
|
|
23
|
+
return result if severity < level
|
|
24
|
+
|
|
25
|
+
# exclude sentry SDK logs -- to prevent recursive log action,
|
|
26
|
+
# do not process internal logs again
|
|
27
|
+
if message.nil? && progname != Sentry::Logger::PROGNAME
|
|
28
|
+
|
|
29
|
+
# handle different nature of Ruby Logger class:
|
|
30
|
+
# inspo from Sentry::Breadcrumb::SentryLogger
|
|
31
|
+
if block_given?
|
|
32
|
+
message = yield
|
|
33
|
+
else
|
|
34
|
+
message = progname
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
message = message.to_s.strip
|
|
38
|
+
|
|
39
|
+
if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
|
|
40
|
+
Sentry.logger.send(method, message, origin: ORIGIN)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Sentry.register_patch(:logger) do |config|
|
|
50
|
+
if config.enable_logs
|
|
51
|
+
::Logger.prepend(Sentry::StdLibLogger)
|
|
52
|
+
else
|
|
53
|
+
config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
# The StructuredLogger class implements Sentry's SDK telemetry logs protocol.
|
|
5
|
+
# It provides methods for logging messages at different severity levels and
|
|
6
|
+
# sending them to Sentry with structured data.
|
|
7
|
+
#
|
|
8
|
+
# This class follows the Sentry Logs Protocol as defined in:
|
|
9
|
+
# https://develop.sentry.dev/sdk/telemetry/logs/
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# Sentry.logger.info("User logged in", user_id: 123)
|
|
13
|
+
#
|
|
14
|
+
# @example With structured data
|
|
15
|
+
# Sentry.logger.warn("API request failed",
|
|
16
|
+
# status_code: 404,
|
|
17
|
+
# endpoint: "/api/users",
|
|
18
|
+
# request_id: "abc-123"
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @example With a message template
|
|
22
|
+
# # Using positional parameters
|
|
23
|
+
# Sentry.logger.info("User %s logged in", ["Jane Doe"])
|
|
24
|
+
#
|
|
25
|
+
# # Using hash parameters
|
|
26
|
+
# Sentry.logger.info("User %{name} logged in", name: "Jane Doe")
|
|
27
|
+
#
|
|
28
|
+
# # Using hash parameters and extra attributes
|
|
29
|
+
# Sentry.logger.info("User %{name} logged in", name: "Jane Doe", user_id: 312)
|
|
30
|
+
#
|
|
31
|
+
# @see https://develop.sentry.dev/sdk/telemetry/logs/ Sentry SDK Telemetry Logs Protocol
|
|
32
|
+
class StructuredLogger
|
|
33
|
+
# Severity number mapping for log levels according to the Sentry Logs Protocol
|
|
34
|
+
# @see https://develop.sentry.dev/sdk/telemetry/logs/#log-severity-number
|
|
35
|
+
LEVELS = {
|
|
36
|
+
trace: 1,
|
|
37
|
+
debug: 5,
|
|
38
|
+
info: 9,
|
|
39
|
+
warn: 13,
|
|
40
|
+
error: 17,
|
|
41
|
+
fatal: 21
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
# @return [Configuration] The Sentry configuration
|
|
45
|
+
# @!visibility private
|
|
46
|
+
attr_reader :config
|
|
47
|
+
|
|
48
|
+
# Initializes a new StructuredLogger instance
|
|
49
|
+
#
|
|
50
|
+
# @param config [Configuration] The Sentry configuration
|
|
51
|
+
def initialize(config)
|
|
52
|
+
@config = config
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Logs a message at TRACE level
|
|
56
|
+
#
|
|
57
|
+
# @param message [String] The log message
|
|
58
|
+
# @param parameters [Array] Array of values to replace template parameters in the message
|
|
59
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
60
|
+
#
|
|
61
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
62
|
+
def trace(message, parameters = [], **attributes)
|
|
63
|
+
log(__method__, message, parameters: parameters, **attributes)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Logs a message at DEBUG level
|
|
67
|
+
#
|
|
68
|
+
# @param message [String] The log message
|
|
69
|
+
# @param parameters [Array] Array of values to replace template parameters in the message
|
|
70
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
71
|
+
#
|
|
72
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
73
|
+
def debug(message, parameters = [], **attributes)
|
|
74
|
+
log(__method__, message, parameters: parameters, **attributes)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Logs a message at INFO level
|
|
78
|
+
#
|
|
79
|
+
# @param message [String] The log message
|
|
80
|
+
# @param parameters [Array] Array of values to replace template parameters in the message
|
|
81
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
82
|
+
#
|
|
83
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
84
|
+
def info(message, parameters = [], **attributes)
|
|
85
|
+
log(__method__, message, parameters: parameters, **attributes)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Logs a message at WARN level
|
|
89
|
+
#
|
|
90
|
+
# @param message [String] The log message
|
|
91
|
+
# @param parameters [Array] Array of values to replace template parameters in the message
|
|
92
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
93
|
+
#
|
|
94
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
95
|
+
def warn(message, parameters = [], **attributes)
|
|
96
|
+
log(__method__, message, parameters: parameters, **attributes)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Logs a message at ERROR level
|
|
100
|
+
#
|
|
101
|
+
# @param message [String] The log message
|
|
102
|
+
# @param parameters [Array] Array of values to replace template parameters in the message
|
|
103
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
104
|
+
#
|
|
105
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
106
|
+
def error(message, parameters = [], **attributes)
|
|
107
|
+
log(__method__, message, parameters: parameters, **attributes)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Logs a message at FATAL level
|
|
111
|
+
#
|
|
112
|
+
# @param message [String] The log message
|
|
113
|
+
# @param parameters [Array] Array of values to replace template parameters in the message
|
|
114
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
115
|
+
#
|
|
116
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
117
|
+
def fatal(message, parameters = [], **attributes)
|
|
118
|
+
log(__method__, message, parameters: parameters, **attributes)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Logs a message at the specified level
|
|
122
|
+
#
|
|
123
|
+
# @param level [Symbol] The log level (:trace, :debug, :info, :warn, :error, :fatal)
|
|
124
|
+
# @param message [String] The log message
|
|
125
|
+
# @param parameters [Array, Hash] Array or Hash of values to replace template parameters in the message
|
|
126
|
+
# @param attributes [Hash] Additional attributes to include with the log
|
|
127
|
+
#
|
|
128
|
+
# @return [LogEvent, nil] The created log event or nil if logging is disabled
|
|
129
|
+
def log(level, message, parameters:, **attributes)
|
|
130
|
+
case parameters
|
|
131
|
+
when Array then
|
|
132
|
+
Sentry.capture_log(message, level: level, severity: LEVELS[level], parameters: parameters, **attributes)
|
|
133
|
+
else
|
|
134
|
+
Sentry.capture_log(message, level: level, severity: LEVELS[level], **parameters)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/sentry/test_helper.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sentry
|
|
2
4
|
module TestHelper
|
|
3
|
-
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
|
|
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"
|
|
4
11
|
|
|
5
12
|
# Alters the existing SDK configuration with test-suitable options. Mainly:
|
|
6
13
|
# - Sets a dummy DSN instead of `nil` or an actual DSN.
|
|
@@ -44,6 +51,8 @@ module Sentry
|
|
|
44
51
|
def teardown_sentry_test
|
|
45
52
|
return unless Sentry.initialized?
|
|
46
53
|
|
|
54
|
+
clear_sentry_events
|
|
55
|
+
|
|
47
56
|
# pop testing layer created by `setup_sentry_test`
|
|
48
57
|
# but keep the base layer to avoid nil-pointer errors
|
|
49
58
|
# TODO: find a way to notify users if they somehow popped the test layer before calling this method
|
|
@@ -53,6 +62,21 @@ module Sentry
|
|
|
53
62
|
Sentry::Scope.global_event_processors.clear
|
|
54
63
|
end
|
|
55
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
|
+
|
|
56
80
|
# @return [Transport]
|
|
57
81
|
def sentry_transport
|
|
58
82
|
Sentry.get_current_client.transport
|
|
@@ -70,6 +94,13 @@ module Sentry
|
|
|
70
94
|
sentry_transport.envelopes
|
|
71
95
|
end
|
|
72
96
|
|
|
97
|
+
def sentry_logs
|
|
98
|
+
sentry_envelopes
|
|
99
|
+
.flat_map(&:items)
|
|
100
|
+
.select { |item| item.headers[:type] == "log" }
|
|
101
|
+
.flat_map { |item| item.payload[:items] }
|
|
102
|
+
end
|
|
103
|
+
|
|
73
104
|
# Returns the last captured event object.
|
|
74
105
|
# @return [Event, nil]
|
|
75
106
|
def last_sentry_event
|
|
@@ -81,5 +112,18 @@ module Sentry
|
|
|
81
112
|
def extract_sentry_exceptions(event)
|
|
82
113
|
event&.exception&.values || []
|
|
83
114
|
end
|
|
115
|
+
|
|
116
|
+
def reset_sentry_globals!
|
|
117
|
+
Sentry::MUTEX.synchronize do
|
|
118
|
+
# Don't check initialized? because sometimes we stub it in tests
|
|
119
|
+
if Sentry.instance_variable_defined?(:@main_hub)
|
|
120
|
+
Sentry::GLOBALS.each do |var|
|
|
121
|
+
Sentry.instance_variable_set(:"@#{var}", nil)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
Thread.current.thread_variable_set(Sentry::THREAD_LOCAL, nil)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
84
128
|
end
|
|
85
129
|
end
|
|
@@ -4,11 +4,11 @@ module Sentry
|
|
|
4
4
|
class ThreadedPeriodicWorker
|
|
5
5
|
include LoggingHelper
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
7
|
+
def initialize(sdk_logger, interval)
|
|
8
8
|
@thread = nil
|
|
9
9
|
@exited = false
|
|
10
|
-
@interval =
|
|
11
|
-
@
|
|
10
|
+
@interval = interval
|
|
11
|
+
@sdk_logger = sdk_logger
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def ensure_thread
|
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
|
|
@@ -9,7 +10,7 @@ module Sentry
|
|
|
9
10
|
# @deprecated Use Sentry::PropagationContext::SENTRY_TRACE_REGEXP instead.
|
|
10
11
|
SENTRY_TRACE_REGEXP = PropagationContext::SENTRY_TRACE_REGEXP
|
|
11
12
|
|
|
12
|
-
UNLABELD_NAME = "<unlabeled transaction>"
|
|
13
|
+
UNLABELD_NAME = "<unlabeled transaction>"
|
|
13
14
|
MESSAGE_PREFIX = "[Tracing]"
|
|
14
15
|
|
|
15
16
|
# https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
|
|
@@ -45,9 +46,6 @@ module Sentry
|
|
|
45
46
|
# @deprecated Use Sentry.configuration instead.
|
|
46
47
|
attr_reader :configuration
|
|
47
48
|
|
|
48
|
-
# @deprecated Use Sentry.logger instead.
|
|
49
|
-
attr_reader :logger
|
|
50
|
-
|
|
51
49
|
# The effective sample rate at which this transaction was sampled.
|
|
52
50
|
# @return [Float, nil]
|
|
53
51
|
attr_reader :effective_sample_rate
|
|
@@ -60,12 +58,17 @@ module Sentry
|
|
|
60
58
|
# @return [Profiler]
|
|
61
59
|
attr_reader :profiler
|
|
62
60
|
|
|
61
|
+
# Sample rand value generated from trace_id
|
|
62
|
+
# @return [String]
|
|
63
|
+
attr_reader :sample_rand
|
|
64
|
+
|
|
63
65
|
def initialize(
|
|
64
66
|
hub:,
|
|
65
67
|
name: nil,
|
|
66
68
|
source: :custom,
|
|
67
69
|
parent_sampled: nil,
|
|
68
70
|
baggage: nil,
|
|
71
|
+
sample_rand: nil,
|
|
69
72
|
**options
|
|
70
73
|
)
|
|
71
74
|
super(transaction: self, **options)
|
|
@@ -78,15 +81,25 @@ module Sentry
|
|
|
78
81
|
@tracing_enabled = hub.configuration.tracing_enabled?
|
|
79
82
|
@traces_sampler = hub.configuration.traces_sampler
|
|
80
83
|
@traces_sample_rate = hub.configuration.traces_sample_rate
|
|
81
|
-
@
|
|
84
|
+
@sdk_logger = hub.configuration.sdk_logger
|
|
82
85
|
@release = hub.configuration.release
|
|
83
86
|
@environment = hub.configuration.environment
|
|
84
87
|
@dsn = hub.configuration.dsn
|
|
85
88
|
@effective_sample_rate = nil
|
|
86
89
|
@contexts = {}
|
|
87
90
|
@measurements = {}
|
|
88
|
-
@
|
|
91
|
+
@sample_rand = sample_rand
|
|
92
|
+
|
|
93
|
+
unless @hub.profiler_running?
|
|
94
|
+
@profiler = @configuration.profiler_class.new(@configuration)
|
|
95
|
+
end
|
|
96
|
+
|
|
89
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
|
|
90
103
|
end
|
|
91
104
|
|
|
92
105
|
# @deprecated use Sentry.continue_trace instead.
|
|
@@ -122,12 +135,15 @@ module Sentry
|
|
|
122
135
|
|
|
123
136
|
baggage.freeze!
|
|
124
137
|
|
|
138
|
+
sample_rand = extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
|
|
139
|
+
|
|
125
140
|
new(
|
|
126
141
|
trace_id: trace_id,
|
|
127
142
|
parent_span_id: parent_span_id,
|
|
128
143
|
parent_sampled: parent_sampled,
|
|
129
144
|
hub: hub,
|
|
130
145
|
baggage: baggage,
|
|
146
|
+
sample_rand: sample_rand,
|
|
131
147
|
**options
|
|
132
148
|
)
|
|
133
149
|
end
|
|
@@ -138,6 +154,11 @@ module Sentry
|
|
|
138
154
|
PropagationContext.extract_sentry_trace(sentry_trace)
|
|
139
155
|
end
|
|
140
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
|
+
|
|
141
162
|
# @return [Hash]
|
|
142
163
|
def to_hash
|
|
143
164
|
hash = super
|
|
@@ -152,6 +173,13 @@ module Sentry
|
|
|
152
173
|
hash
|
|
153
174
|
end
|
|
154
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
|
+
|
|
155
183
|
# @return [Transaction]
|
|
156
184
|
def deep_dup
|
|
157
185
|
copy = super
|
|
@@ -224,7 +252,7 @@ module Sentry
|
|
|
224
252
|
@effective_sample_rate /= 2**factor
|
|
225
253
|
end
|
|
226
254
|
|
|
227
|
-
@sampled =
|
|
255
|
+
@sampled = @sample_rand < @effective_sample_rate
|
|
228
256
|
end
|
|
229
257
|
|
|
230
258
|
if @sampled
|
|
@@ -257,7 +285,7 @@ module Sentry
|
|
|
257
285
|
@name = UNLABELD_NAME
|
|
258
286
|
end
|
|
259
287
|
|
|
260
|
-
@
|
|
288
|
+
@hub.stop_profiler!(self)
|
|
261
289
|
|
|
262
290
|
if @sampled
|
|
263
291
|
event = hub.current_client.event_from_transaction(self)
|
|
@@ -265,8 +293,8 @@ module Sentry
|
|
|
265
293
|
else
|
|
266
294
|
is_backpressure = Sentry.backpressure_monitor&.downsample_factor&.positive?
|
|
267
295
|
reason = is_backpressure ? :backpressure : :sample_rate
|
|
268
|
-
hub.current_client.transport.record_lost_event(reason,
|
|
269
|
-
hub.current_client.transport.record_lost_event(reason,
|
|
296
|
+
hub.current_client.transport.record_lost_event(reason, "transaction")
|
|
297
|
+
hub.current_client.transport.record_lost_event(reason, "span")
|
|
270
298
|
end
|
|
271
299
|
end
|
|
272
300
|
|
|
@@ -299,6 +327,8 @@ module Sentry
|
|
|
299
327
|
# Start the profiler.
|
|
300
328
|
# @return [void]
|
|
301
329
|
def start_profiler!
|
|
330
|
+
return unless profiler
|
|
331
|
+
|
|
302
332
|
profiler.set_initial_sample_decision(sampled)
|
|
303
333
|
profiler.start
|
|
304
334
|
end
|
|
@@ -328,6 +358,7 @@ module Sentry
|
|
|
328
358
|
items = {
|
|
329
359
|
"trace_id" => trace_id,
|
|
330
360
|
"sample_rate" => effective_sample_rate&.to_s,
|
|
361
|
+
"sample_rand" => Utils::SampleRand.format(@sample_rand),
|
|
331
362
|
"sampled" => sampled&.to_s,
|
|
332
363
|
"environment" => @environment,
|
|
333
364
|
"release" => @release,
|
|
@@ -336,9 +367,6 @@ module Sentry
|
|
|
336
367
|
|
|
337
368
|
items["transaction"] = name unless source_low_quality?
|
|
338
369
|
|
|
339
|
-
user = @hub.current_scope&.user
|
|
340
|
-
items["user_segment"] = user["segment"] if user && user["segment"]
|
|
341
|
-
|
|
342
370
|
items.compact!
|
|
343
371
|
@baggage = Baggage.new(items, mutable: false)
|
|
344
372
|
end
|
|
@@ -59,8 +59,11 @@ module Sentry
|
|
|
59
59
|
|
|
60
60
|
private
|
|
61
61
|
|
|
62
|
+
EMPTY_PROFILE = {}.freeze
|
|
63
|
+
|
|
62
64
|
def populate_profile(transaction)
|
|
63
|
-
profile_hash = transaction.profiler
|
|
65
|
+
profile_hash = transaction.profiler&.to_hash || EMPTY_PROFILE
|
|
66
|
+
|
|
64
67
|
return if profile_hash.empty?
|
|
65
68
|
|
|
66
69
|
profile_hash.merge!(
|
|
@@ -74,8 +77,7 @@ module Sentry
|
|
|
74
77
|
id: event_id,
|
|
75
78
|
name: transaction.name,
|
|
76
79
|
trace_id: transaction.trace_id,
|
|
77
|
-
|
|
78
|
-
active_thead_id: '0'
|
|
80
|
+
active_thread_id: transaction.profiler.active_thread_id.to_s
|
|
79
81
|
}
|
|
80
82
|
)
|
|
81
83
|
|
|
@@ -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
|
|
@@ -7,7 +7,7 @@ module Sentry
|
|
|
7
7
|
class HTTPTransport < Transport
|
|
8
8
|
GZIP_ENCODING = "gzip"
|
|
9
9
|
GZIP_THRESHOLD = 1024 * 30
|
|
10
|
-
CONTENT_TYPE =
|
|
10
|
+
CONTENT_TYPE = "application/x-sentry-envelope"
|
|
11
11
|
|
|
12
12
|
DEFAULT_DELAY = 60
|
|
13
13
|
RETRY_AFTER_HEADER = "retry-after"
|
|
@@ -23,7 +23,6 @@ module Sentry
|
|
|
23
23
|
Zlib::BufError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
|
24
24
|
].freeze
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
def initialize(*args)
|
|
28
27
|
super
|
|
29
28
|
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}") if @dsn
|
|
@@ -38,19 +37,15 @@ module Sentry
|
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
headers = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
"Content-Type" => CONTENT_TYPE,
|
|
41
|
+
"Content-Encoding" => encoding,
|
|
42
|
+
"User-Agent" => USER_AGENT
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
auth_header = generate_auth_header
|
|
47
|
-
headers[
|
|
46
|
+
headers["X-Sentry-Auth"] = auth_header if auth_header
|
|
48
47
|
|
|
49
|
-
response =
|
|
50
|
-
request = ::Net::HTTP::Post.new(endpoint, headers)
|
|
51
|
-
request.body = data
|
|
52
|
-
http.request(request)
|
|
53
|
-
end
|
|
48
|
+
response = do_request(endpoint, headers, data)
|
|
54
49
|
|
|
55
50
|
if response.code.match?(/\A2\d{2}/)
|
|
56
51
|
handle_rate_limited_response(response) if has_rate_limited_header?(response)
|
|
@@ -60,7 +55,7 @@ module Sentry
|
|
|
60
55
|
else
|
|
61
56
|
error_info = "the server responded with status #{response.code}"
|
|
62
57
|
error_info += "\nbody: #{response.body}"
|
|
63
|
-
error_info += " Error in headers is: #{response['x-sentry-error']}" if response[
|
|
58
|
+
error_info += " Error in headers is: #{response['x-sentry-error']}" if response["x-sentry-error"]
|
|
64
59
|
|
|
65
60
|
raise Sentry::ExternalError, error_info
|
|
66
61
|
end
|
|
@@ -78,13 +73,13 @@ module Sentry
|
|
|
78
73
|
|
|
79
74
|
now = Sentry.utc_now.to_i
|
|
80
75
|
fields = {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
"sentry_version" => PROTOCOL_VERSION,
|
|
77
|
+
"sentry_client" => USER_AGENT,
|
|
78
|
+
"sentry_timestamp" => now,
|
|
79
|
+
"sentry_key" => @dsn.public_key
|
|
85
80
|
}
|
|
86
|
-
fields[
|
|
87
|
-
|
|
81
|
+
fields["sentry_secret"] = @dsn.secret_key if @dsn.secret_key
|
|
82
|
+
"Sentry " + fields.map { |key, value| "#{key}=#{value}" }.join(", ")
|
|
88
83
|
end
|
|
89
84
|
|
|
90
85
|
def conn
|
|
@@ -112,6 +107,14 @@ module Sentry
|
|
|
112
107
|
connection
|
|
113
108
|
end
|
|
114
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
|
+
|
|
115
118
|
private
|
|
116
119
|
|
|
117
120
|
def has_rate_limited_header?(headers)
|