sentry-ruby 5.26.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/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/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 +6 -2
- metadata +10 -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/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
|
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
|
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
|
@@ -137,6 +138,7 @@ files:
|
|
137
138
|
- lib/sentry/transaction_event.rb
|
138
139
|
- lib/sentry/transport.rb
|
139
140
|
- lib/sentry/transport/configuration.rb
|
141
|
+
- lib/sentry/transport/debug_transport.rb
|
140
142
|
- lib/sentry/transport/dummy_transport.rb
|
141
143
|
- lib/sentry/transport/http_transport.rb
|
142
144
|
- lib/sentry/transport/spotlight_transport.rb
|
@@ -149,21 +151,22 @@ files:
|
|
149
151
|
- lib/sentry/utils/logging_helper.rb
|
150
152
|
- lib/sentry/utils/real_ip.rb
|
151
153
|
- lib/sentry/utils/request_id.rb
|
154
|
+
- lib/sentry/utils/sample_rand.rb
|
152
155
|
- lib/sentry/utils/uuid.rb
|
153
156
|
- lib/sentry/vernier/output.rb
|
154
157
|
- lib/sentry/vernier/profiler.rb
|
155
158
|
- lib/sentry/version.rb
|
156
159
|
- sentry-ruby-core.gemspec
|
157
160
|
- sentry-ruby.gemspec
|
158
|
-
homepage: https://github.com/getsentry/sentry-ruby/tree/5.
|
161
|
+
homepage: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
|
159
162
|
licenses:
|
160
163
|
- MIT
|
161
164
|
metadata:
|
162
|
-
homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.
|
163
|
-
source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.
|
164
|
-
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
|
165
168
|
bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
|
166
|
-
documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.
|
169
|
+
documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.27.0
|
167
170
|
rdoc_options: []
|
168
171
|
require_paths:
|
169
172
|
- lib
|
@@ -178,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
181
|
- !ruby/object:Gem::Version
|
179
182
|
version: '0'
|
180
183
|
requirements: []
|
181
|
-
rubygems_version: 3.6.
|
184
|
+
rubygems_version: 3.6.9
|
182
185
|
specification_version: 4
|
183
186
|
summary: A gem that provides a client interface for the Sentry error logger
|
184
187
|
test_files: []
|