sentry-ruby 5.23.0 → 5.24.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09ca0c97390e688d58572ffe3ae022523a1003a48ccea319857b302b3e1ebb95'
4
- data.tar.gz: 28f154fb38f21c50090dc8ae3c2bc7b335f288a298e97d934a24db1b8e7ac372
3
+ metadata.gz: 724630875bf8195cd594f3e761794886520b8eb95c13bf9a82642cdaf6f3ea94
4
+ data.tar.gz: c086418c80e60b60d1e099701fdb6b9ae1a9136bd464213dd9ff9cc17c582b75
5
5
  SHA512:
6
- metadata.gz: 5f170fe27327a1753f4a6f2d62b5a694f30c91f1f86c4e2caa837d70467298ea2af261475a2da1045e3513257f3a9be4bb66a0c2e0dba271e43a1478ecd42c0b
7
- data.tar.gz: 32a7f13e8eee4f7c8958dfd1a083aecf7ee306fec695703c5dbeff810e4ae5ebd84a1435e7edaef8a61d94eaf64910442422d2142007d77c5be31080ad32f244
6
+ metadata.gz: f8db6e3259785a16378f1a2f43f4dbb6e82be7f7eed84ba8f6a7ab8f1dc0f8ae4989a1d9fad5022361d756e0d8fd80d3cf16b6444933f10380e112e54afeaec1
7
+ data.tar.gz: 86ad04ab139eb703950d206a1a2a029f42da4fdf43896a342fa961ec8f7cd607de27270b49ec4ba7a635e2d34018387990e1a56d26287af39bf2708aee004a23
@@ -9,8 +9,7 @@ module Sentry
9
9
  include LoggingHelper
10
10
 
11
11
  attr_reader :max_queue, :number_of_threads
12
- # @deprecated Use Sentry.logger to retrieve the current logger instead.
13
- attr_reader :logger
12
+
14
13
  attr_accessor :shutdown_timeout
15
14
 
16
15
  DEFAULT_MAX_QUEUE = 30
@@ -19,7 +18,7 @@ module Sentry
19
18
  @shutdown_timeout = 1
20
19
  @number_of_threads = configuration.background_worker_threads
21
20
  @max_queue = configuration.background_worker_max_queue
22
- @logger = configuration.logger
21
+ @sdk_logger = configuration.sdk_logger
23
22
  @debug = configuration.debug
24
23
  @shutdown_callback = nil
25
24
 
@@ -6,7 +6,7 @@ module Sentry
6
6
  MAX_DOWNSAMPLE_FACTOR = 10
7
7
 
8
8
  def initialize(configuration, client, interval: DEFAULT_INTERVAL)
9
- super(configuration.logger, interval)
9
+ super(configuration.sdk_logger, interval)
10
10
  @client = client
11
11
 
12
12
  @healthy = true
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Sentry
4
4
  class Breadcrumb
5
+ MAX_NESTING = 10
5
6
  DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
6
7
 
7
8
  # @return [String, nil]
@@ -47,7 +48,7 @@ module Sentry
47
48
  # @param message [String]
48
49
  # @return [void]
49
50
  def message=(message)
50
- @message = message ? message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES) : ""
51
+ @message = message && Utils::EncodingHelper.valid_utf_8?(message) ? message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES) : ""
51
52
  end
52
53
 
53
54
  # @param level [String]
@@ -60,16 +61,16 @@ module Sentry
60
61
 
61
62
  def serialized_data
62
63
  begin
63
- ::JSON.parse(::JSON.generate(@data))
64
+ ::JSON.parse(::JSON.generate(@data, max_nesting: MAX_NESTING))
64
65
  rescue Exception => e
65
- Sentry.logger.debug(LOGGER_PROGNAME) do
66
+ Sentry.sdk_logger.debug(LOGGER_PROGNAME) do
66
67
  <<~MSG
67
68
  can't serialize breadcrumb data because of error: #{e}
68
69
  data: #{@data}
69
70
  MSG
70
71
  end
71
72
 
72
- DATA_SERIALIZATION_ERROR_MESSAGE
73
+ { error: DATA_SERIALIZATION_ERROR_MESSAGE }
73
74
  end
74
75
  end
75
76
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "sentry/cron/monitor_config"
5
+ require "sentry/utils/uuid"
5
6
 
6
7
  module Sentry
7
8
  class CheckInEvent < Event
@@ -43,7 +44,7 @@ module Sentry
43
44
  self.status = status
44
45
  self.duration = duration
45
46
  self.monitor_config = monitor_config
46
- self.check_in_id = check_in_id || SecureRandom.uuid.delete("-")
47
+ self.check_in_id = check_in_id || Utils.uuid
47
48
  end
48
49
 
49
50
  # @return [Hash]
data/lib/sentry/client.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sentry/transport"
4
+ require "sentry/log_event"
5
+ require "sentry/log_event_buffer"
4
6
 
5
7
  module Sentry
6
8
  class Client
@@ -14,16 +16,16 @@ module Sentry
14
16
  # @return [SpotlightTransport, nil]
15
17
  attr_reader :spotlight_transport
16
18
 
19
+ # @!visibility private
20
+ attr_reader :log_event_buffer
21
+
17
22
  # @!macro configuration
18
23
  attr_reader :configuration
19
24
 
20
- # @deprecated Use Sentry.logger to retrieve the current logger instead.
21
- attr_reader :logger
22
-
23
25
  # @param configuration [Configuration]
24
26
  def initialize(configuration)
25
27
  @configuration = configuration
26
- @logger = configuration.logger
28
+ @sdk_logger = configuration.sdk_logger
27
29
 
28
30
  if transport_class = configuration.transport.transport_class
29
31
  @transport = transport_class.new(configuration)
@@ -38,6 +40,8 @@ module Sentry
38
40
  end
39
41
 
40
42
  @spotlight_transport = SpotlightTransport.new(configuration) if configuration.spotlight
43
+
44
+ @log_event_buffer = LogEventBuffer.new(configuration, self).start
41
45
  end
42
46
 
43
47
  # Applies the given scope's data to the event and sends it to Sentry.
@@ -88,6 +92,15 @@ module Sentry
88
92
  nil
89
93
  end
90
94
 
95
+ # Buffer a log event to be sent later with other logs in a single envelope
96
+ # @param event [LogEvent] the log event to be buffered
97
+ # @return [LogEvent]
98
+ def buffer_log_event(event, scope)
99
+ return unless event.is_a?(LogEvent)
100
+ @log_event_buffer.add_event(scope.apply_to_event(event))
101
+ event
102
+ end
103
+
91
104
  # Capture an envelope directly.
92
105
  # @param envelope [Envelope] the envelope to be captured.
93
106
  # @return [void]
@@ -100,6 +113,7 @@ module Sentry
100
113
  def flush
101
114
  transport.flush if configuration.sending_to_dsn_allowed?
102
115
  spotlight_transport.flush if spotlight_transport
116
+ @log_event_buffer.flush
103
117
  end
104
118
 
105
119
  # Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
@@ -167,6 +181,27 @@ module Sentry
167
181
  )
168
182
  end
169
183
 
184
+ # Initializes a LogEvent object with the given message and options
185
+ #
186
+ # @param message [String] the log message
187
+ # @param level [Symbol] the log level (:trace, :debug, :info, :warn, :error, :fatal)
188
+ # @param options [Hash] additional options
189
+ # @option options [Array] :parameters Array of values to replace template tokens in the message
190
+ #
191
+ # @return [LogEvent] the created log event
192
+ def event_from_log(message, level:, **options)
193
+ return unless configuration.sending_allowed?
194
+
195
+ attributes = options.reject { |k, _| k == :level || k == :severity }
196
+
197
+ LogEvent.new(
198
+ level: level,
199
+ body: message,
200
+ timestamp: Time.now.to_f,
201
+ attributes: attributes
202
+ )
203
+ end
204
+
170
205
  # Initializes an Event object with the given Transaction object.
171
206
  # @param transaction [Transaction] the transaction to be recorded.
172
207
  # @return [TransactionEvent]
@@ -12,6 +12,8 @@ require "sentry/cron/configuration"
12
12
  require "sentry/metrics/configuration"
13
13
  require "sentry/linecache"
14
14
  require "sentry/interfaces/stacktrace_builder"
15
+ require "sentry/logger"
16
+ require "sentry/log_event_buffer"
15
17
 
16
18
  module Sentry
17
19
  class Configuration
@@ -183,7 +185,19 @@ module Sentry
183
185
  # Logger used by Sentry. In Rails, this is the Rails logger, otherwise
184
186
  # Sentry provides its own Sentry::Logger.
185
187
  # @return [Logger]
186
- attr_accessor :logger
188
+ attr_accessor :sdk_logger
189
+
190
+ # @deprecated Use {#sdk_logger=} instead.
191
+ def logger=(logger)
192
+ warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."
193
+ self.sdk_logger = logger
194
+ end
195
+
196
+ # @deprecated Use {#sdk_logger} instead.
197
+ def logger
198
+ warn "[sentry] `config.logger` is deprecated. Please use `config.sdk_logger` instead."
199
+ self.sdk_logger
200
+ end
187
201
 
188
202
  # Project directory root for in_app detection. Could be Rails root, etc.
189
203
  # Set automatically for Rails.
@@ -262,6 +276,10 @@ module Sentry
262
276
  # @return [Proc]
263
277
  attr_accessor :traces_sampler
264
278
 
279
+ # Enable Structured Logging
280
+ # @return [Boolean]
281
+ attr_accessor :enable_logs
282
+
265
283
  # Easier way to use performance tracing
266
284
  # If set to true, will set traces_sample_rate to 1.0
267
285
  # @deprecated It will be removed in the next major release.
@@ -307,6 +325,10 @@ module Sentry
307
325
  # @return [Array<Symbol>]
308
326
  attr_accessor :enabled_patches
309
327
 
328
+ # Maximum number of log events to buffer before sending
329
+ # @return [Integer]
330
+ attr_accessor :max_log_events
331
+
310
332
  # these are not config options
311
333
  # @!visibility private
312
334
  attr_reader :errors, :gem_specs
@@ -419,7 +441,7 @@ module Sentry
419
441
  self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
420
442
  self.inspect_exception_causes_for_exclusion = true
421
443
  self.linecache = ::Sentry::LineCache.new
422
- self.logger = ::Sentry::Logger.new(STDOUT)
444
+ self.sdk_logger = ::Sentry::Logger.new(STDOUT)
423
445
  self.project_root = Dir.pwd
424
446
  self.propagate_traces = true
425
447
 
@@ -446,6 +468,7 @@ module Sentry
446
468
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
447
469
  self.traces_sampler = nil
448
470
  self.enable_tracing = nil
471
+ self.enable_logs = false
449
472
 
450
473
  self.profiler_class = Sentry::Profiler
451
474
 
@@ -455,6 +478,8 @@ module Sentry
455
478
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
456
479
 
457
480
  run_post_initialization_callbacks
481
+
482
+ self.max_log_events = LogEventBuffer::DEFAULT_MAX_EVENTS
458
483
  end
459
484
 
460
485
  def validate
@@ -15,7 +15,7 @@ module Sentry
15
15
  # rate limits and client reports use the data_category rather than envelope item type
16
16
  def self.data_category(type)
17
17
  case type
18
- when "session", "attachment", "transaction", "profile", "span" then type
18
+ when "session", "attachment", "transaction", "profile", "span", "log" then type
19
19
  when "sessions" then "session"
20
20
  when "check_in" then "monitor"
21
21
  when "statsd", "metric_meta" then "metric_bucket"
data/lib/sentry/event.rb CHANGED
@@ -7,6 +7,7 @@ require "sentry/backtrace"
7
7
  require "sentry/utils/real_ip"
8
8
  require "sentry/utils/request_id"
9
9
  require "sentry/utils/custom_inspection"
10
+ require "sentry/utils/uuid"
10
11
 
11
12
  module Sentry
12
13
  # This is an abstract class that defines the shared attributes of an event.
@@ -50,7 +51,7 @@ module Sentry
50
51
  # @param message [String, nil]
51
52
  def initialize(configuration:, integration_meta: nil, message: nil)
52
53
  # Set some simple default values
53
- @event_id = SecureRandom.uuid.delete("-")
54
+ @event_id = Utils.uuid
54
55
  @timestamp = Sentry.utc_now.iso8601
55
56
  @platform = :ruby
56
57
  @type = self.class::TYPE
data/lib/sentry/hub.rb CHANGED
@@ -216,6 +216,16 @@ module Sentry
216
216
  event.check_in_id
217
217
  end
218
218
 
219
+ def capture_log_event(message, **options)
220
+ return unless current_client
221
+
222
+ event = current_client.event_from_log(message, **options)
223
+
224
+ return unless event
225
+
226
+ current_client.buffer_log_event(event, current_scope)
227
+ end
228
+
219
229
  def capture_event(event, **options, &block)
220
230
  check_argument_type!(event, Sentry::Event)
221
231
 
@@ -99,7 +99,7 @@ module Sentry
99
99
  # Rails adds objects to the Rack env that can sometimes raise exceptions
100
100
  # when `to_s` is called.
101
101
  # See: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L134
102
- Sentry.logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
102
+ Sentry.sdk_logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
103
103
  next
104
104
  end
105
105
  end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # Event type that represents a log entry with its attributes
5
+ #
6
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
7
+ class LogEvent < Event
8
+ TYPE = "log"
9
+
10
+ DEFAULT_PARAMETERS = [].freeze
11
+ DEFAULT_ATTRIBUTES = {}.freeze
12
+ DEFAULT_CONTEXT = {}.freeze
13
+
14
+ SERIALIZEABLE_ATTRIBUTES = %i[
15
+ level
16
+ body
17
+ timestamp
18
+ trace_id
19
+ attributes
20
+ ]
21
+
22
+ SENTRY_ATTRIBUTES = {
23
+ "sentry.trace.parent_span_id" => :parent_span_id,
24
+ "sentry.environment" => :environment,
25
+ "sentry.release" => :release,
26
+ "sentry.address" => :server_name,
27
+ "sentry.sdk.name" => :sdk_name,
28
+ "sentry.sdk.version" => :sdk_version,
29
+ "sentry.message.template" => :template
30
+ }
31
+
32
+ LEVELS = %i[trace debug info warn error fatal].freeze
33
+
34
+ attr_accessor :level, :body, :template, :attributes
35
+
36
+ def initialize(configuration: Sentry.configuration, **options)
37
+ super(configuration: configuration)
38
+
39
+ @type = TYPE
40
+ @level = options.fetch(:level)
41
+ @body = options[:body]
42
+ @template = @body if is_template?
43
+ @attributes = options[:attributes] || DEFAULT_ATTRIBUTES
44
+ @contexts = DEFAULT_CONTEXT
45
+ end
46
+
47
+ def to_hash
48
+ SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |name, memo|
49
+ memo[name] = serialize(name)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def serialize(name)
56
+ serializer = :"serialize_#{name}"
57
+
58
+ if respond_to?(serializer, true)
59
+ __send__(serializer)
60
+ else
61
+ public_send(name)
62
+ end
63
+ end
64
+
65
+ def serialize_level
66
+ level.to_s
67
+ end
68
+
69
+ def serialize_sdk_name
70
+ Sentry.sdk_meta["name"]
71
+ end
72
+
73
+ def serialize_sdk_version
74
+ Sentry.sdk_meta["version"]
75
+ end
76
+
77
+ def serialize_timestamp
78
+ Time.parse(timestamp).to_f
79
+ end
80
+
81
+ def serialize_trace_id
82
+ contexts.dig(:trace, :trace_id)
83
+ end
84
+
85
+ def serialize_parent_span_id
86
+ contexts.dig(:trace, :parent_span_id)
87
+ end
88
+
89
+ def serialize_body
90
+ if parameters.empty?
91
+ body
92
+ elsif parameters.is_a?(Hash)
93
+ body % parameters
94
+ else
95
+ sprintf(body, *parameters)
96
+ end
97
+ end
98
+
99
+ def serialize_attributes
100
+ hash = attributes.each_with_object({}) do |(key, value), memo|
101
+ memo[key] = attribute_hash(value)
102
+ end
103
+
104
+ SENTRY_ATTRIBUTES.each do |key, name|
105
+ if (value = serialize(name))
106
+ hash[key] = attribute_hash(value)
107
+ end
108
+ end
109
+
110
+ hash
111
+ end
112
+
113
+ def attribute_hash(value)
114
+ { value: value, type: value_type(value) }
115
+ end
116
+
117
+ def value_type(value)
118
+ case value
119
+ when Integer
120
+ "integer"
121
+ when TrueClass, FalseClass
122
+ "boolean"
123
+ when Float
124
+ "double"
125
+ else
126
+ "string"
127
+ end
128
+ end
129
+
130
+ def parameters
131
+ @parameters ||= begin
132
+ return DEFAULT_PARAMETERS unless template
133
+
134
+ parameters = template_tokens.empty? ?
135
+ attributes.fetch(:parameters, DEFAULT_PARAMETERS) : attributes.slice(*template_tokens)
136
+
137
+ if parameters.is_a?(Hash)
138
+ parameters.each do |key, value|
139
+ attributes["sentry.message.parameter.#{key}"] = value
140
+ end
141
+ else
142
+ parameters.each_with_index do |param, index|
143
+ attributes["sentry.message.parameter.#{index}"] = param
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ TOKEN_REGEXP = /%\{(\w+)\}/
150
+
151
+ def template_tokens
152
+ @template_tokens ||= body.scan(TOKEN_REGEXP).flatten.map(&:to_sym)
153
+ end
154
+
155
+ def is_template?
156
+ body.include?("%s") || TOKEN_REGEXP.match?(body)
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/threaded_periodic_worker"
4
+
5
+ module Sentry
6
+ # LogEventBuffer buffers log events and sends them to Sentry in a single envelope.
7
+ #
8
+ # This is used internally by the `Sentry::Client`.
9
+ #
10
+ # @!visibility private
11
+ class LogEventBuffer < ThreadedPeriodicWorker
12
+ FLUSH_INTERVAL = 5 # seconds
13
+ DEFAULT_MAX_EVENTS = 100
14
+
15
+ # @!visibility private
16
+ attr_reader :pending_events
17
+
18
+ def initialize(configuration, client)
19
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
20
+
21
+ @client = client
22
+ @pending_events = []
23
+ @max_events = configuration.max_log_events || DEFAULT_MAX_EVENTS
24
+ @dsn = configuration.dsn
25
+ @sdk = Sentry.sdk_meta
26
+ @mutex = Mutex.new
27
+
28
+ log_debug("[Logging] Initialized buffer with max_events=#{@max_events}, flush_interval=#{FLUSH_INTERVAL}s")
29
+ end
30
+
31
+ def start
32
+ ensure_thread
33
+ self
34
+ end
35
+
36
+ def flush
37
+ @mutex.synchronize do
38
+ return if empty?
39
+
40
+ log_debug("[LogEventBuffer] flushing #{size} log events")
41
+
42
+ send_events
43
+ end
44
+
45
+ log_debug("[LogEventBuffer] flushed #{size} log events")
46
+
47
+ self
48
+ end
49
+ alias_method :run, :flush
50
+
51
+ def add_event(event)
52
+ raise ArgumentError, "expected a LogEvent, got #{event.class}" unless event.is_a?(LogEvent)
53
+
54
+ @mutex.synchronize do
55
+ @pending_events << event
56
+ send_events if size >= @max_events
57
+ end
58
+
59
+ self
60
+ end
61
+
62
+ def empty?
63
+ @pending_events.empty?
64
+ end
65
+
66
+ def size
67
+ @pending_events.size
68
+ end
69
+
70
+ private
71
+
72
+ def send_events
73
+ @client.send_envelope(to_envelope)
74
+ @pending_events.clear
75
+ end
76
+
77
+ def to_envelope
78
+ envelope = Envelope.new(
79
+ event_id: SecureRandom.uuid.delete("-"),
80
+ sent_at: Sentry.utc_now.iso8601,
81
+ dsn: @dsn,
82
+ sdk: @sdk
83
+ )
84
+
85
+ envelope.add_item(
86
+ {
87
+ type: "log",
88
+ item_count: size,
89
+ content_type: "application/vnd.sentry.items.log+json"
90
+ },
91
+ { items: @pending_events.map(&:to_hash) }
92
+ )
93
+
94
+ envelope
95
+ end
96
+ end
97
+ end
@@ -34,7 +34,7 @@ module Sentry
34
34
  attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
35
35
 
36
36
  def initialize(configuration, client)
37
- super(configuration.logger, FLUSH_INTERVAL)
37
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
38
38
  @client = client
39
39
  @before_emit = configuration.metrics.before_emit
40
40
  @enable_code_locations = configuration.metrics.enable_code_locations
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require_relative "profiler/helpers"
5
+ require "sentry/utils/uuid"
5
6
 
6
7
  module Sentry
7
8
  class Profiler
@@ -17,7 +18,7 @@ module Sentry
17
18
  attr_reader :sampled, :started, :event_id
18
19
 
19
20
  def initialize(configuration)
20
- @event_id = SecureRandom.uuid.delete("-")
21
+ @event_id = Utils.uuid
21
22
  @started = false
22
23
  @sampled = nil
23
24
 
@@ -192,7 +193,7 @@ module Sentry
192
193
  private
193
194
 
194
195
  def log(message)
195
- Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
196
+ Sentry.sdk_logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
196
197
  end
197
198
 
198
199
  def record_lost_event(reason)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "sentry/baggage"
5
+ require "sentry/utils/uuid"
5
6
 
6
7
  module Sentry
7
8
  class PropagationContext
@@ -66,8 +67,8 @@ module Sentry
66
67
  end
67
68
  end
68
69
 
69
- @trace_id ||= SecureRandom.uuid.delete("-")
70
- @span_id = SecureRandom.uuid.delete("-").slice(0, 16)
70
+ @trace_id ||= Utils.uuid
71
+ @span_id = Utils.uuid.slice(0, 16)
71
72
  end
72
73
 
73
74
  # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
@@ -124,8 +125,7 @@ module Sentry
124
125
  "trace_id" => trace_id,
125
126
  "environment" => configuration.environment,
126
127
  "release" => configuration.release,
127
- "public_key" => configuration.dsn&.public_key,
128
- "user_segment" => @scope.user && @scope.user["segment"]
128
+ "public_key" => configuration.dsn&.public_key
129
129
  }
130
130
 
131
131
  items.compact!
data/lib/sentry/scope.rb CHANGED
@@ -54,7 +54,7 @@ module Sentry
54
54
  event.transaction = transaction_name if transaction_name
55
55
  event.transaction_info = { source: transaction_source } if transaction_source
56
56
  event.fingerprint = fingerprint
57
- event.level = level
57
+ event.level = level unless event.is_a?(LogEvent)
58
58
  event.breadcrumbs = breadcrumbs
59
59
  event.rack_env = rack_env if rack_env
60
60
  event.attachments = attachments
@@ -5,7 +5,7 @@ module Sentry
5
5
  FLUSH_INTERVAL = 60
6
6
 
7
7
  def initialize(configuration, client)
8
- super(configuration.logger, FLUSH_INTERVAL)
8
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
9
9
  @client = client
10
10
  @pending_aggregates = {}
11
11
  @release = configuration.release
data/lib/sentry/span.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "sentry/metrics/local_aggregator"
5
+ require "sentry/utils/uuid"
5
6
 
6
7
  module Sentry
7
8
  class Span
@@ -127,8 +128,8 @@ module Sentry
127
128
  timestamp: nil,
128
129
  origin: nil
129
130
  )
130
- @trace_id = trace_id || SecureRandom.uuid.delete("-")
131
- @span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
131
+ @trace_id = trace_id || Utils.uuid
132
+ @span_id = span_id || Utils.uuid.slice(0, 16)
132
133
  @parent_span_id = parent_span_id
133
134
  @sampled = sampled
134
135
  @start_timestamp = start_timestamp || Sentry.utc_now.to_f
@@ -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
@@ -72,6 +72,13 @@ module Sentry
72
72
  sentry_transport.envelopes
73
73
  end
74
74
 
75
+ def sentry_logs
76
+ sentry_envelopes
77
+ .flat_map(&:items)
78
+ .select { |item| item.headers[:type] == "log" }
79
+ .flat_map { |item| item.payload[:items] }
80
+ end
81
+
75
82
  # Returns the last captured event object.
76
83
  # @return [Event, nil]
77
84
  def last_sentry_event
@@ -4,11 +4,11 @@ module Sentry
4
4
  class ThreadedPeriodicWorker
5
5
  include LoggingHelper
6
6
 
7
- def initialize(logger, internal)
7
+ def initialize(sdk_logger, interval)
8
8
  @thread = nil
9
9
  @exited = false
10
- @interval = internal
11
- @logger = logger
10
+ @interval = interval
11
+ @sdk_logger = sdk_logger
12
12
  end
13
13
 
14
14
  def ensure_thread
@@ -45,9 +45,6 @@ module Sentry
45
45
  # @deprecated Use Sentry.configuration instead.
46
46
  attr_reader :configuration
47
47
 
48
- # @deprecated Use Sentry.logger instead.
49
- attr_reader :logger
50
-
51
48
  # The effective sample rate at which this transaction was sampled.
52
49
  # @return [Float, nil]
53
50
  attr_reader :effective_sample_rate
@@ -78,7 +75,7 @@ module Sentry
78
75
  @tracing_enabled = hub.configuration.tracing_enabled?
79
76
  @traces_sampler = hub.configuration.traces_sampler
80
77
  @traces_sample_rate = hub.configuration.traces_sample_rate
81
- @logger = hub.configuration.logger
78
+ @sdk_logger = hub.configuration.sdk_logger
82
79
  @release = hub.configuration.release
83
80
  @environment = hub.configuration.environment
84
81
  @dsn = hub.configuration.dsn
@@ -342,9 +339,6 @@ module Sentry
342
339
 
343
340
  items["transaction"] = name unless source_low_quality?
344
341
 
345
- user = @hub.current_scope&.user
346
- items["user_segment"] = user["segment"] if user && user["segment"]
347
-
348
342
  items.compact!
349
343
  @baggage = Baggage.new(items, mutable: false)
350
344
  end
@@ -26,11 +26,8 @@ module Sentry
26
26
 
27
27
  attr_reader :rate_limits, :discarded_events, :last_client_report_sent
28
28
 
29
- # @deprecated Use Sentry.logger to retrieve the current logger instead.
30
- attr_reader :logger
31
-
32
29
  def initialize(configuration)
33
- @logger = configuration.logger
30
+ @sdk_logger = configuration.sdk_logger
34
31
  @transport_configuration = configuration.transport
35
32
  @dsn = configuration.dsn
36
33
  @rate_limits = {}
@@ -133,10 +130,21 @@ module Sentry
133
130
 
134
131
  envelope = Envelope.new(envelope_headers)
135
132
 
136
- envelope.add_item(
137
- { type: item_type, content_type: "application/json" },
138
- event_payload
139
- )
133
+ if event.is_a?(LogEvent)
134
+ envelope.add_item(
135
+ {
136
+ type: "log",
137
+ item_count: 1,
138
+ content_type: "application/vnd.sentry.items.log+json"
139
+ },
140
+ { items: [event_payload] }
141
+ )
142
+ else
143
+ envelope.add_item(
144
+ { type: item_type, content_type: "application/json" },
145
+ event_payload
146
+ )
147
+ end
140
148
 
141
149
  if event.is_a?(TransactionEvent) && event.profile
142
150
  envelope.add_item(
@@ -1,22 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
+ # @private
4
5
  module LoggingHelper
6
+ # @!visibility private
7
+ attr_reader :sdk_logger
8
+
9
+ # @!visibility private
5
10
  def log_error(message, exception, debug: false)
6
11
  message = "#{message}: #{exception.message}"
7
12
  message += "\n#{exception.backtrace.join("\n")}" if debug
8
13
 
9
- @logger.error(LOGGER_PROGNAME) do
14
+ sdk_logger.error(LOGGER_PROGNAME) do
10
15
  message
11
16
  end
12
17
  end
13
18
 
19
+ # @!visibility private
14
20
  def log_debug(message)
15
- @logger.debug(LOGGER_PROGNAME) { message }
21
+ sdk_logger.debug(LOGGER_PROGNAME) { message }
16
22
  end
17
23
 
24
+ # @!visibility private
18
25
  def log_warn(message)
19
- @logger.warn(LOGGER_PROGNAME) { message }
26
+ sdk_logger.warn(LOGGER_PROGNAME) { message }
20
27
  end
21
28
  end
22
29
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sentry
6
+ module Utils
7
+ DELIMITER = "-"
8
+
9
+ def self.uuid
10
+ SecureRandom.uuid.delete(DELIMITER)
11
+ end
12
+ end
13
+ end
@@ -3,6 +3,7 @@
3
3
  require "securerandom"
4
4
  require_relative "../profiler/helpers"
5
5
  require_relative "output"
6
+ require "sentry/utils/uuid"
6
7
 
7
8
  module Sentry
8
9
  module Vernier
@@ -12,7 +13,7 @@ module Sentry
12
13
  attr_reader :started, :event_id, :result
13
14
 
14
15
  def initialize(configuration)
15
- @event_id = SecureRandom.uuid.delete("-")
16
+ @event_id = Utils.uuid
16
17
 
17
18
  @started = false
18
19
  @sampled = nil
@@ -103,7 +104,7 @@ module Sentry
103
104
  private
104
105
 
105
106
  def log(message)
106
- Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler::Vernier] #{message}" }
107
+ Sentry.sdk_logger.debug(LOGGER_PROGNAME) { "[Profiler::Vernier] #{message}" }
107
108
  end
108
109
 
109
110
  def record_lost_event(reason)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.23.0"
4
+ VERSION = "5.24.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -11,7 +11,7 @@ require "sentry/utils/argument_checking_helper"
11
11
  require "sentry/utils/encoding_helper"
12
12
  require "sentry/utils/logging_helper"
13
13
  require "sentry/configuration"
14
- require "sentry/logger"
14
+ require "sentry/structured_logger"
15
15
  require "sentry/event"
16
16
  require "sentry/error_event"
17
17
  require "sentry/transaction_event"
@@ -54,6 +54,7 @@ module Sentry
54
54
 
55
55
  GLOBALS = %i[
56
56
  main_hub
57
+ logger
57
58
  session_flusher
58
59
  backpressure_monitor
59
60
  metrics_aggregator
@@ -138,7 +139,7 @@ module Sentry
138
139
  # @param version [String] version of the integration
139
140
  def register_integration(name, version)
140
141
  if initialized?
141
- logger.warn(LOGGER_PROGNAME) do
142
+ sdk_logger.warn(LOGGER_PROGNAME) do
142
143
  <<~MSG
143
144
  Integration '#{name}' is loaded after the SDK is initialized, which can cause unexpected behavior. Please make sure all integrations are loaded before SDK initialization.
144
145
  MSG
@@ -238,6 +239,7 @@ module Sentry
238
239
  def init(&block)
239
240
  config = Configuration.new
240
241
  yield(config) if block_given?
242
+
241
243
  config.detect_release
242
244
  apply_patches(config)
243
245
  config.validate
@@ -489,6 +491,25 @@ module Sentry
489
491
  get_current_hub.capture_check_in(slug, status, **options)
490
492
  end
491
493
 
494
+ # Captures a log event and sends it to Sentry via the currently active hub.
495
+ # This is the underlying method used by the StructuredLogger class.
496
+ #
497
+ # @param message [String] the log message
498
+ # @param [Hash] options Extra log event options
499
+ # @option options [Symbol] level The log level (:trace, :debug, :info, :warn, :error, :fatal)
500
+ # @option options [Integer] severity The severity number according to the Sentry Logs Protocol
501
+ # @option options [Hash] Additional attributes to include with the log
502
+ #
503
+ # @example Direct usage (prefer using Sentry.logger instead)
504
+ # Sentry.capture_log("User logged in", level: :info, user_id: 123)
505
+ #
506
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/ Sentry SDK Telemetry Logs Protocol
507
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
508
+ def capture_log(message, **options)
509
+ return unless initialized?
510
+ get_current_hub.capture_log_event(message, **options)
511
+ end
512
+
492
513
  # Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
493
514
  #
494
515
  # @return [Transaction, nil]
@@ -593,6 +614,43 @@ module Sentry
593
614
  get_current_hub.continue_trace(env, **options)
594
615
  end
595
616
 
617
+ # Returns the structured logger instance that implements Sentry's SDK telemetry logs protocol.
618
+ #
619
+ # This logger is only available when logs are enabled in the configuration.
620
+ #
621
+ # @example Enable logs in configuration
622
+ # Sentry.init do |config|
623
+ # config.dsn = "YOUR_DSN"
624
+ # config.enable_logs = true
625
+ # end
626
+ #
627
+ # @example Basic usage
628
+ # Sentry.logger.info("User logged in successfully", user_id: 123)
629
+ # Sentry.logger.error("Failed to process payment",
630
+ # transaction_id: "tx_123",
631
+ # error_code: "PAYMENT_FAILED"
632
+ # )
633
+ #
634
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/ Sentry SDK Telemetry Logs Protocol
635
+ #
636
+ # @return [StructuredLogger, nil] The structured logger instance or nil if logs are disabled
637
+ def logger
638
+ @logger ||=
639
+ if configuration.enable_logs
640
+ # Initialize the public-facing Structured Logger if logs are enabled
641
+ # This creates a StructuredLogger instance that implements Sentry's SDK telemetry logs protocol
642
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/
643
+ StructuredLogger.new(configuration)
644
+ else
645
+ warn <<~STR
646
+ [sentry] `Sentry.logger` will no longer be used as internal SDK logger when `enable_logs` feature is turned on.
647
+ Use Sentry.configuration.sdk_logger for SDK-specific logging needs."
648
+ STR
649
+
650
+ configuration.sdk_logger
651
+ end
652
+ end
653
+
596
654
  ##### Helpers #####
597
655
 
598
656
  # @!visibility private
@@ -604,8 +662,8 @@ module Sentry
604
662
  end
605
663
 
606
664
  # @!visibility private
607
- def logger
608
- configuration.logger
665
+ def sdk_logger
666
+ configuration.sdk_logger
609
667
  end
610
668
 
611
669
  # @!visibility private
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.23.0
4
+ version: 5.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-11 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: concurrent-ruby
@@ -48,8 +48,8 @@ email: accounts@sentry.io
48
48
  executables: []
49
49
  extensions: []
50
50
  extra_rdoc_files:
51
- - README.md
52
51
  - LICENSE.txt
52
+ - README.md
53
53
  files:
54
54
  - ".gitignore"
55
55
  - ".rspec"
@@ -101,6 +101,8 @@ files:
101
101
  - lib/sentry/interfaces/stacktrace_builder.rb
102
102
  - lib/sentry/interfaces/threads.rb
103
103
  - lib/sentry/linecache.rb
104
+ - lib/sentry/log_event.rb
105
+ - lib/sentry/log_event_buffer.rb
104
106
  - lib/sentry/logger.rb
105
107
  - lib/sentry/metrics.rb
106
108
  - lib/sentry/metrics/aggregator.rb
@@ -127,6 +129,7 @@ files:
127
129
  - lib/sentry/session.rb
128
130
  - lib/sentry/session_flusher.rb
129
131
  - lib/sentry/span.rb
132
+ - lib/sentry/structured_logger.rb
130
133
  - lib/sentry/test_helper.rb
131
134
  - lib/sentry/threaded_periodic_worker.rb
132
135
  - lib/sentry/transaction.rb
@@ -145,20 +148,21 @@ files:
145
148
  - lib/sentry/utils/logging_helper.rb
146
149
  - lib/sentry/utils/real_ip.rb
147
150
  - lib/sentry/utils/request_id.rb
151
+ - lib/sentry/utils/uuid.rb
148
152
  - lib/sentry/vernier/output.rb
149
153
  - lib/sentry/vernier/profiler.rb
150
154
  - lib/sentry/version.rb
151
155
  - sentry-ruby-core.gemspec
152
156
  - sentry-ruby.gemspec
153
- homepage: https://github.com/getsentry/sentry-ruby/tree/5.23.0/sentry-ruby
157
+ homepage: https://github.com/getsentry/sentry-ruby/tree/5.24.0/sentry-ruby
154
158
  licenses:
155
159
  - MIT
156
160
  metadata:
157
- homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.23.0/sentry-ruby
158
- source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.23.0/sentry-ruby
159
- changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.23.0/CHANGELOG.md
161
+ homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.24.0/sentry-ruby
162
+ source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.24.0/sentry-ruby
163
+ changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.24.0/CHANGELOG.md
160
164
  bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
161
- documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.23.0
165
+ documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.24.0
162
166
  rdoc_options: []
163
167
  require_paths:
164
168
  - lib
@@ -173,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
177
  - !ruby/object:Gem::Version
174
178
  version: '0'
175
179
  requirements: []
176
- rubygems_version: 3.6.2
180
+ rubygems_version: 3.6.7
177
181
  specification_version: 4
178
182
  summary: A gem that provides a client interface for the Sentry error logger
179
183
  test_files: []