fix: Add block syntax support to StructuredLogger for compatibility#2911
fix: Add block syntax support to StructuredLogger for compatibility#2911jeromepl wants to merge 1 commit intogetsentry:masterfrom
Conversation
Ruby's Logger supports passing the message as a block. For Sentry's structured logger to be used interchangeably with Ruby's logger, it should also support this syntax.
| # @param attributes [Hash] Additional attributes to include with the log | ||
| # | ||
| # @return [LogEvent, nil] The created log event or nil if logging is disabled | ||
| def log(level, message, parameters:, **attributes) | ||
| def log(level, message = nil, parameters:, **attributes, &block) | ||
| message = yield if message.nil? && block_given? | ||
| case parameters | ||
| when Array then | ||
| Sentry.capture_log(message, level: level, severity: LEVELS[level], parameters: parameters, **attributes) |
There was a problem hiding this comment.
Bug: DebugStructuredLogger methods were not updated to support block syntax. Calling them with a block will raise an ArgumentError because the message parameter is still required.
Severity: CRITICAL
Suggested Fix
Update the method definitions in DebugStructuredLogger to match StructuredLogger. Make the message parameter optional with a default nil value, add a &block parameter, and evaluate the block if no message is provided. This will ensure both logger classes behave consistently.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: sentry-ruby/lib/sentry/structured_logger.rb#L126-L133
Potential issue: The pull request adds block syntax support to `StructuredLogger`'s
logging methods, but the corresponding methods in `DebugStructuredLogger` were not
updated. The `DebugStructuredLogger` methods do not accept a block parameter (`&block`)
and still require a non-optional `message` argument. Consequently, if
`config.structured_logging.logger_class` is set to `Sentry::DebugStructuredLogger`, any
call to the logger using block syntax, such as `Sentry.logger.info { "message" }`, will
raise an `ArgumentError` and crash the application. This breaks the intended
interchangeability with Ruby's standard logger.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: DebugStructuredLogger not updated for block syntax support
- Updated DebugStructuredLogger log-level methods and log to accept optional message with block forwarding so block syntax works and no longer raises arity errors.
- ✅ Fixed: Nil message causes NoMethodError in LogEvent
- Added an explicit ArgumentError guard in StructuredLogger.log when both message and block are absent to prevent nil from reaching LogEvent.
Or push these changes by commenting:
@cursor push 811f47aaff
Preview (811f47aaff)
diff --git a/sentry-ruby/lib/sentry/debug_structured_logger.rb b/sentry-ruby/lib/sentry/debug_structured_logger.rb
--- a/sentry-ruby/lib/sentry/debug_structured_logger.rb
+++ b/sentry-ruby/lib/sentry/debug_structured_logger.rb
@@ -26,14 +26,20 @@
# Override all log level methods to capture events
%i[trace debug info warn error fatal].each do |level|
- define_method(level) do |message, parameters = [], **attributes|
+ define_method(level) do |message = nil, parameters = [], **attributes, &block|
+ message = block.call if message.nil? && block
+ raise ArgumentError, "message or block is required" if message.nil?
+
log_event = capture_log_event(level, message, parameters, **attributes)
backend.public_send(level, message, parameters, **attributes)
log_event
end
end
- def log(level, message, parameters:, **attributes)
+ def log(level, message = nil, parameters:, **attributes, &block)
+ message = block.call if message.nil? && block
+ raise ArgumentError, "message or block is required" if message.nil?
+
log_event = capture_log_event(level, message, parameters, **attributes)
backend.log(level, message, parameters: parameters, **attributes)
log_event
diff --git a/sentry-ruby/lib/sentry/structured_logger.rb b/sentry-ruby/lib/sentry/structured_logger.rb
--- a/sentry-ruby/lib/sentry/structured_logger.rb
+++ b/sentry-ruby/lib/sentry/structured_logger.rb
@@ -59,8 +59,8 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def trace(message, parameters = [], **attributes)
- log(__method__, message, parameters: parameters, **attributes)
+ def trace(message = nil, parameters = [], **attributes, &block)
+ log(__method__, message, parameters: parameters, **attributes, &block)
end
# Logs a message at DEBUG level
@@ -70,8 +70,8 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def debug(message, parameters = [], **attributes)
- log(__method__, message, parameters: parameters, **attributes)
+ def debug(message = nil, parameters = [], **attributes, &block)
+ log(__method__, message, parameters: parameters, **attributes, &block)
end
# Logs a message at INFO level
@@ -81,8 +81,8 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def info(message, parameters = [], **attributes)
- log(__method__, message, parameters: parameters, **attributes)
+ def info(message = nil, parameters = [], **attributes, &block)
+ log(__method__, message, parameters: parameters, **attributes, &block)
end
# Logs a message at WARN level
@@ -92,8 +92,8 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def warn(message, parameters = [], **attributes)
- log(__method__, message, parameters: parameters, **attributes)
+ def warn(message = nil, parameters = [], **attributes, &block)
+ log(__method__, message, parameters: parameters, **attributes, &block)
end
# Logs a message at ERROR level
@@ -103,8 +103,8 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def error(message, parameters = [], **attributes)
- log(__method__, message, parameters: parameters, **attributes)
+ def error(message = nil, parameters = [], **attributes, &block)
+ log(__method__, message, parameters: parameters, **attributes, &block)
end
# Logs a message at FATAL level
@@ -114,8 +114,8 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def fatal(message, parameters = [], **attributes)
- log(__method__, message, parameters: parameters, **attributes)
+ def fatal(message = nil, parameters = [], **attributes, &block)
+ log(__method__, message, parameters: parameters, **attributes, &block)
end
# Logs a message at the specified level
@@ -126,7 +126,10 @@
# @param attributes [Hash] Additional attributes to include with the log
#
# @return [LogEvent, nil] The created log event or nil if logging is disabled
- def log(level, message, parameters:, **attributes)
+ def log(level, message = nil, parameters:, **attributes, &block)
+ message = yield if message.nil? && block_given?
+ raise ArgumentError, "message or block is required" if message.nil?
+
case parameters
when Array then
Sentry.capture_log(message, level: level, severity: LEVELS[level], parameters: parameters, **attributes)
diff --git a/sentry-ruby/spec/sentry/debug_structured_logger_spec.rb b/sentry-ruby/spec/sentry/debug_structured_logger_spec.rb
--- a/sentry-ruby/spec/sentry/debug_structured_logger_spec.rb
+++ b/sentry-ruby/spec/sentry/debug_structured_logger_spec.rb
@@ -66,6 +66,21 @@
expect(log_event["parameters"]).to eq(["param1", "param2"])
expect(log_event["attributes"]["extra_attr"]).to eq("extra")
end
+
+ it "supports block syntax for compatibility with StructuredLogger" do
+ debug_logger.public_send(level) { "Test #{level} block message" }
+
+ logged_events = debug_logger.logged_events
+ expect(logged_events).not_to be_empty
+
+ log_event = logged_events.last
+ expect(log_event["message"]).to eq("Test #{level} block message")
+ end
+
+ it "raises an argument error when no message or block is given" do
+ expect { debug_logger.public_send(level) }
+ .to raise_error(ArgumentError, "message or block is required")
+ end
end
end
end
@@ -82,6 +97,22 @@
expect(log_event["message"]).to eq("Test log message")
expect(log_event["attributes"]["custom_attr"]).to eq("custom_value")
end
+
+ it "supports block syntax for compatibility with StructuredLogger" do
+ debug_logger.log(:info, parameters: []) { "Test log block message" }
+
+ logged_events = debug_logger.logged_events
+ expect(logged_events).not_to be_empty
+
+ log_event = logged_events.last
+ expect(log_event["level"]).to eq("info")
+ expect(log_event["message"]).to eq("Test log block message")
+ end
+
+ it "raises an argument error when no message or block is given" do
+ expect { debug_logger.log(:info, parameters: []) }
+ .to raise_error(ArgumentError, "message or block is required")
+ end
end
describe "#logged_events" do
diff --git a/sentry-ruby/spec/sentry/structured_logger_spec.rb b/sentry-ruby/spec/sentry/structured_logger_spec.rb
--- a/sentry-ruby/spec/sentry/structured_logger_spec.rb
+++ b/sentry-ruby/spec/sentry/structured_logger_spec.rb
@@ -135,6 +135,22 @@
expect(log_event[:attributes]["sentry.message.parameter.day"]).to eql({ value: "Monday", type: "string" })
end
+ it "logs with block syntax for compatibility with ruby Logger" do
+ Sentry.logger.public_send(level) { "Hello world" }
+
+ expect(sentry_logs).to_not be_empty
+
+ log_event = sentry_logs.last
+
+ expect(log_event[:level]).to eql(level)
+ expect(log_event[:body]).to eql("Hello world")
+ end
+
+ it "raises an argument error when no message or block is given" do
+ expect { Sentry.logger.public_send(level) }
+ .to raise_error(ArgumentError, "message or block is required")
+ end
+
context "handling of malformed strings" do
let(:malformed_string_default) do
Sentry::Utils::EncodingHelper::MALFORMED_STRINGThis Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
| def trace(message, parameters = [], **attributes) | ||
| log(__method__, message, parameters: parameters, **attributes) | ||
| def trace(message = nil, parameters = [], **attributes, &block) | ||
| log(__method__, message, parameters: parameters, **attributes, &block) |
There was a problem hiding this comment.
DebugStructuredLogger not updated for block syntax support
Medium Severity
DebugStructuredLogger overrides all the same log-level methods and log with the old signatures — message is required and no &block is accepted. After this change, calling block syntax like logger.info { "Hello" } on a DebugStructuredLogger instance raises an ArgumentError because it still expects a mandatory message argument and silently ignores the block. This breaks the compatibility goal for anyone using the debug logger variant.
Additional Locations (1)
| # @return [LogEvent, nil] The created log event or nil if logging is disabled | ||
| def log(level, message, parameters:, **attributes) | ||
| def log(level, message = nil, parameters:, **attributes, &block) | ||
| message = yield if message.nil? && block_given? |
There was a problem hiding this comment.
Nil message causes NoMethodError in LogEvent
Low Severity
Making message optional without a nil guard means calling a log method with no arguments and no block passes nil through to LogEvent#is_template?, which calls body.include?("%s") on nil, raising a NoMethodError. Previously, this scenario raised a clear ArgumentError at the method boundary.



Description
Ruby's Logger supports passing the message as a block. For Sentry's structured logger to be used interchangeably with Ruby's logger, it should also support this syntax.
For example, I wanted to pass
Sentry.loggerin our Rails app configured withconfig.rails.structured_logging.subscribers = { ... }to Faraday's logging middleware but that did not work because that middleware uses the block syntax:https://github.com/lostisland/faraday/blob/8b4d1fd06fd47dd33f3720794d4df38498c240ec/lib/faraday/logging/formatter.rb#L26:L28