Skip to content

Commit 16980a7

Browse files
committed
Auto-flush log events when adding and fix flush
1 parent 5bc7332 commit 16980a7

File tree

3 files changed

+73
-99
lines changed

3 files changed

+73
-99
lines changed

sentry-ruby/lib/sentry/log_event_buffer.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
require "sentry/threaded_periodic_worker"
44

55
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
611
class LogEventBuffer < ThreadedPeriodicWorker
712
FLUSH_INTERVAL = 5 # seconds
813
DEFAULT_MAX_EVENTS = 100
@@ -30,13 +35,11 @@ def start
3035

3136
def flush
3237
@mutex.synchronize do
33-
return unless size >= @max_events
38+
return if empty?
3439

3540
log_debug("[LogEventBuffer] flushing #{size} log events")
3641

37-
@client.send_envelope(to_envelope)
38-
39-
@pending_events.clear
42+
send_events
4043
end
4144

4245
log_debug("[LogEventBuffer] flushed #{size} log events")
@@ -50,6 +53,7 @@ def add_event(event)
5053

5154
@mutex.synchronize do
5255
@pending_events << event
56+
send_events if size >= @max_events
5357
end
5458

5559
self
@@ -65,6 +69,11 @@ def size
6569

6670
private
6771

72+
def send_events
73+
@client.send_envelope(to_envelope)
74+
@pending_events.clear
75+
end
76+
6877
def to_envelope
6978
envelope = Envelope.new(
7079
event_id: SecureRandom.uuid.delete("-"),

sentry-ruby/spec/sentry/log_event_buffer_spec.rb

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@
77

88
let(:string_io) { StringIO.new }
99
let(:logger) { ::Logger.new(string_io) }
10+
let(:client) { double(Sentry::Client) }
11+
let(:log_event) do
12+
Sentry::LogEvent.new(
13+
configuration: Sentry.configuration,
14+
level: :info,
15+
body: "Test message"
16+
)
17+
end
1018

1119
before do
1220
perform_basic_setup do |config|
1321
config.sdk_logger = logger
1422
config.background_worker_threads = 0
15-
config.max_log_events = 3
23+
config.max_log_events = max_log_events
1624
end
1725

1826
Sentry.background_worker = Sentry::BackgroundWorker.new(Sentry.configuration)
@@ -22,17 +30,8 @@
2230
Sentry.background_worker = Class.new { def shutdown; end; }.new
2331
end
2432

25-
let(:client) { Sentry.get_current_client }
26-
let(:transport) { client.transport }
27-
2833
describe "#add_event" do
29-
let(:log_event) do
30-
Sentry::LogEvent.new(
31-
configuration: Sentry.configuration,
32-
level: :info,
33-
body: "Test message"
34-
)
35-
end
34+
let(:max_log_events) { 3 }
3635

3736
it "does nothing when there are no pending events" do
3837
expect(client).not_to receive(:capture_envelope)
@@ -43,38 +42,36 @@
4342
end
4443

4544
it "does nothing when the number of events is less than max_events " do
46-
2.times { log_event_buffer.add_event(log_event) }
45+
expect(client).to_not receive(:send_envelope)
4746

48-
log_event_buffer.flush
49-
50-
expect(sentry_envelopes.size).to be(0)
47+
2.times { log_event_buffer.add_event(log_event) }
5148
end
5249

53-
it "sends pending events to the client" do
54-
3.times { log_event_buffer.add_event(log_event) }
55-
56-
log_event_buffer.flush
50+
it "auto-flushes pending events to the client when the number of events reaches max_events" do
51+
expect(client).to receive(:send_envelope)
5752

58-
expect(sentry_envelopes.size).to be(1)
53+
3.times { log_event_buffer.add_event(log_event) }
5954

6055
expect(log_event_buffer).to be_empty
6156
end
57+
end
6258

63-
it "thread-safely handles concurrent access", skip: RUBY_ENGINE == "jruby" do
64-
expect(client).to receive(:send_envelope) do |_envelope|
65-
sleep 0.1
66-
end
59+
describe "multi-threaded access" do
60+
let(:max_log_events) { 30 }
6761

68-
threads = 100.times.map do
69-
(1..50).to_a.sample.times { log_event_buffer.add_event(log_event) }
62+
it "thread-safely handles concurrent access" do
63+
expect(client).to receive(:send_envelope).exactly(3).times
7064

65+
threads = 3.times.map do
7166
Thread.new do
72-
log_event_buffer.flush
67+
(20..30).to_a.sample.times { log_event_buffer.add_event(log_event) }
7368
end
7469
end
7570

7671
threads.each(&:join)
7772

73+
log_event_buffer.flush
74+
7875
expect(log_event_buffer).to be_empty
7976
end
8077
end

sentry-ruby/spec/sentry/structured_logger_spec.rb

Lines changed: 36 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -21,111 +21,79 @@
2121
end
2222
end
2323

24-
let(:logs) do
25-
Sentry.get_current_client.log_event_buffer.pending_events
26-
end
27-
2824
["info", "warn", "error", "fatal"].each do |level|
2925
describe "##{level}" do
3026
it "logs using default logger and LogEvent logger with extra attributes" do
3127
payload = { user_id: 123, action: "create" }
3228

3329
Sentry.logger.public_send(level, "Hello World", payload)
3430

35-
expect(logs).to_not be_empty
31+
expect(sentry_logs).to_not be_empty
3632

37-
log_event = logs.last
33+
log_event = sentry_logs.last
3834

39-
expect(log_event.type).to eql("log")
40-
expect(log_event.level).to eql(level.to_sym)
41-
expect(log_event.body).to eql("Hello World")
42-
expect(log_event.attributes).to include(payload)
35+
expect(log_event[:level]).to eql(level)
36+
expect(log_event[:body]).to eql("Hello World")
37+
expect(log_event[:attributes]).to include({ user_id: { value: 123, type: "integer" } })
38+
expect(log_event[:attributes]).to include({ action: { value: "create", type: "string" } })
4339
end
4440

4541
it "logs with template parameters" do
4642
Sentry.logger.public_send(level, "Hello %s it is %s", ["Jane", "Monday"])
4743

48-
expect(logs).to_not be_empty
49-
50-
log_event = logs.last
51-
log_hash = log_event.to_hash
52-
53-
expect(log_event.type).to eql("log")
54-
expect(log_event.level).to eql(level.to_sym)
55-
expect(log_event.body).to eql("Hello %s it is %s")
44+
expect(sentry_logs).to_not be_empty
5645

57-
expect(log_hash[:body]).to eql("Hello Jane it is Monday")
46+
log_event = sentry_logs.last
5847

59-
attributes = log_hash[:attributes]
60-
61-
expect(attributes["sentry.message.template"]).to eql({ value: "Hello %s it is %s", type: "string" })
62-
expect(attributes["sentry.message.parameters.0"]).to eql({ value: "Jane", type: "string" })
63-
expect(attributes["sentry.message.parameters.1"]).to eql({ value: "Monday", type: "string" })
48+
expect(log_event[:level]).to eql(level)
49+
expect(log_event[:body]).to eql("Hello Jane it is Monday")
50+
expect(log_event[:attributes]["sentry.message.template"]).to eql({ value: "Hello %s it is %s", type: "string" })
51+
expect(log_event[:attributes]["sentry.message.parameters.0"]).to eql({ value: "Jane", type: "string" })
52+
expect(log_event[:attributes]["sentry.message.parameters.1"]).to eql({ value: "Monday", type: "string" })
6453
end
6554

6655
it "logs with template parameters and extra attributres" do
6756
Sentry.logger.public_send(level, "Hello %s it is %s", ["Jane", "Monday"], extra: 312)
6857

69-
expect(logs).to_not be_empty
70-
71-
log_event = logs.last
72-
log_hash = log_event.to_hash
73-
74-
expect(log_event.type).to eql("log")
75-
expect(log_event.level).to eql(level.to_sym)
76-
expect(log_event.body).to eql("Hello %s it is %s")
58+
expect(sentry_logs).to_not be_empty
7759

78-
expect(log_hash[:body]).to eql("Hello Jane it is Monday")
60+
log_event = sentry_logs.last
7961

80-
attributes = log_hash[:attributes]
81-
82-
expect(attributes[:extra]).to eql({ value: 312, type: "integer" })
83-
expect(attributes["sentry.message.template"]).to eql({ value: "Hello %s it is %s", type: "string" })
84-
expect(attributes["sentry.message.parameters.0"]).to eql({ value: "Jane", type: "string" })
85-
expect(attributes["sentry.message.parameters.1"]).to eql({ value: "Monday", type: "string" })
62+
expect(log_event[:level]).to eql(level)
63+
expect(log_event[:body]).to eql("Hello Jane it is Monday")
64+
expect(log_event[:attributes][:extra]).to eql({ value: 312, type: "integer" })
65+
expect(log_event[:attributes]["sentry.message.template"]).to eql({ value: "Hello %s it is %s", type: "string" })
66+
expect(log_event[:attributes]["sentry.message.parameters.0"]).to eql({ value: "Jane", type: "string" })
67+
expect(log_event[:attributes]["sentry.message.parameters.1"]).to eql({ value: "Monday", type: "string" })
8668
end
8769

8870
it "logs with hash-based template parameters" do
8971
Sentry.logger.public_send(level, "Hello %{name}, it is %{day}", name: "Jane", day: "Monday")
9072

91-
expect(logs).to_not be_empty
92-
93-
log_event = logs.last
94-
log_hash = log_event.to_hash
95-
96-
expect(log_event.type).to eql("log")
97-
expect(log_event.level).to eql(level.to_sym)
98-
expect(log_event.body).to eql("Hello %{name}, it is %{day}")
99-
100-
expect(log_hash[:body]).to eql("Hello Jane, it is Monday")
73+
expect(sentry_logs).to_not be_empty
10174

102-
attributes = log_hash[:attributes]
75+
log_event = sentry_logs.last
10376

104-
expect(attributes["sentry.message.template"]).to eql({ value: "Hello %{name}, it is %{day}", type: "string" })
105-
expect(attributes["sentry.message.parameters.name"]).to eql({ value: "Jane", type: "string" })
106-
expect(attributes["sentry.message.parameters.day"]).to eql({ value: "Monday", type: "string" })
77+
expect(log_event[:level]).to eql(level)
78+
expect(log_event[:body]).to eql("Hello Jane, it is Monday")
79+
expect(log_event[:attributes]["sentry.message.template"]).to eql({ value: "Hello %{name}, it is %{day}", type: "string" })
80+
expect(log_event[:attributes]["sentry.message.parameters.name"]).to eql({ value: "Jane", type: "string" })
81+
expect(log_event[:attributes]["sentry.message.parameters.day"]).to eql({ value: "Monday", type: "string" })
10782
end
10883

10984
it "logs with hash-based template parameters and extra attributes" do
11085
Sentry.logger.public_send(level, "Hello %{name}, it is %{day}", name: "Jane", day: "Monday", user_id: 123)
11186

112-
expect(logs).to_not be_empty
113-
114-
log_event = logs.last
115-
log_hash = log_event.to_hash
116-
117-
expect(log_event.type).to eql("log")
118-
expect(log_event.level).to eql(level.to_sym)
119-
expect(log_event.body).to eql("Hello %{name}, it is %{day}")
120-
121-
expect(log_hash[:body]).to eql("Hello Jane, it is Monday")
87+
expect(sentry_logs).to_not be_empty
12288

123-
attributes = log_hash[:attributes]
89+
log_event = sentry_logs.last
12490

125-
expect(attributes[:user_id]).to eql({ value: 123, type: "integer" })
126-
expect(attributes["sentry.message.template"]).to eql({ value: "Hello %{name}, it is %{day}", type: "string" })
127-
expect(attributes["sentry.message.parameters.name"]).to eql({ value: "Jane", type: "string" })
128-
expect(attributes["sentry.message.parameters.day"]).to eql({ value: "Monday", type: "string" })
91+
expect(log_event[:level]).to eql(level)
92+
expect(log_event[:body]).to eql("Hello Jane, it is Monday")
93+
expect(log_event[:attributes][:user_id]).to eql({ value: 123, type: "integer" })
94+
expect(log_event[:attributes]["sentry.message.template"]).to eql({ value: "Hello %{name}, it is %{day}", type: "string" })
95+
expect(log_event[:attributes]["sentry.message.parameters.name"]).to eql({ value: "Jane", type: "string" })
96+
expect(log_event[:attributes]["sentry.message.parameters.day"]).to eql({ value: "Monday", type: "string" })
12997
end
13098
end
13199
end

0 commit comments

Comments
 (0)