From 4f021737f90e6d99054d8b621b6f7c479bddd115 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Thu, 1 Oct 2026 18:48:42 +0200 Subject: [PATCH 1/5] Add failing tests for logging that fails the request Each of these makes the request raise (a 500) or loses the batch today: - collapse_into_single_event without HTTPContext (KeyError :http) - capture_request_body with a missing or non-rewindable Rack 3 input - capture_response_body with a body that isn't an Array - an error while building or formatting an event in HTTPEvents, HTTPContext, UserContext or ErrorEvent (ErrorEvent then raises the logging error instead of the app's exception) Co-Authored-By: Claude Opus 5.5 --- spec/logtail-rack/error_event_spec.rb | 14 +++ spec/logtail-rack/http_context_spec.rb | 18 ++++ spec/logtail-rack/http_events_spec.rb | 126 +++++++++++++++++++++++++ spec/logtail-rack/user_context_spec.rb | 25 +++++ 4 files changed, 183 insertions(+) create mode 100644 spec/logtail-rack/error_event_spec.rb create mode 100644 spec/logtail-rack/http_context_spec.rb create mode 100644 spec/logtail-rack/user_context_spec.rb diff --git a/spec/logtail-rack/error_event_spec.rb b/spec/logtail-rack/error_event_spec.rb new file mode 100644 index 0000000..63942e3 --- /dev/null +++ b/spec/logtail-rack/error_event_spec.rb @@ -0,0 +1,14 @@ +require "spec_helper" +require "stringio" + +RSpec.describe Logtail::Integrations::Rack::ErrorEvent do + it "re-raise the app's exception when logging it raises" do + app = ->(env) { raise "app failure" } + logger = Logtail::Logger.new(StringIO.new) + # Like the JSON formatter does with json 3 and ActiveSupport 8.0 or older + logger.formatter = ->(*) { raise ArgumentError, "unknown keyword: :quirks_mode" } + allow(Logtail::Config.instance).to receive(:logger).and_return(logger) + + expect { described_class.new(app).call(Rack::MockRequest.env_for("https://example.com/test-page")) }.to raise_error(RuntimeError, "app failure") + end +end diff --git a/spec/logtail-rack/http_context_spec.rb b/spec/logtail-rack/http_context_spec.rb new file mode 100644 index 0000000..7c2806a --- /dev/null +++ b/spec/logtail-rack/http_context_spec.rb @@ -0,0 +1,18 @@ +require "spec_helper" + +RSpec.describe Logtail::Integrations::Rack::HTTPContext do + it "pass the request on without the HTTP context when reading the request raises" do + http_contexts = [] + app = lambda { |env| + http_contexts << Logtail::CurrentContext.fetch(:http, nil) + [200, { "content-type" => "text/plain" }, ["hello"]] + } + # Rack::Request#host raises ArgumentError for invalid UTF-8 in a UTF-8 string + request = Rack::MockRequest.env_for("https://example.com/test-page", "HTTP_X_FORWARDED_HOST" => "\xFF") + + response = described_class.new(app).call(request) + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["hello"]]) + expect(http_contexts).to eq([nil]) + end +end diff --git a/spec/logtail-rack/http_events_spec.rb b/spec/logtail-rack/http_events_spec.rb index 9de15db..ad9ea11 100755 --- a/spec/logtail-rack/http_events_spec.rb +++ b/spec/logtail-rack/http_events_spec.rb @@ -17,6 +17,105 @@ expect(logs.map { |log| log['message'] }).to match(['Started GET "/test-page"', /Completed 200 OK in \d+\.\d+ms/]) end + it "return the app's response when collapsing into a single event without HTTPContext" do + app = ->(env) { [200, { "content-type" => "text/plain" }, ["hello"]] } + + response = nil + logs = capture_logs { with_collapse_into_single_event { response = described_class.new(app).call(mock_request) } } + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["hello"]]) + expect(logs.map { |log| log['message'] }).to match([/\ACompleted 200 OK in \d+\.\d+ms\z/]) + end + + it "capture the request body and leave it for the app" do + app = ->(env) { [200, { "content-type" => "text/plain" }, [env["rack.input"].read]] } + request = Rack::MockRequest.env_for('https://example.com/form', method: "POST", input: "name=value") + + response = nil + logs = capture_logs { with_capture_request_body { response = described_class.new(app).call(request) } } + + expect(response[2]).to eq(["name=value"]) + expect(logs.first["event"]["http_request_received"]["body"]).to eq("name=value") + end + + it "leave the request body for the app when the input can't be rewound" do + app = ->(env) { [200, { "content-type" => "text/plain" }, [env["rack.input"].read]] } + # Rack 3 doesn't require rack.input to be rewindable + input = StringIO.new("name=value") + input.singleton_class.send(:undef_method, :rewind) + request = Rack::MockRequest.env_for('https://example.com/form', method: "POST", input: input) + + response = nil + logs = capture_logs { with_capture_request_body { response = described_class.new(app).call(request) } } + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["name=value"]]) + expect(logs.first["event"]["http_request_received"]["body"]).to be_nil + end + + it "return the app's response when capturing the request body without a request input" do + app = ->(env) { [200, { "content-type" => "text/plain" }, ["hello"]] } + # Rack 3.1 and later may leave rack.input out + request = Rack::MockRequest.env_for('https://example.com/test-page') + request.delete("rack.input") + + response = nil + logs = capture_logs { with_capture_request_body { response = described_class.new(app).call(request) } } + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["hello"]]) + expect(logs.first["event"]["http_request_received"]["body"]).to be_nil + end + + it "log a captured Array response body as one String" do + app = ->(env) { [200, { "content-type" => "text/plain" }, ["hello", " world"]] } + + logs = capture_logs { with_capture_response_body { described_class.new(app).call(mock_request) } } + + expect(logs.last["event"]["http_response_sent"]["body"]).to eq("hello world") + end + + it "skip capturing a response body that isn't an Array and return it untouched" do + # Can be iterated only once, by the server + body = Object.new + def body.each + yield "hello" + end + app = ->(env) { [200, { "content-type" => "text/plain" }, body] } + + response = nil + logs = capture_logs { with_capture_response_body { response = described_class.new(app).call(mock_request) } } + + expect(response[2]).to be(body) + expect(logs.last["event"]["http_response_sent"]["body"]).to be_nil + end + + it "return the app's response and log the response when logging the request raises" do + app = ->(env) { [200, { "content-type" => "text/plain" }, ["hello"]] } + # Rack::Request#host raises ArgumentError for invalid UTF-8 in a UTF-8 string + request = Rack::MockRequest.env_for('https://example.com/test-page', 'HTTP_X_FORWARDED_HOST' => "\xFF") + + response = nil + logs = nil + debug_logs = capture_debug_logs { logs = capture_logs { response = described_class.new(app).call(request) } } + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["hello"]]) + expect(logs.map { |log| log['message'] }).to match([/\ACompleted 200 OK in \d+\.\d+ms\z/]) + expect(debug_logs).to include("Logtail::Integrations::Rack::HTTPEvents could not log an event: #") + end + + it "return the app's response when the logger raises" do + app = ->(env) { [200, { "content-type" => "text/plain" }, ["hello"]] } + logger = Logtail::Logger.new(StringIO.new) + # Like the JSON formatter does with json 3 and ActiveSupport 8.0 or older + logger.formatter = ->(*) { raise ArgumentError, "unknown keyword: :quirks_mode" } + allow(Logtail::Config.instance).to receive(:logger).and_return(logger) + + response = nil + debug_logs = capture_debug_logs { response = described_class.new(app).call(mock_request) } + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["hello"]]) + expect(debug_logs.scan("Logtail::Integrations::Rack::HTTPEvents could not log an event: #").length).to eq(2) + end + it "log HTTP request headers, filtering the Authorization header by default" do logs = capture_logs { middleware.call mock_request } @@ -148,6 +247,33 @@ def capture_logs(&blk) Logtail::Config.instance.logger = old_logger end + def capture_debug_logs(&blk) + string_io = StringIO.new + Logtail::Config.instance.debug_logger = ::Logger.new(string_io) + + blk.call + + string_io.string + ensure + Logtail::Config.instance.debug_logger = nil + end + + def with_capture_request_body(&blk) + Logtail::Integrations::Rack::HTTPEvents.capture_request_body = true + + blk.call + ensure + Logtail::Integrations::Rack::HTTPEvents.capture_request_body = false + end + + def with_capture_response_body(&blk) + Logtail::Integrations::Rack::HTTPEvents.capture_response_body = true + + blk.call + ensure + Logtail::Integrations::Rack::HTTPEvents.capture_response_body = false + end + def with_http_header_filters(headers, &blk) Logtail::Integrations::Rack::HTTPEvents.http_header_filters = headers diff --git a/spec/logtail-rack/user_context_spec.rb b/spec/logtail-rack/user_context_spec.rb new file mode 100644 index 0000000..ee40d8d --- /dev/null +++ b/spec/logtail-rack/user_context_spec.rb @@ -0,0 +1,25 @@ +require "spec_helper" + +RSpec.describe Logtail::Integrations::Rack::UserContext do + it "pass the request on without the user context when reading the user raises" do + user = Object.new + def user.id + 1 + end + # Like an ActiveRecord user loaded without its email column + def user.email + raise NoMethodError, "missing attribute 'email' for User" + end + user_contexts = [] + app = lambda { |env| + user_contexts << Logtail::CurrentContext.fetch(:user, nil) + [200, { "content-type" => "text/plain" }, ["hello"]] + } + request = Rack::MockRequest.env_for("https://example.com/test-page", "warden" => double(user: user)) + + response = described_class.new(app).call(request) + + expect(response).to eq([200, { "content-type" => "text/plain" }, ["hello"]]) + expect(user_contexts).to eq([nil]) + end +end From 827bcac83c4f37253ca142841ce65066e779cf3c Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Thu, 1 Oct 2026 18:57:19 +0200 Subject: [PATCH 2/5] Never fail a request because logging it failed - Middleware#log_safely logs the event its block builds and sends any StandardError from building or writing it to the debug logger. HTTPEvents (both modes) and ErrorEvent log through it, so ErrorEvent re-raises the app's exception, not the logging error. - HTTPContext and UserContext build their context in a method that rescues like SessionContext#get_session_id, and the request goes on without that context. SessionContext reports what it rescues too. - The collapsed event fetches the HTTP context with a nil default. - capture_request_body skips a missing or non-rewindable input instead of consuming the body the app still needs. - capture_response_body logs Array bodies joined, and nothing else. The app's own exceptions are untouched: nothing wraps @app.call. Co-Authored-By: Claude Opus 5.5 --- lib/logtail-rack/error_event.rb | 2 +- lib/logtail-rack/http_context.rb | 32 ++++++++++++++++++++--------- lib/logtail-rack/http_events.rb | 11 +++++----- lib/logtail-rack/middleware.rb | 11 ++++++++++ lib/logtail-rack/session_context.rb | 1 + lib/logtail-rack/user_context.rb | 3 +++ lib/logtail-rack/util/request.rb | 4 ++++ 7 files changed, 48 insertions(+), 16 deletions(-) diff --git a/lib/logtail-rack/error_event.rb b/lib/logtail-rack/error_event.rb index 19d0f00..79b0f3b 100755 --- a/lib/logtail-rack/error_event.rb +++ b/lib/logtail-rack/error_event.rb @@ -11,7 +11,7 @@ def call(env) begin status, headers, body = @app.call(env) rescue Exception => exception - Config.instance.logger.fatal do + log_safely(:fatal) do Events::Error.new( name: exception.class.name, error_message: exception.message, diff --git a/lib/logtail-rack/http_context.rb b/lib/logtail-rack/http_context.rb index 7f1336d..4701b6e 100755 --- a/lib/logtail-rack/http_context.rb +++ b/lib/logtail-rack/http_context.rb @@ -1,3 +1,4 @@ +require "logtail/config" require "logtail/contexts/http" require "logtail/current_context" require "logtail-rack/middleware" @@ -10,19 +11,30 @@ module Rack # A Rack middleware that is reponsible for adding the HTTP context {Logtail::Contexts::HTTP}. class HTTPContext < Middleware def call(env) - request = Util::Request.new(env) - context = Contexts::HTTP.new( - host: Util::Encoding.force_utf8_encoding(request.host), - method: Util::Encoding.force_utf8_encoding(request.request_method), - path: request.path, - remote_addr: Util::Encoding.force_utf8_encoding(request.ip), - request_id: request.request_id - ) - - CurrentContext.with(context.to_hash) do + context = get_http_context(env) + if context + CurrentContext.with(context) do + @app.call(env) + end + else @app.call(env) end end + + private + def get_http_context(env) + request = Util::Request.new(env) + Contexts::HTTP.new( + host: Util::Encoding.force_utf8_encoding(request.host), + method: Util::Encoding.force_utf8_encoding(request.request_method), + path: request.path, + remote_addr: Util::Encoding.force_utf8_encoding(request.ip), + request_id: request.request_id + ).to_hash + rescue StandardError => e + Logtail::Config.instance.debug { "Could not build the HTTP context: #{e.inspect}\n\n#{e.backtrace}" } + nil + end end end end diff --git a/lib/logtail-rack/http_events.rb b/lib/logtail-rack/http_events.rb index 6cce46e..4594e06 100755 --- a/lib/logtail-rack/http_events.rb +++ b/lib/logtail-rack/http_events.rb @@ -149,8 +149,8 @@ def call(env) status, headers, body = @app.call(env) request_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) - Config.instance.logger.info do - http_context = CurrentContext.fetch(:http) + log_safely(:info) do + http_context = CurrentContext.fetch(:http, nil) content_length = response_content_length(headers) duration_ms = ((request_end - request_start) * 1000.0).round(1) @@ -181,7 +181,7 @@ def call(env) [status, headers, body] else - Config.instance.logger.info do + log_safely(:info) do event_body = capture_request_body? ? request.body_content : nil http_request = HTTPRequest.new( body: event_body, @@ -220,8 +220,9 @@ def call(env) status, headers, body = @app.call(env) request_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) - Config.instance.logger.info do - event_body = capture_response_body? ? body : nil + log_safely(:info) do + # Only an Array body can be read twice, other bodies may stream to the server once + event_body = capture_response_body? && body.is_a?(Array) ? body.join : nil content_length = response_content_length(headers) duration_ms = ((request_end - request_start) * 1000.0).round(1) diff --git a/lib/logtail-rack/middleware.rb b/lib/logtail-rack/middleware.rb index e8f9f94..e758ee6 100755 --- a/lib/logtail-rack/middleware.rb +++ b/lib/logtail-rack/middleware.rb @@ -1,3 +1,5 @@ +require "logtail/config" + module Logtail module Integrations module Rack @@ -22,6 +24,15 @@ def enabled? def initialize(app) @app = app end + + private + # Logs the event the block builds. Logging must never fail the request, so an error raised + # while building or writing the event only goes to the debug logger. + def log_safely(severity, &block) + Config.instance.logger.public_send(severity, &block) + rescue StandardError => e + Config.instance.debug { "#{self.class.name} could not log an event: #{e.inspect}\n\n#{e.backtrace}" } + end end end end diff --git a/lib/logtail-rack/session_context.rb b/lib/logtail-rack/session_context.rb index 4618d78..5eeb6e4 100755 --- a/lib/logtail-rack/session_context.rb +++ b/lib/logtail-rack/session_context.rb @@ -39,6 +39,7 @@ def get_session_id(env) nil end rescue Exception => e + Logtail::Config.instance.debug { "Could not obtain the session id: #{e.inspect}\n\n#{e.backtrace}" } nil end end diff --git a/lib/logtail-rack/user_context.rb b/lib/logtail-rack/user_context.rb index 7e26c57..9e90666 100755 --- a/lib/logtail-rack/user_context.rb +++ b/lib/logtail-rack/user_context.rb @@ -93,6 +93,9 @@ def get_user_hash(env) Logtail::Config.instance.debug { "Could not locate any user data" } nil end + rescue StandardError => e + Logtail::Config.instance.debug { "Could not obtain the user context: #{e.inspect}\n\n#{e.backtrace}" } + nil end def get_user_object_hash(user) diff --git a/lib/logtail-rack/util/request.rb b/lib/logtail-rack/util/request.rb index d412e83..d71c13a 100755 --- a/lib/logtail-rack/util/request.rb +++ b/lib/logtail-rack/util/request.rb @@ -13,6 +13,10 @@ class Request < ::Rack::Request REQUEST_ID_KEY_NAME2 = 'HTTP_X_REQUEST_ID'.freeze def body_content + # Rack 3 allows a missing or non-rewindable input, and reading one that can't be rewound takes the body + # from the app + return nil unless body.respond_to?(:rewind) + content = body.read body.rewind content From 4fd28916a265adfe35986e435a2253341eba7854 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 2 Oct 2026 08:20:18 +0200 Subject: [PATCH 3/5] Add failing tests for the runtime context of the logged events logtail reports the line that called the logger as the runtime context of a log line. Since log_safely calls the logger itself, with public_send, every request, response and error event says it was logged by middleware.rb in Kernel#public_send instead of by the middleware's call, as on 0.2.8. These tests pass on 0.2.8. Co-Authored-By: Claude Opus 5.5 --- spec/logtail-rack/error_event_spec.rb | 14 ++++++++++++++ spec/logtail-rack/http_events_spec.rb | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/spec/logtail-rack/error_event_spec.rb b/spec/logtail-rack/error_event_spec.rb index 63942e3..d944cfb 100644 --- a/spec/logtail-rack/error_event_spec.rb +++ b/spec/logtail-rack/error_event_spec.rb @@ -11,4 +11,18 @@ expect { described_class.new(app).call(Rack::MockRequest.env_for("https://example.com/test-page")) }.to raise_error(RuntimeError, "app failure") end + + it "log the error with this middleware's call as its runtime context" do + app = ->(env) { raise "app failure" } + io = StringIO.new + logger = Logtail::Logger.new(io) + logger.formatter = Logtail::Logger::JSONFormatter.new + allow(Logtail::Config.instance).to receive(:logger).and_return(logger) + + expect { described_class.new(app).call(Rack::MockRequest.env_for("https://example.com/test-page")) }.to raise_error(RuntimeError, "app failure") + + runtime = JSON.parse(io.string)["context"]["runtime"] + expect(runtime["file"]).to end_with("lib/logtail-rack/error_event.rb") + expect(runtime["frame_label"]).to match(/(\A|#)call\z/) + end end diff --git a/spec/logtail-rack/http_events_spec.rb b/spec/logtail-rack/http_events_spec.rb index ad9ea11..909774f 100755 --- a/spec/logtail-rack/http_events_spec.rb +++ b/spec/logtail-rack/http_events_spec.rb @@ -17,6 +17,23 @@ expect(logs.map { |log| log['message'] }).to match(['Started GET "/test-page"', /Completed 200 OK in \d+\.\d+ms/]) end + it "log the request and the response with this middleware's call as their runtime context" do + logs = capture_logs { middleware.call mock_request } + + runtimes = logs.map { |log| log["context"]["runtime"] } + expect(runtimes.map { |runtime| runtime["file"] }).to all(end_with("lib/logtail-rack/http_events.rb")) + expect(runtimes.map { |runtime| runtime["frame_label"] }).to all(match(/(\A|#)call\z/)) + end + + it "log the single event with this middleware's call as its runtime context" do + stack = Logtail::Integrations::Rack::HTTPContext.new(middleware) + logs = capture_logs { with_collapse_into_single_event { stack.call mock_request } } + + runtime = logs.first["context"]["runtime"] + expect(runtime["file"]).to end_with("lib/logtail-rack/http_events.rb") + expect(runtime["frame_label"]).to match(/(\A|#)call\z/) + end + it "return the app's response when collapsing into a single event without HTTPContext" do app = ->(env) { [200, { "content-type" => "text/plain" }, ["hello"]] } From de74cca35469d7827ec942b289ae266bef936b92 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 2 Oct 2026 08:21:55 +0200 Subject: [PATCH 4/5] Call the logger at the call site again so the runtime context points to it log_safely called the logger itself, so logtail reported middleware.rb and Kernel#public_send as the runtime context of every request, response and error event. The middlewares call the logger themselves again, as on 0.2.8, and only rescue an error raised while building or writing the event with a rescue modifier on the block. logging_failed passes it to the debug logger, as log_safely did. The runtime context of the events is the same as on 0.2.8 again: the file, the line of the logger call and the label. The tests now check the exact label, so a block around the logger call would fail them too. Co-Authored-By: Claude Opus 5.5 --- lib/logtail-rack/error_event.rb | 4 ++-- lib/logtail-rack/http_events.rb | 12 ++++++------ lib/logtail-rack/middleware.rb | 15 +++++++++------ spec/logtail-rack/error_event_spec.rb | 2 +- spec/logtail-rack/http_events_spec.rb | 4 ++-- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/logtail-rack/error_event.rb b/lib/logtail-rack/error_event.rb index 79b0f3b..34eddf4 100755 --- a/lib/logtail-rack/error_event.rb +++ b/lib/logtail-rack/error_event.rb @@ -11,13 +11,13 @@ def call(env) begin status, headers, body = @app.call(env) rescue Exception => exception - log_safely(:fatal) do + Config.instance.logger.fatal do Events::Error.new( name: exception.class.name, error_message: exception.message, backtrace: exception.backtrace ) - end + end rescue logging_failed($!) raise exception end diff --git a/lib/logtail-rack/http_events.rb b/lib/logtail-rack/http_events.rb index 4594e06..185ee2b 100755 --- a/lib/logtail-rack/http_events.rb +++ b/lib/logtail-rack/http_events.rb @@ -149,7 +149,7 @@ def call(env) status, headers, body = @app.call(env) request_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) - log_safely(:info) do + Config.instance.logger.info do http_context = CurrentContext.fetch(:http, nil) content_length = response_content_length(headers) duration_ms = ((request_end - request_start) * 1000.0).round(1) @@ -177,11 +177,11 @@ def call(env) } } } - end + end rescue logging_failed($!) [status, headers, body] else - log_safely(:info) do + Config.instance.logger.info do event_body = capture_request_body? ? request.body_content : nil http_request = HTTPRequest.new( body: event_body, @@ -214,13 +214,13 @@ def call(env) } } } - end + end rescue logging_failed($!) request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) status, headers, body = @app.call(env) request_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) - log_safely(:info) do + Config.instance.logger.info do # Only an Array body can be read twice, other bodies may stream to the server once event_body = capture_response_body? && body.is_a?(Array) ? body.join : nil content_length = response_content_length(headers) @@ -249,7 +249,7 @@ def call(env) } } } - end + end rescue logging_failed($!) [status, headers, body] end diff --git a/lib/logtail-rack/middleware.rb b/lib/logtail-rack/middleware.rb index e758ee6..5d66e3a 100755 --- a/lib/logtail-rack/middleware.rb +++ b/lib/logtail-rack/middleware.rb @@ -26,12 +26,15 @@ def initialize(app) end private - # Logs the event the block builds. Logging must never fail the request, so an error raised - # while building or writing the event only goes to the debug logger. - def log_safely(severity, &block) - Config.instance.logger.public_send(severity, &block) - rescue StandardError => e - Config.instance.debug { "#{self.class.name} could not log an event: #{e.inspect}\n\n#{e.backtrace}" } + # Logging must never fail the request, so an error raised while building or writing an event + # only goes to the debug logger. The middlewares call the logger themselves, so that the + # runtime context of the line points to them, and rescue the error with this method: + # + # Config.instance.logger.info do + # ... + # end rescue logging_failed($!) + def logging_failed(error) + Config.instance.debug { "#{self.class.name} could not log an event: #{error.inspect}\n\n#{error.backtrace}" } end end end diff --git a/spec/logtail-rack/error_event_spec.rb b/spec/logtail-rack/error_event_spec.rb index d944cfb..82cb7e5 100644 --- a/spec/logtail-rack/error_event_spec.rb +++ b/spec/logtail-rack/error_event_spec.rb @@ -23,6 +23,6 @@ runtime = JSON.parse(io.string)["context"]["runtime"] expect(runtime["file"]).to end_with("lib/logtail-rack/error_event.rb") - expect(runtime["frame_label"]).to match(/(\A|#)call\z/) + expect(runtime["frame_label"]).to match(/\A(Logtail::Integrations::Rack::ErrorEvent#)?call\z/) end end diff --git a/spec/logtail-rack/http_events_spec.rb b/spec/logtail-rack/http_events_spec.rb index 909774f..c268fd2 100755 --- a/spec/logtail-rack/http_events_spec.rb +++ b/spec/logtail-rack/http_events_spec.rb @@ -22,7 +22,7 @@ runtimes = logs.map { |log| log["context"]["runtime"] } expect(runtimes.map { |runtime| runtime["file"] }).to all(end_with("lib/logtail-rack/http_events.rb")) - expect(runtimes.map { |runtime| runtime["frame_label"] }).to all(match(/(\A|#)call\z/)) + expect(runtimes.map { |runtime| runtime["frame_label"] }).to all(match(/\A(Logtail::Integrations::Rack::HTTPEvents#)?call\z/)) end it "log the single event with this middleware's call as its runtime context" do @@ -31,7 +31,7 @@ runtime = logs.first["context"]["runtime"] expect(runtime["file"]).to end_with("lib/logtail-rack/http_events.rb") - expect(runtime["frame_label"]).to match(/(\A|#)call\z/) + expect(runtime["frame_label"]).to match(/\A(Logtail::Integrations::Rack::HTTPEvents#)?call\z/) end it "return the app's response when collapsing into a single event without HTTPContext" do From bca4370a10fa3aa4be8790b5c55d144cc04c8933 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 2 Oct 2026 08:27:11 +0200 Subject: [PATCH 5/5] Expect the label Ruby 3.3 and older give the error event's logger call ErrorEvent logs the error in a rescue clause, which Ruby 3.3 and older label "rescue in call", as for 0.2.8. The test now accepts that label too. Co-Authored-By: Claude Opus 5.5 --- spec/logtail-rack/error_event_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/logtail-rack/error_event_spec.rb b/spec/logtail-rack/error_event_spec.rb index 82cb7e5..70cbb92 100644 --- a/spec/logtail-rack/error_event_spec.rb +++ b/spec/logtail-rack/error_event_spec.rb @@ -23,6 +23,7 @@ runtime = JSON.parse(io.string)["context"]["runtime"] expect(runtime["file"]).to end_with("lib/logtail-rack/error_event.rb") - expect(runtime["frame_label"]).to match(/\A(Logtail::Integrations::Rack::ErrorEvent#)?call\z/) + # Ruby 3.3 and older label code in a rescue clause "rescue in call" + expect(runtime["frame_label"]).to match(/\A(rescue in )?(Logtail::Integrations::Rack::ErrorEvent#)?call\z/) end end