Skip to content

Commit ee6536f

Browse files
authored
Support for before_send_log option (#2634)
* Add support for `before_log_send` * Handle dropped log events * Update CHANGELOG * Enhance log event processing with before_send_log callback and record discarded events * Use Sentry::Utils.uuid * Refactor log event processing to count discarded logs instead of storing them
1 parent 73745bb commit ee6536f

File tree

6 files changed

+128
-26
lines changed

6 files changed

+128
-26
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased
2+
3+
### Features
4+
5+
- Support for `before_send_log` ([#2634](https://github.com/getsentry/sentry-ruby/pull/2634))
6+
17
## 5.24.0
28

39
### Features

sentry-ruby/lib/sentry/client.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "sentry/transport"
44
require "sentry/log_event"
55
require "sentry/log_event_buffer"
6+
require "sentry/utils/uuid"
67

78
module Sentry
89
class Client
@@ -272,6 +273,53 @@ def send_event(event, hint = nil)
272273
raise
273274
end
274275

276+
# Send an envelope with batched logs
277+
# @param log_events [Array<LogEvent>] the log events to be sent
278+
# @api private
279+
# @return [void]
280+
def send_logs(log_events)
281+
envelope = Envelope.new(
282+
event_id: Sentry::Utils.uuid,
283+
sent_at: Sentry.utc_now.iso8601,
284+
dsn: configuration.dsn,
285+
sdk: Sentry.sdk_meta
286+
)
287+
288+
discarded_count = 0
289+
envelope_items = []
290+
291+
if configuration.before_send_log
292+
log_events.each do |log_event|
293+
processed_log_event = configuration.before_send_log.call(log_event)
294+
295+
if processed_log_event
296+
envelope_items << processed_log_event.to_hash
297+
else
298+
discarded_count += 1
299+
end
300+
end
301+
302+
envelope_items
303+
else
304+
envelope_items = log_events.map(&:to_hash)
305+
end
306+
307+
envelope.add_item(
308+
{
309+
type: "log",
310+
item_count: envelope_items.size,
311+
content_type: "application/vnd.sentry.items.log+json"
312+
},
313+
{ items: envelope_items }
314+
)
315+
316+
send_envelope(envelope)
317+
318+
unless discarded_count.zero?
319+
transport.record_lost_event(:before_send, "log_item", num: discarded_count)
320+
end
321+
end
322+
275323
# Send an envelope directly to Sentry.
276324
# @param envelope [Envelope] the envelope to be sent.
277325
# @return [void]

sentry-ruby/lib/sentry/configuration.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ class Configuration
101101
# @return [Proc]
102102
attr_reader :before_send_transaction
103103

104+
# Optional Proc, called before sending an event to the server
105+
# @example
106+
# config.before_send_log = lambda do |log|
107+
# log.attributes["sentry"] = true
108+
# log
109+
# end
110+
# @return [Proc]
111+
attr_accessor :before_send_log
112+
104113
# An array of breadcrumbs loggers to be used. Available options are:
105114
# - :sentry_logger
106115
# - :http_logger
@@ -465,6 +474,7 @@ def initialize
465474

466475
self.before_send = nil
467476
self.before_send_transaction = nil
477+
self.before_send_log = nil
468478
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
469479
self.traces_sampler = nil
470480
self.enable_tracing = nil

sentry-ruby/lib/sentry/log_event_buffer.rb

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ def initialize(configuration, client)
2121
@client = client
2222
@pending_events = []
2323
@max_events = configuration.max_log_events || DEFAULT_MAX_EVENTS
24-
@dsn = configuration.dsn
25-
@sdk = Sentry.sdk_meta
2624
@mutex = Mutex.new
2725

2826
log_debug("[Logging] Initialized buffer with max_events=#{@max_events}, flush_interval=#{FLUSH_INTERVAL}s")
@@ -70,28 +68,8 @@ def size
7068
private
7169

7270
def send_events
73-
@client.send_envelope(to_envelope)
71+
@client.send_logs(@pending_events)
7472
@pending_events.clear
7573
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
9674
end
9775
end

sentry-ruby/spec/sentry/log_event_buffer_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@
4242
end
4343

4444
it "does nothing when the number of events is less than max_events " do
45-
expect(client).to_not receive(:send_envelope)
45+
expect(client).to_not receive(:send_logs)
4646

4747
2.times { log_event_buffer.add_event(log_event) }
4848
end
4949

5050
it "auto-flushes pending events to the client when the number of events reaches max_events" do
51-
expect(client).to receive(:send_envelope)
51+
expect(client).to receive(:send_logs)
5252

5353
3.times { log_event_buffer.add_event(log_event) }
5454

@@ -60,7 +60,7 @@
6060
let(:max_log_events) { 30 }
6161

6262
it "thread-safely handles concurrent access" do
63-
expect(client).to receive(:send_envelope).exactly(3).times
63+
expect(client).to receive(:send_logs).exactly(3).times
6464

6565
threads = 3.times.map do
6666
Thread.new do

sentry-ruby/spec/sentry/structured_logger_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,65 @@
9999
end
100100
end
101101
end
102+
103+
describe "using config.before_send_log" do
104+
let(:transport) do
105+
Sentry.get_current_client.transport
106+
end
107+
108+
before do
109+
perform_basic_setup do |config|
110+
config.enable_logs = true
111+
config.send_client_reports = send_client_reports
112+
config.max_log_events = 1
113+
config.before_send_log = before_send_log
114+
end
115+
end
116+
117+
context "when send_client_reports is turned off and the callback returns a log event" do
118+
let(:send_client_reports) { false }
119+
120+
let(:before_send_log) do
121+
->(log) {
122+
log.attributes["hello"] = "world"
123+
log
124+
}
125+
end
126+
127+
it "sends processed log events" do
128+
Sentry.logger.info("Hello World", user_id: 125, action: "create")
129+
Sentry.logger.info("Hello World", user_id: 123, action: "create")
130+
Sentry.logger.info("Hello World", user_id: 127, action: "create")
131+
132+
expect(sentry_logs.size).to be(3)
133+
134+
log_event = sentry_logs.last
135+
136+
expect(log_event[:attributes]["hello"]).to eql({ value: "world", type: "string" })
137+
end
138+
end
139+
140+
context "when send_client_reports is turned on and the callback returns a log event" do
141+
let(:send_client_reports) { true }
142+
143+
let(:before_send_log) do
144+
->(log) {
145+
if log.attributes[:user_id] == 123
146+
log
147+
end
148+
}
149+
end
150+
151+
it "records discarded events" do
152+
Sentry.logger.info("Hello World", user_id: 125, action: "create")
153+
Sentry.logger.info("Hello World", user_id: 123, action: "create")
154+
Sentry.logger.info("Hello World", user_id: 127, action: "create")
155+
156+
expect(sentry_logs.size).to be(1)
157+
158+
expect(transport.discarded_events).to include([:before_send, "log_item"] => 2)
159+
end
160+
end
161+
end
102162
end
103163
end

0 commit comments

Comments
 (0)