Skip to content

Commit 3e52677

Browse files
committed
WIP - add e2e specs for rails structured logging
1 parent 334b4d8 commit 3e52677

File tree

3 files changed

+347
-4
lines changed

3 files changed

+347
-4
lines changed

spec/apps/rails-mini/Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ gem "rake"
66
gem "puma"
77
gem 'railties', '~> 8.0'
88
gem 'actionpack', '~> 8.0'
9+
gem 'activerecord', '~> 8.0'
10+
gem 'activejob', '~> 8.0'
11+
gem 'sqlite3', '>= 2.1'
912
gem 'sentry-ruby', path: Pathname(__dir__).join("../../..").realpath
1013
gem 'sentry-rails', path: Pathname(__dir__).join("../../..").realpath

spec/apps/rails-mini/app.rb

Lines changed: 219 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
Bundler.require
66

77
ENV["RAILS_ENV"] = "development"
8+
ENV["DATABASE_URL"] = "sqlite3::memory:"
89

9-
require "action_controller"
10+
require "action_controller/railtie"
11+
require "active_record/railtie"
12+
require "active_job/railtie"
1013

1114
class RailsMiniApp < Rails::Application
1215
config.hosts = nil
@@ -17,6 +20,12 @@ class RailsMiniApp < Rails::Application
1720
config.api_only = true
1821
config.force_ssl = false
1922

23+
config.active_record.logger = Logger.new($stdout)
24+
config.active_record.migration_error = :page_load
25+
26+
config.active_job.queue_adapter = :inline
27+
config.active_job.logger = Logger.new($stdout)
28+
2029
initializer :configure_sentry do
2130
Sentry.init do |config|
2231
config.dsn = ENV["SENTRY_DSN"]
@@ -30,12 +39,71 @@ class RailsMiniApp < Rails::Application
3039
config.release = "sentry-ruby-rails-mini-#{Time.now.utc}"
3140

3241
config.transport.transport_class = Sentry::DebugTransport
33-
config.sdk_debug_transport_log_file = "/workspace/sentry/log/sentry_debug_events.log"
3442
config.background_worker_threads = 0
43+
44+
config.enable_logs = true
45+
config.structured_logger_class = Sentry::DebugStructuredLogger
46+
config.sdk_debug_structured_logger_log_file = "/workspace/sentry/log/sentry_debug_logs.log"
47+
config.rails.structured_logging.enabled = true
48+
config.rails.structured_logging.attach_to = [:active_record, :action_controller, :active_job]
3549
end
3650
end
3751
end
3852

53+
class Post < ActiveRecord::Base
54+
end
55+
56+
class User < ActiveRecord::Base
57+
end
58+
59+
class ApplicationJob < ActiveJob::Base
60+
retry_on ActiveRecord::Deadlocked
61+
62+
discard_on ActiveJob::DeserializationError
63+
end
64+
65+
class SampleJob < ApplicationJob
66+
queue_as :default
67+
68+
def perform(message = "Hello from ActiveJob!")
69+
Rails.logger.info("SampleJob executed with message: #{message}")
70+
71+
Post.count
72+
User.count
73+
74+
message
75+
end
76+
end
77+
78+
class DatabaseJob < ApplicationJob
79+
queue_as :default
80+
81+
def perform(post_title = "Test Post")
82+
Rails.logger.info("DatabaseJob creating post: #{post_title}")
83+
84+
post = Post.create!(title: post_title, content: "Content for #{post_title}")
85+
found_post = Post.find(post.id)
86+
87+
Rails.logger.info("DatabaseJob found post: #{found_post.title}")
88+
89+
found_post
90+
end
91+
end
92+
93+
class FailingJob < ApplicationJob
94+
queue_as :default
95+
96+
def perform(should_fail = true)
97+
Rails.logger.info("FailingJob started")
98+
99+
if should_fail
100+
raise StandardError, "Intentional job failure for testing"
101+
end
102+
103+
"Job completed successfully"
104+
end
105+
end
106+
39107
class ErrorController < ActionController::Base
40108
before_action :set_cors_headers
41109

@@ -61,7 +129,8 @@ def health
61129
status: "ok",
62130
timestamp: Time.now.utc.iso8601,
63131
sentry_initialized: Sentry.initialized?,
64-
log_file_writable: check_log_file_writable
132+
log_file_writable: check_log_file_writable,
133+
structured_log_file_writable: check_structured_log_file_writable
65134
}
66135
end
67136

@@ -80,6 +149,123 @@ def check_log_file_writable
80149
false
81150
end
82151

152+
def check_structured_log_file_writable
153+
log_file_path = "/workspace/sentry/log/sentry_debug_logs.log"
154+
File.writable?(File.dirname(log_file_path)) &&
155+
(!File.exist?(log_file_path) || File.writable?(log_file_path))
156+
rescue
157+
false
158+
end
159+
160+
def set_cors_headers
161+
response.headers['Access-Control-Allow-Origin'] = '*'
162+
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
163+
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, sentry-trace, baggage'
164+
end
165+
end
166+
167+
class PostsController < ActionController::Base
168+
before_action :set_cors_headers
169+
before_action :ensure_database_setup
170+
171+
def index
172+
posts = Post.all.to_a
173+
174+
Sentry.logger.info("Posts index accessed", posts_count: posts.length)
175+
176+
render json: {
177+
posts: posts.map { |p| { id: p.id, title: p.title, content: p.content } }
178+
}
179+
end
180+
181+
def create
182+
post = Post.create!(post_params)
183+
184+
Sentry.logger.info("Post created", post_id: post.id, title: post.title)
185+
186+
render json: { post: { id: post.id, title: post.title, content: post.content } }, status: :created
187+
rescue ActiveRecord::RecordInvalid => e
188+
render json: { error: e.message }, status: :unprocessable_entity
189+
end
190+
191+
def show
192+
post = Post.find(params[:id])
193+
render json: { post: { id: post.id, title: post.title, content: post.content } }
194+
rescue ActiveRecord::RecordNotFound
195+
render json: { error: "Post not found" }, status: :not_found
196+
end
197+
198+
private
199+
200+
def post_params
201+
params.require(:post).permit(:title, :content)
202+
end
203+
204+
def ensure_database_setup
205+
unless ActiveRecord::Base.connection.table_exists?('posts')
206+
setup_database
207+
end
208+
end
209+
210+
def set_cors_headers
211+
response.headers['Access-Control-Allow-Origin'] = '*'
212+
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
213+
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, sentry-trace, baggage'
214+
end
215+
end
216+
217+
class JobsController < ActionController::Base
218+
before_action :set_cors_headers
219+
before_action :ensure_database_setup
220+
221+
def sample_job
222+
job = SampleJob.perform_later("Hello from Rails mini app!")
223+
224+
Sentry.logger.info("SampleJob enqueued", job_id: job.job_id)
225+
226+
render json: {
227+
message: "SampleJob enqueued successfully",
228+
job_id: job.job_id,
229+
job_class: job.class.name
230+
}
231+
end
232+
233+
def database_job
234+
title = params[:title] || "Test Post from Job"
235+
job = DatabaseJob.perform_later(title)
236+
237+
Sentry.logger.info("DatabaseJob enqueued", job_id: job.job_id, post_title: title)
238+
239+
render json: {
240+
message: "DatabaseJob enqueued successfully",
241+
job_id: job.job_id,
242+
job_class: job.class.name,
243+
post_title: title
244+
}
245+
end
246+
247+
def failing_job
248+
should_fail = params[:should_fail] != "false"
249+
job = FailingJob.perform_later(should_fail)
250+
251+
Sentry.logger.info("FailingJob enqueued", job_id: job.job_id, should_fail: should_fail)
252+
253+
render json: {
254+
message: "FailingJob enqueued successfully",
255+
job_id: job.job_id,
256+
job_class: job.class.name,
257+
should_fail: should_fail
258+
}
259+
end
260+
261+
private
262+
263+
def ensure_database_setup
264+
unless ActiveRecord::Base.connection.table_exists?('posts')
265+
setup_database
266+
end
267+
end
268+
83269
def set_cors_headers
84270
response.headers['Access-Control-Allow-Origin'] = '*'
85271
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
@@ -89,12 +275,41 @@ def set_cors_headers
89275

90276
RailsMiniApp.initialize!
91277

278+
def setup_database
279+
ActiveRecord::Schema.define do
280+
create_table :posts, force: true do |t|
281+
t.string :title, null: false
282+
t.text :content
283+
t.timestamps
284+
end
285+
286+
create_table :users, force: true do |t|
287+
t.string :name, null: false
288+
t.string :email
289+
t.timestamps
290+
end
291+
end
292+
293+
Post.create!(title: "Welcome Post", content: "Welcome to the Rails mini app!")
294+
Post.create!(title: "Sample Post", content: "This is a sample post for testing.")
295+
User.create!(name: "Test User", email: "[email protected]")
296+
end
297+
298+
setup_database
299+
92300
RailsMiniApp.routes.draw do
93301
get '/health', to: 'events#health'
94302
get '/error', to: 'error#error'
95303
get '/trace_headers', to: 'events#trace_headers'
96304

97-
# Add CORS headers for cross-origin requests from JS app
305+
get '/posts', to: 'posts#index'
306+
post '/posts', to: 'posts#create'
307+
get '/posts/:id', to: 'posts#show'
308+
309+
post '/jobs/sample', to: 'jobs#sample_job'
310+
post '/jobs/database', to: 'jobs#database_job'
311+
post '/jobs/failing', to: 'jobs#failing_job'
312+
98313
match '*path', to: proc { |env|
99314
[200, {
100315
'Access-Control-Allow-Origin' => '*',

0 commit comments

Comments
 (0)