Skip to content

Commit 854f4ca

Browse files
solnicvivekmiyani
andauthored
Handle more extra attribute types when logging (#2815)
* Handle params or nested objects correctly * Add tests * Update CHANGELOG.md --------- Co-authored-by: Vivek <[email protected]>
1 parent 4fcb941 commit 854f4ca

File tree

6 files changed

+88
-49
lines changed

6 files changed

+88
-49
lines changed

CHANGELOG.md

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
### Bug Fixes
3535

3636
- Handle empty frames case gracefully with local vars ([#2807](https://github.com/getsentry/sentry-ruby/pull/2807))
37+
- Handle more extra attribute types when using `extra` attributes for structured logging ([#2815](https://github.com/getsentry/sentry-ruby/pull/2815))
38+
```ruby
39+
# This now works too and the nested hash is dumped to JSON string
40+
Sentry.logger.info("Hello World", extra: { today: Date.today, user_id: user.id })
41+
```
3742

3843
## 6.2.0
3944

@@ -544,7 +549,6 @@
544549
- Use `Concurrent.available_processor_count` instead of `Concurrent.usable_processor_count` ([#2358](https://github.com/getsentry/sentry-ruby/pull/2358))
545550

546551
- Support for tracing Faraday requests ([#2345](https://github.com/getsentry/sentry-ruby/pull/2345))
547-
548552
- Closes [#1795](https://github.com/getsentry/sentry-ruby/issues/1795)
549553
- Please note that the Faraday instrumentation has some limitations in case of async requests: <https://github.com/lostisland/faraday/issues/1381>
550554

@@ -567,7 +571,6 @@
567571
```
568572

569573
- Transaction data are now included in the context ([#2365](https://github.com/getsentry/sentry-ruby/pull/2365))
570-
571574
- Closes [#2363](https://github.com/getsentry/sentry-ruby/issues/2363)
572575

573576
- Inject Sentry meta tags in the Rails application layout automatically in the generator ([#2369](https://github.com/getsentry/sentry-ruby/pull/2369))
@@ -681,7 +684,6 @@
681684
- Fix warning about default gems on Ruby 3.3.0 ([#2225](https://github.com/getsentry/sentry-ruby/pull/2225))
682685
- Add `hint:` support to `Sentry::Rails::ErrorSubscriber` [#2235](https://github.com/getsentry/sentry-ruby/pull/2235)
683686
- Add [Metrics](https://docs.sentry.io/product/metrics/) support
684-
685687
- Add main APIs and `Aggregator` thread [#2247](https://github.com/getsentry/sentry-ruby/pull/2247)
686688
- Add `Sentry::Metrics.timing` API for measuring block duration [#2254](https://github.com/getsentry/sentry-ruby/pull/2254)
687689
- Add metric summaries on spans [#2255](https://github.com/getsentry/sentry-ruby/pull/2255)
@@ -841,7 +843,6 @@
841843
- Improve default slug generation for Crons [#2168](https://github.com/getsentry/sentry-ruby/pull/2168)
842844
- Change release name generator to use full SHA commit hash and align with `sentry-cli` and other Sentry SDKs [#2174](https://github.com/getsentry/sentry-ruby/pull/2174)
843845
- Automatic Crons support for scheduling gems
844-
845846
- Add support for [`sidekiq-cron`](https://github.com/sidekiq-cron/sidekiq-cron) [#2170](https://github.com/getsentry/sentry-ruby/pull/2170)
846847
847848
You can opt in to the `sidekiq-cron` patch and we will automatically monitor check-ins for all jobs listed in your `config/schedule.yml` file.
@@ -897,7 +898,6 @@
897898
- Adopt Rails 7.1's new BroadcastLogger [#2120](https://github.com/getsentry/sentry-ruby/pull/2120)
898899
- Support sending events after all retries were performed (sentry-resque) [#2087](https://github.com/getsentry/sentry-ruby/pull/2087)
899900
- Add [Cron Monitoring](https://docs.sentry.io/product/crons/) support
900-
901901
- Add `Sentry.capture_check_in` API for Cron Monitoring [#2117](https://github.com/getsentry/sentry-ruby/pull/2117)
902902
903903
You can now track progress of long running scheduled jobs.
@@ -980,7 +980,6 @@
980980
```
981981

982982
- Tracing without Performance
983-
984983
- Implement `PropagationContext` on `Scope` and add `Sentry.get_trace_propagation_headers` API [#2084](https://github.com/getsentry/sentry-ruby/pull/2084)
985984
- Implement `Sentry.continue_trace` API [#2089](https://github.com/getsentry/sentry-ruby/pull/2089)
986985

@@ -1067,7 +1066,6 @@
10671066
```
10681067

10691068
Some implementation caveats:
1070-
10711069
- Profiles are sampled **relative** to traces, so if both rates are 0.5, we will capture 0.25 of all requests.
10721070
- Profiles are only captured for code running within a transaction.
10731071
- Profiles for multi-threaded servers like `puma` might not capture frames correctly when async I/O is happening. This is a `stackprof` limitation.
@@ -1152,7 +1150,6 @@
11521150
- Expose `end_timestamp` in `Span#finish` and `Transaction#finish` [#1946](https://github.com/getsentry/sentry-ruby/pull/1946)
11531151
- Add `Transaction#set_context` api [#1947](https://github.com/getsentry/sentry-ruby/pull/1947)
11541152
- Add OpenTelemetry support with new `sentry-opentelemetry` gem
1155-
11561153
- Add `config.instrumenter` to switch between `:sentry` and `:otel` instrumentation [#1944](https://github.com/getsentry/sentry-ruby/pull/1944)
11571154

11581155
The new `sentry-opentelemetry` gem adds support to automatically integrate OpenTelemetry performance tracing with Sentry. [Give it a try](https://github.com/getsentry/sentry-ruby/tree/master/sentry-opentelemetry#getting-started) and let us know if you have any feedback or problems with using it.
@@ -1172,7 +1169,6 @@
11721169
```
11731170

11741171
- Use `Sentry.with_child_span` in redis and net/http instead of `span.start_child` [#1920](https://github.com/getsentry/sentry-ruby/pull/1920)
1175-
11761172
- This might change the nesting of some spans and make it more accurate
11771173
- Followup fix to set the sentry-trace header in the correct place [#1922](https://github.com/getsentry/sentry-ruby/pull/1922)
11781174

@@ -1201,14 +1197,12 @@
12011197
### Features
12021198

12031199
- Support rack 3 [#1884](https://github.com/getsentry/sentry-ruby/pull/1884)
1204-
12051200
- We no longer need the `HTTP_VERSION` check for ignoring the header
12061201

12071202
- Add [Dynamic Sampling](https://docs.sentry.io/product/sentry-basics/sampling/) support
12081203
The SDK now supports Sentry's Dynamic Sampling product.
12091204

12101205
Note that this is not supported for users still using the `config.async` option.
1211-
12121206
- Parse incoming [W3C Baggage Headers](https://www.w3.org/TR/baggage/) and propagate them to continue traces [#1869](https://github.com/getsentry/sentry-ruby/pull/1869)
12131207
- in all outgoing requests in our net/http patch
12141208
- in Sentry transactions as [Dynamic Sampling Context](https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/)
@@ -1244,7 +1238,6 @@
12441238
- Expose `:values` in `ExceptionInterface`, so that it can be accessed in `before_send` under `event.exception.values` [#1843](https://github.com/getsentry/sentry-ruby/pull/1843)
12451239

12461240
- Add top level `Sentry.close` API [#1844](https://github.com/getsentry/sentry-ruby/pull/1844)
1247-
12481241
- Cleans up SDK state and sets it to uninitialized
12491242
- No-ops all SDK APIs and also disables the transport layer, so nothing will be sent to Sentry after closing the SDK
12501243

@@ -1406,7 +1399,6 @@ end
14061399
- Check envelope size before sending it [#1747](https://github.com/getsentry/sentry-ruby/pull/1747)
14071400

14081401
The SDK will now check if the envelope's event items are oversized before sending the envelope. It goes like this:
1409-
14101402
1. If an event is oversized (200kb), the SDK will remove its breadcrumbs (which in our experience is the most common cause).
14111403
2. If the event size now falls within the limit, it'll be sent.
14121404
3. Otherwise, the event will be thrown away. The SDK will also log a debug message about the event's attributes size (in bytes) breakdown. For example,
@@ -1725,7 +1717,6 @@ When `config.send_default_pii` is set as `true`, `:http_logger` will include que
17251717
### Features
17261718

17271719
- Support exception frame's local variable capturing
1728-
17291720
- [#1580](https://github.com/getsentry/sentry-ruby/pull/1580)
17301721
- [#1589](https://github.com/getsentry/sentry-ruby/pull/1589)
17311722

sentry-rails/spec/sentry/rails/log_subscribers/action_controller_subscriber_spec.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
expect(log_event[:attributes][:method][:value]).to eq("GET")
3636
expect(log_event[:attributes][:path][:value]).to eq("/world")
37-
expect(log_event[:attributes][:format][:value]).to eq(:html)
37+
expect(log_event[:attributes][:format][:value]).to eq("\"html\"")
3838
expect(log_event[:attributes]["sentry.origin"][:value]).to eq("auto.log.rails.log_subscriber")
3939
end
4040

@@ -275,8 +275,10 @@
275275
log_event = sentry_logs.find { |log| log[:body] == "HelloController#world" }
276276
expect(log_event).not_to be_nil
277277
expect(log_event[:attributes][:params]).to be_present
278-
expect(log_event[:attributes][:params][:value]).to include("safe_param" => "value")
279-
expect(log_event[:attributes][:params][:value]).to include("password" => "[FILTERED]")
278+
279+
params = JSON.parse(log_event[:attributes][:params][:value])
280+
expect(params).to include("safe_param" => "value")
281+
expect(params).to include("password" => "[FILTERED]")
280282
end
281283

282284
it "filters sensitive parameter names" do
@@ -295,7 +297,7 @@
295297
log_event = sentry_logs.find { |log| log[:body] == "HelloController#world" }
296298
expect(log_event).not_to be_nil
297299

298-
params = log_event[:attributes][:params][:value]
300+
params = JSON.parse(log_event[:attributes][:params][:value])
299301
expect(params).to include("normal_param" => "value")
300302
expect(params).to include("password" => "[FILTERED]")
301303
expect(params).to include("api_key" => "[FILTERED]")
@@ -323,7 +325,7 @@
323325
log_event = sentry_logs.find { |log| log[:body] == "HelloController#world" }
324326
expect(log_event).not_to be_nil
325327

326-
params = log_event[:attributes][:params][:value]
328+
params = JSON.parse(log_event[:attributes][:params][:value])
327329
expect(params).to include("normal_param" => "value")
328330
expect(params["user"]).to include("name" => "John")
329331
expect(params["user"]).to include("password" => "[FILTERED]")

sentry-rails/spec/sentry/rails/log_subscribers/action_mailer_subscriber_spec.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
expect(log_event[:attributes][:mailer][:value]).to eq("UserMailer")
3838
expect(log_event[:attributes][:duration_ms][:value]).to be > 0
3939
expect(log_event[:attributes][:perform_deliveries][:value]).to be true
40-
expect(log_event[:attributes][:delivery_method][:value]).to eq(:test)
40+
expect(log_event[:attributes][:delivery_method][:value]).to eq("\"test\"")
4141
expect(log_event[:attributes]["sentry.origin"][:value]).to eq("auto.log.rails.log_subscriber")
4242
expect(log_event[:attributes][:date]).to be_present
4343
end
@@ -76,7 +76,7 @@
7676

7777
log_event = sentry_logs.find { |log| log[:body] == "Email delivered via NotificationMailer" }
7878
expect(log_event).not_to be_nil
79-
expect(log_event[:attributes][:delivery_method][:value]).to eq(:smtp)
79+
expect(log_event[:attributes][:delivery_method][:value]).to eq("\"smtp\"")
8080
end
8181

8282
it "includes date when available" do
@@ -179,12 +179,12 @@
179179
expect(log_event).not_to be_nil
180180
expect(log_event[:attributes][:params]).to be_present
181181

182-
params = log_event[:attributes][:params][:value]
182+
params = JSON.parse(log_event[:attributes][:params][:value])
183183

184-
expect(params).to include(user_id: 123, safe_param: "value")
185-
expect(params[:password]).to eq("[FILTERED]")
186-
expect(params[:api_key]).to eq("[FILTERED]")
187-
expect(params).to include(email_address: "[email protected]", subject: "Welcome!")
184+
expect(params).to include("user_id" => 123, "safe_param" => "value")
185+
expect(params["password"]).to eq("[FILTERED]")
186+
expect(params["api_key"]).to eq("[FILTERED]")
187+
expect(params).to include("email_address" => "[email protected]", "subject" => "Welcome!")
188188
end
189189
end
190190

sentry-rails/spec/sentry/rails/log_subscribers/active_job_subscriber_spec.rb

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
expect(attributes[:job_id][:value]).to be_a(String)
5858
expect(attributes[:queue_name][:value]).to eq("default")
5959
expect(attributes[:executions][:value]).to eq(1)
60-
expect(attributes[:priority][:value]).to be_a(Integer).or be_nil
60+
expect(attributes[:priority][:value]).to be_a(Integer).or(eq("null")).or be_nil
6161
end
6262

6363
it "includes adapter information when available" do
@@ -122,8 +122,9 @@ def perform(*args, **kwargs)
122122
expect(log_event).not_to be_nil
123123

124124
attributes = log_event[:attributes]
125-
expect(attributes[:arguments][:value]).to be_a(Array)
126-
expect(attributes[:arguments][:value]).to include("safe_arg")
125+
arguments = JSON.parse(attributes[:arguments][:value])
126+
expect(arguments).to be_a(Array)
127+
expect(arguments).to include("safe_arg")
127128
end
128129

129130
it "filters sensitive arguments" do
@@ -147,11 +148,11 @@ def perform(password:, token:, safe_data:)
147148
expect(log_event).not_to be_nil
148149

149150
attributes = log_event[:attributes]
150-
arguments = attributes[:arguments][:value]
151+
arguments = JSON.parse(attributes[:arguments][:value])
151152

152-
expect(arguments.first).to include(safe_data: "public")
153-
expect(arguments.first).to include(password: "[FILTERED]")
154-
expect(arguments.first).to include(token: "[FILTERED]")
153+
expect(arguments.first).to include("safe_data" => "public")
154+
expect(arguments.first).to include("password" => "[FILTERED]")
155+
expect(arguments.first).to include("token" => "[FILTERED]")
155156

156157
Rails.application.config.filter_parameters = original_filter_params
157158
end
@@ -175,7 +176,7 @@ def perform(short_string, long_string)
175176
expect(log_event).not_to be_nil
176177

177178
attributes = log_event[:attributes]
178-
arguments = attributes[:arguments][:value]
179+
arguments = JSON.parse(attributes[:arguments][:value])
179180

180181
expect(arguments).to include("short")
181182
expect(arguments).to include("[FILTERED: 150 chars]")
@@ -204,11 +205,11 @@ def perform(string_arg, hash_arg, number_arg, array_arg)
204205
expect(log_event).not_to be_nil
205206

206207
attributes = log_event[:attributes]
207-
arguments = attributes[:arguments][:value]
208+
arguments = JSON.parse(attributes[:arguments][:value])
208209

209210
expect(arguments[0]).to eq("string_value")
210-
expect(arguments[1]).to include(safe_key: "value")
211-
expect(arguments[1]).to include(password: "[FILTERED]")
211+
expect(arguments[1]).to include("safe_key" => "value")
212+
expect(arguments[1]).to include("password" => "[FILTERED]")
212213
expect(arguments[2]).to eq(42)
213214
expect(arguments[3]).to eq([1, 2, 3])
214215
end

sentry-ruby/lib/sentry/log_event.rb

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "json"
4+
35
module Sentry
46
# Event type that represents a log entry with its attributes
57
#
@@ -62,13 +64,6 @@ class LogEvent
6264
user_email
6365
].map { |name| [name, :"serialize_#{name}"] }.to_h
6466

65-
VALUE_TYPES = Hash.new("string").merge!({
66-
TrueClass => "boolean",
67-
FalseClass => "boolean",
68-
Integer => "integer",
69-
Float => "double"
70-
}).freeze
71-
7267
TOKEN_REGEXP = /%\{(\w+)\}/
7368

7469
def initialize(configuration: Sentry.configuration, **options)
@@ -178,11 +173,22 @@ def serialize_attributes
178173
end
179174

180175
def attribute_hash(value)
181-
{ value: value, type: value_type(value) }
182-
end
183-
184-
def value_type(value)
185-
VALUE_TYPES[value.class]
176+
case value
177+
when String
178+
{ value: value, type: "string" }
179+
when TrueClass, FalseClass
180+
{ value: value, type: "boolean" }
181+
when Integer
182+
{ value: value, type: "integer" }
183+
when Float
184+
{ value: value, type: "double" }
185+
else
186+
begin
187+
{ value: JSON.generate(value), type: "string" }
188+
rescue
189+
{ value: value, type: "string" }
190+
end
191+
end
186192
end
187193

188194
def parameters

sentry-ruby/spec/sentry/structured_logger_spec.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,45 @@
6767
expect(log_event[:attributes]["sentry.message.parameter.1"]).to eql({ value: "Monday", type: "string" })
6868
end
6969

70+
it "logs with nested hash attributes" do
71+
attributes = { number: 312, string: "hello" }
72+
73+
Sentry.logger.public_send(level, "Hello world", extra: attributes)
74+
75+
expect(sentry_logs).to_not be_empty
76+
77+
log_event = sentry_logs.last
78+
79+
expect(log_event[:level]).to eql(level)
80+
expect(log_event[:attributes][:extra]).to eql({ type: "string", value: attributes.to_json })
81+
end
82+
83+
it "logs with array attributes" do
84+
attributes = [1, 2, 3, "hello"]
85+
86+
Sentry.logger.public_send(level, "Hello world", extra: attributes)
87+
88+
expect(sentry_logs).to_not be_empty
89+
90+
log_event = sentry_logs.last
91+
92+
expect(log_event[:level]).to eql(level)
93+
expect(log_event[:attributes][:extra]).to eql({ type: "string", value: attributes.to_json })
94+
end
95+
96+
it "logs with date in attributes" do
97+
attributes = Date.today
98+
99+
Sentry.logger.public_send(level, "Hello world", extra: attributes)
100+
101+
expect(sentry_logs).to_not be_empty
102+
103+
log_event = sentry_logs.last
104+
105+
expect(log_event[:level]).to eql(level)
106+
expect(log_event[:attributes][:extra]).to eql({ type: "string", value: attributes.to_json })
107+
end
108+
70109
it "logs with hash-based template parameters" do
71110
Sentry.logger.public_send(level, "Hello %{name}, it is %{day}", name: "Jane", day: "Monday")
72111

0 commit comments

Comments
 (0)