sentry-ruby 5.10.0 → 5.26.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/.rspec +3 -1
- data/Gemfile +12 -13
- data/README.md +26 -11
- data/Rakefile +9 -11
- data/bin/console +2 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +11 -5
- data/lib/sentry/backpressure_monitor.rb +45 -0
- data/lib/sentry/backtrace.rb +12 -9
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/breadcrumb.rb +13 -6
- data/lib/sentry/check_in_event.rb +61 -0
- data/lib/sentry/client.rb +214 -25
- data/lib/sentry/configuration.rb +221 -38
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +77 -0
- data/lib/sentry/cron/monitor_config.rb +53 -0
- data/lib/sentry/cron/monitor_schedule.rb +42 -0
- data/lib/sentry/dsn.rb +4 -4
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -68
- data/lib/sentry/error_event.rb +2 -2
- data/lib/sentry/event.rb +28 -47
- 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 +9 -0
- data/lib/sentry/hub.rb +138 -6
- data/lib/sentry/integrable.rb +10 -0
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +5 -3
- data/lib/sentry/interfaces/mechanism.rb +20 -0
- data/lib/sentry/interfaces/request.rb +8 -8
- data/lib/sentry/interfaces/single_exception.rb +13 -9
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
- data/lib/sentry/linecache.rb +3 -3
- data/lib/sentry/log_event.rb +206 -0
- data/lib/sentry/log_event_buffer.rb +75 -0
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +248 -0
- data/lib/sentry/metrics/configuration.rb +47 -0
- data/lib/sentry/metrics/counter_metric.rb +25 -0
- data/lib/sentry/metrics/distribution_metric.rb +25 -0
- data/lib/sentry/metrics/gauge_metric.rb +35 -0
- data/lib/sentry/metrics/local_aggregator.rb +53 -0
- data/lib/sentry/metrics/metric.rb +19 -0
- data/lib/sentry/metrics/set_metric.rb +28 -0
- data/lib/sentry/metrics/timing.rb +51 -0
- data/lib/sentry/metrics.rb +56 -0
- data/lib/sentry/net/http.rb +27 -44
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +41 -60
- data/lib/sentry/propagation_context.rb +135 -0
- data/lib/sentry/puma.rb +12 -5
- data/lib/sentry/rack/capture_exceptions.rb +17 -8
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +4 -15
- data/lib/sentry/redis.rb +10 -4
- data/lib/sentry/release_detector.rb +5 -5
- data/lib/sentry/rspec.rb +91 -0
- data/lib/sentry/scope.rb +75 -39
- data/lib/sentry/session.rb +2 -2
- data/lib/sentry/session_flusher.rb +15 -43
- data/lib/sentry/span.rb +92 -8
- data/lib/sentry/std_lib_logger.rb +50 -0
- data/lib/sentry/structured_logger.rb +138 -0
- data/lib/sentry/test_helper.rb +42 -13
- data/lib/sentry/threaded_periodic_worker.rb +39 -0
- data/lib/sentry/transaction.rb +44 -43
- data/lib/sentry/transaction_event.rb +10 -6
- data/lib/sentry/transport/configuration.rb +73 -1
- data/lib/sentry/transport/http_transport.rb +71 -41
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +53 -49
- data/lib/sentry/utils/argument_checking_helper.rb +12 -0
- 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 -7
- data/lib/sentry/utils/real_ip.rb +2 -2
- data/lib/sentry/utils/request_id.rb +1 -1
- 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 +206 -35
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +15 -6
- metadata +61 -11
@@ -1,58 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Sentry
|
4
|
-
class SessionFlusher
|
5
|
-
include LoggingHelper
|
6
|
-
|
4
|
+
class SessionFlusher < ThreadedPeriodicWorker
|
7
5
|
FLUSH_INTERVAL = 60
|
8
6
|
|
9
7
|
def initialize(configuration, client)
|
10
|
-
|
11
|
-
@exited = false
|
8
|
+
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
12
9
|
@client = client
|
13
10
|
@pending_aggregates = {}
|
14
11
|
@release = configuration.release
|
15
12
|
@environment = configuration.environment
|
16
|
-
@
|
13
|
+
@mutex = Mutex.new
|
17
14
|
|
18
15
|
log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
|
19
16
|
end
|
20
17
|
|
21
18
|
def flush
|
22
19
|
return if @pending_aggregates.empty?
|
23
|
-
envelope = pending_envelope
|
24
|
-
|
25
|
-
Sentry.background_worker.perform do
|
26
|
-
@client.transport.send_envelope(envelope)
|
27
|
-
end
|
28
20
|
|
29
|
-
@
|
21
|
+
@client.capture_envelope(pending_envelope)
|
30
22
|
end
|
31
23
|
|
24
|
+
alias_method :run, :flush
|
25
|
+
|
32
26
|
def add_session(session)
|
33
|
-
return if @exited
|
34
27
|
return unless @release
|
35
28
|
|
36
|
-
|
37
|
-
ensure_thread
|
38
|
-
rescue ThreadError
|
39
|
-
log_debug("Session flusher thread creation failed")
|
40
|
-
@exited = true
|
41
|
-
return
|
42
|
-
end
|
29
|
+
return unless ensure_thread
|
43
30
|
|
44
31
|
return unless Session::AGGREGATE_STATUSES.include?(session.status)
|
45
32
|
@pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
|
46
33
|
@pending_aggregates[session.aggregation_key][session.status] += 1
|
47
34
|
end
|
48
35
|
|
49
|
-
def kill
|
50
|
-
log_debug("Killing session flusher")
|
51
|
-
|
52
|
-
@exited = true
|
53
|
-
@thread&.kill
|
54
|
-
end
|
55
|
-
|
56
36
|
private
|
57
37
|
|
58
38
|
def init_aggregates(aggregation_key)
|
@@ -62,11 +42,15 @@ module Sentry
|
|
62
42
|
end
|
63
43
|
|
64
44
|
def pending_envelope
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
45
|
+
aggregates = @mutex.synchronize do
|
46
|
+
aggregates = @pending_aggregates.values
|
47
|
+
@pending_aggregates = {}
|
48
|
+
aggregates
|
49
|
+
end
|
69
50
|
|
51
|
+
envelope = Envelope.new
|
52
|
+
header = { type: "sessions" }
|
53
|
+
payload = { attrs: attrs, aggregates: aggregates }
|
70
54
|
envelope.add_item(header, payload)
|
71
55
|
envelope
|
72
56
|
end
|
@@ -74,17 +58,5 @@ module Sentry
|
|
74
58
|
def attrs
|
75
59
|
{ release: @release, environment: @environment }
|
76
60
|
end
|
77
|
-
|
78
|
-
def ensure_thread
|
79
|
-
return if @thread&.alive?
|
80
|
-
|
81
|
-
@thread = Thread.new do
|
82
|
-
loop do
|
83
|
-
sleep(FLUSH_INTERVAL)
|
84
|
-
flush
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
61
|
end
|
90
62
|
end
|
data/lib/sentry/span.rb
CHANGED
@@ -1,9 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "securerandom"
|
4
|
+
require "sentry/metrics/local_aggregator"
|
5
|
+
require "sentry/utils/uuid"
|
4
6
|
|
5
7
|
module Sentry
|
6
8
|
class Span
|
9
|
+
# We will try to be consistent with OpenTelemetry on this front going forward.
|
10
|
+
# https://develop.sentry.dev/sdk/performance/span-data-conventions/
|
11
|
+
module DataConventions
|
12
|
+
URL = "url"
|
13
|
+
HTTP_STATUS_CODE = "http.response.status_code"
|
14
|
+
HTTP_QUERY = "http.query"
|
15
|
+
HTTP_METHOD = "http.request.method"
|
16
|
+
|
17
|
+
# An identifier for the database management system (DBMS) product being used.
|
18
|
+
# Example: postgresql
|
19
|
+
DB_SYSTEM = "db.system"
|
20
|
+
|
21
|
+
# The name of the database being accessed.
|
22
|
+
# For commands that switch the database, this should be set to the target database
|
23
|
+
# (even if the command fails).
|
24
|
+
# Example: myDatabase
|
25
|
+
DB_NAME = "db.name"
|
26
|
+
|
27
|
+
# Name of the database host.
|
28
|
+
# Example: example.com
|
29
|
+
SERVER_ADDRESS = "server.address"
|
30
|
+
|
31
|
+
# Logical server port number
|
32
|
+
# Example: 80; 8080; 443
|
33
|
+
SERVER_PORT = "server.port"
|
34
|
+
|
35
|
+
# Physical server IP address or Unix socket address.
|
36
|
+
# Example: 10.5.3.2
|
37
|
+
SERVER_SOCKET_ADDRESS = "server.socket.address"
|
38
|
+
|
39
|
+
# Physical server port.
|
40
|
+
# Recommended: If different than server.port.
|
41
|
+
# Example: 16456
|
42
|
+
SERVER_SOCKET_PORT = "server.socket.port"
|
43
|
+
|
44
|
+
FILEPATH = "code.filepath"
|
45
|
+
LINENO = "code.lineno"
|
46
|
+
FUNCTION = "code.function"
|
47
|
+
NAMESPACE = "code.namespace"
|
48
|
+
|
49
|
+
MESSAGING_MESSAGE_ID = "messaging.message.id"
|
50
|
+
MESSAGING_DESTINATION_NAME = "messaging.destination.name"
|
51
|
+
MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
|
52
|
+
MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
|
53
|
+
end
|
54
|
+
|
7
55
|
STATUS_MAP = {
|
8
56
|
400 => "invalid_argument",
|
9
57
|
401 => "unauthenticated",
|
@@ -18,6 +66,8 @@ module Sentry
|
|
18
66
|
504 => "deadline_exceeded"
|
19
67
|
}
|
20
68
|
|
69
|
+
DEFAULT_SPAN_ORIGIN = "manual"
|
70
|
+
|
21
71
|
# An uuid that can be used to identify a trace.
|
22
72
|
# @return [String]
|
23
73
|
attr_reader :trace_id
|
@@ -51,6 +101,9 @@ module Sentry
|
|
51
101
|
# Span data
|
52
102
|
# @return [Hash]
|
53
103
|
attr_reader :data
|
104
|
+
# Span origin that tracks what kind of instrumentation created a span
|
105
|
+
# @return [String]
|
106
|
+
attr_reader :origin
|
54
107
|
|
55
108
|
# The SpanRecorder the current span belongs to.
|
56
109
|
# SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
|
@@ -72,10 +125,11 @@ module Sentry
|
|
72
125
|
parent_span_id: nil,
|
73
126
|
sampled: nil,
|
74
127
|
start_timestamp: nil,
|
75
|
-
timestamp: nil
|
128
|
+
timestamp: nil,
|
129
|
+
origin: nil
|
76
130
|
)
|
77
|
-
@trace_id = trace_id ||
|
78
|
-
@span_id = span_id ||
|
131
|
+
@trace_id = trace_id || Utils.uuid
|
132
|
+
@span_id = span_id || Utils.uuid.slice(0, 16)
|
79
133
|
@parent_span_id = parent_span_id
|
80
134
|
@sampled = sampled
|
81
135
|
@start_timestamp = start_timestamp || Sentry.utc_now.to_f
|
@@ -86,6 +140,7 @@ module Sentry
|
|
86
140
|
@status = status
|
87
141
|
@data = {}
|
88
142
|
@tags = {}
|
143
|
+
@origin = origin || DEFAULT_SPAN_ORIGIN
|
89
144
|
end
|
90
145
|
|
91
146
|
# Finishes the span by adding a timestamp.
|
@@ -111,9 +166,15 @@ module Sentry
|
|
111
166
|
transaction.get_baggage&.serialize
|
112
167
|
end
|
113
168
|
|
169
|
+
# Returns the Dynamic Sampling Context from the transaction baggage.
|
170
|
+
# @return [Hash, nil]
|
171
|
+
def get_dynamic_sampling_context
|
172
|
+
transaction.get_baggage&.dynamic_sampling_context
|
173
|
+
end
|
174
|
+
|
114
175
|
# @return [Hash]
|
115
176
|
def to_hash
|
116
|
-
{
|
177
|
+
hash = {
|
117
178
|
trace_id: @trace_id,
|
118
179
|
span_id: @span_id,
|
119
180
|
parent_span_id: @parent_span_id,
|
@@ -123,8 +184,14 @@ module Sentry
|
|
123
184
|
op: @op,
|
124
185
|
status: @status,
|
125
186
|
tags: @tags,
|
126
|
-
data: @data
|
187
|
+
data: @data,
|
188
|
+
origin: @origin
|
127
189
|
}
|
190
|
+
|
191
|
+
summary = metrics_summary
|
192
|
+
hash[:_metrics_summary] = summary if summary
|
193
|
+
|
194
|
+
hash
|
128
195
|
end
|
129
196
|
|
130
197
|
# Returns the span's context that can be used to embed in an Event.
|
@@ -136,7 +203,9 @@ module Sentry
|
|
136
203
|
parent_span_id: @parent_span_id,
|
137
204
|
description: @description,
|
138
205
|
op: @op,
|
139
|
-
status: @status
|
206
|
+
status: @status,
|
207
|
+
origin: @origin,
|
208
|
+
data: @data
|
140
209
|
}
|
141
210
|
end
|
142
211
|
|
@@ -193,7 +262,7 @@ module Sentry
|
|
193
262
|
|
194
263
|
|
195
264
|
# Sets the span's status.
|
196
|
-
# @param
|
265
|
+
# @param status [String] status of the span.
|
197
266
|
def set_status(status)
|
198
267
|
@status = status
|
199
268
|
end
|
@@ -208,7 +277,7 @@ module Sentry
|
|
208
277
|
# @param status_code [String] example: "500".
|
209
278
|
def set_http_status(status_code)
|
210
279
|
status_code = status_code.to_i
|
211
|
-
set_data(
|
280
|
+
set_data(DataConventions::HTTP_STATUS_CODE, status_code)
|
212
281
|
|
213
282
|
status =
|
214
283
|
if status_code >= 200 && status_code < 299
|
@@ -232,5 +301,20 @@ module Sentry
|
|
232
301
|
def set_tag(key, value)
|
233
302
|
@tags[key] = value
|
234
303
|
end
|
304
|
+
|
305
|
+
# Sets the origin of the span.
|
306
|
+
# @param origin [String]
|
307
|
+
def set_origin(origin)
|
308
|
+
@origin = origin
|
309
|
+
end
|
310
|
+
|
311
|
+
# Collects gauge metrics on the span for metric summaries.
|
312
|
+
def metrics_local_aggregator
|
313
|
+
@metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
|
314
|
+
end
|
315
|
+
|
316
|
+
def metrics_summary
|
317
|
+
@metrics_local_aggregator&.to_hash
|
318
|
+
end
|
235
319
|
end
|
236
320
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
# Ruby Logger support Add commentMore actions
|
5
|
+
# intercepts any logger instance and send the log to Sentry too.
|
6
|
+
module StdLibLogger
|
7
|
+
SEVERITY_MAP = {
|
8
|
+
0 => :debug,
|
9
|
+
1 => :info,
|
10
|
+
2 => :warn,
|
11
|
+
3 => :error,
|
12
|
+
4 => :fatal
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def add(severity, message = nil, progname = nil, &block)
|
16
|
+
result = super
|
17
|
+
|
18
|
+
return unless Sentry.initialized? && Sentry.get_current_hub
|
19
|
+
|
20
|
+
# exclude sentry SDK logs -- to prevent recursive log action,
|
21
|
+
# do not process internal logs again
|
22
|
+
if message.nil? && progname != Sentry::Logger::PROGNAME
|
23
|
+
|
24
|
+
# handle different nature of Ruby Logger class:
|
25
|
+
# inspo from Sentry::Breadcrumb::SentryLogger
|
26
|
+
if block_given?
|
27
|
+
message = yield
|
28
|
+
else
|
29
|
+
message = progname
|
30
|
+
end
|
31
|
+
|
32
|
+
message = message.to_s.strip
|
33
|
+
|
34
|
+
if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
|
35
|
+
Sentry.logger.send(method, message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Sentry.register_patch(:logger) do |config|
|
45
|
+
if config.enable_logs
|
46
|
+
::Logger.prepend(Sentry::StdLibLogger)
|
47
|
+
else
|
48
|
+
config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch")
|
49
|
+
end
|
50
|
+
end
|
@@ -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,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sentry
|
2
4
|
module TestHelper
|
3
|
-
DUMMY_DSN =
|
5
|
+
DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
|
4
6
|
|
5
7
|
# Alters the existing SDK configuration with test-suitable options. Mainly:
|
6
8
|
# - Sets a dummy DSN instead of `nil` or an actual DSN.
|
@@ -14,24 +16,28 @@ module Sentry
|
|
14
16
|
# @return [void]
|
15
17
|
def setup_sentry_test(&block)
|
16
18
|
raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
|
17
|
-
|
19
|
+
dummy_config = Sentry.configuration.dup
|
18
20
|
# configure dummy DSN, so the events will not be sent to the actual service
|
19
|
-
|
21
|
+
dummy_config.dsn = DUMMY_DSN
|
20
22
|
# set transport to DummyTransport, so we can easily intercept the captured events
|
21
|
-
|
23
|
+
dummy_config.transport.transport_class = Sentry::DummyTransport
|
22
24
|
# make sure SDK allows sending under the current environment
|
23
|
-
|
25
|
+
dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
|
24
26
|
# disble async event sending
|
25
|
-
|
27
|
+
dummy_config.background_worker_threads = 0
|
26
28
|
|
27
29
|
# user can overwrite some of the configs, with a few exceptions like:
|
28
30
|
# - include_local_variables
|
29
31
|
# - auto_session_tracking
|
30
|
-
block&.call(
|
32
|
+
block&.call(dummy_config)
|
31
33
|
|
32
|
-
|
34
|
+
# the base layer's client should already use the dummy config so nothing will be sent by accident
|
35
|
+
base_client = Sentry::Client.new(dummy_config)
|
36
|
+
Sentry.get_current_hub.bind_client(base_client)
|
37
|
+
# create a new layer so mutations made to the testing scope or configuration could be simply popped later
|
38
|
+
Sentry.get_current_hub.push_scope
|
39
|
+
test_client = Sentry::Client.new(dummy_config.dup)
|
33
40
|
Sentry.get_current_hub.bind_client(test_client)
|
34
|
-
Sentry.get_current_scope.clear
|
35
41
|
end
|
36
42
|
|
37
43
|
# Clears all stored events and envelopes.
|
@@ -40,9 +46,13 @@ module Sentry
|
|
40
46
|
def teardown_sentry_test
|
41
47
|
return unless Sentry.initialized?
|
42
48
|
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
# pop testing layer created by `setup_sentry_test`
|
50
|
+
# but keep the base layer to avoid nil-pointer errors
|
51
|
+
# TODO: find a way to notify users if they somehow popped the test layer before calling this method
|
52
|
+
if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
|
53
|
+
Sentry.get_current_hub.pop_scope
|
54
|
+
end
|
55
|
+
Sentry::Scope.global_event_processors.clear
|
46
56
|
end
|
47
57
|
|
48
58
|
# @return [Transport]
|
@@ -62,6 +72,13 @@ module Sentry
|
|
62
72
|
sentry_transport.envelopes
|
63
73
|
end
|
64
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
|
+
|
65
82
|
# Returns the last captured event object.
|
66
83
|
# @return [Event, nil]
|
67
84
|
def last_sentry_event
|
@@ -73,6 +90,18 @@ module Sentry
|
|
73
90
|
def extract_sentry_exceptions(event)
|
74
91
|
event&.exception&.values || []
|
75
92
|
end
|
93
|
+
|
94
|
+
def reset_sentry_globals!
|
95
|
+
Sentry::MUTEX.synchronize do
|
96
|
+
# Don't check initialized? because sometimes we stub it in tests
|
97
|
+
if Sentry.instance_variable_defined?(:@main_hub)
|
98
|
+
Sentry::GLOBALS.each do |var|
|
99
|
+
Sentry.instance_variable_set(:"@#{var}", nil)
|
100
|
+
end
|
101
|
+
|
102
|
+
Thread.current.thread_variable_set(Sentry::THREAD_LOCAL, nil)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
76
106
|
end
|
77
107
|
end
|
78
|
-
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class ThreadedPeriodicWorker
|
5
|
+
include LoggingHelper
|
6
|
+
|
7
|
+
def initialize(sdk_logger, interval)
|
8
|
+
@thread = nil
|
9
|
+
@exited = false
|
10
|
+
@interval = interval
|
11
|
+
@sdk_logger = sdk_logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def ensure_thread
|
15
|
+
return false if @exited
|
16
|
+
return true if @thread&.alive?
|
17
|
+
|
18
|
+
@thread = Thread.new do
|
19
|
+
loop do
|
20
|
+
sleep(@interval)
|
21
|
+
run
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
true
|
26
|
+
rescue ThreadError
|
27
|
+
log_debug("[#{self.class.name}] thread creation failed")
|
28
|
+
@exited = true
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def kill
|
33
|
+
log_debug("[#{self.class.name}] thread killed")
|
34
|
+
|
35
|
+
@exited = true
|
36
|
+
@thread&.kill
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|