55Bundler . require
66
77ENV [ "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
1114class 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,9 +39,67 @@ 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 . rails . structured_logging . enabled = true
47+ config . rails . structured_logging . attach_to = [ :active_record , :action_controller , :active_job ]
48+ end
49+ end
50+ end
51+
52+ class Post < ActiveRecord ::Base
53+ end
54+
55+ class User < ActiveRecord ::Base
56+ end
57+
58+ class ApplicationJob < ActiveJob ::Base
59+ retry_on ActiveRecord ::Deadlocked
60+
61+ discard_on ActiveJob ::DeserializationError
62+ end
63+
64+ class SampleJob < ApplicationJob
65+ queue_as :default
66+
67+ def perform ( message = "Hello from ActiveJob!" )
68+ Rails . logger . info ( "SampleJob executed with message: #{ message } " )
69+
70+ Post . count
71+ User . count
72+
73+ message
74+ end
75+ end
76+
77+ class DatabaseJob < ApplicationJob
78+ queue_as :default
79+
80+ def perform ( post_title = "Test Post" )
81+ Rails . logger . info ( "DatabaseJob creating post: #{ post_title } " )
82+
83+ post = Post . create! ( title : post_title , content : "Content for #{ post_title } " )
84+ found_post = Post . find ( post . id )
85+
86+ Rails . logger . info ( "DatabaseJob found post: #{ found_post . title } " )
87+
88+ found_post
89+ end
90+ end
91+
92+ class FailingJob < ApplicationJob
93+ queue_as :default
94+
95+ def perform ( should_fail = true )
96+ Rails . logger . info ( "FailingJob started" )
97+
98+ if should_fail
99+ raise StandardError , "Intentional job failure for testing"
35100 end
101+
102+ "Job completed successfully"
36103 end
37104end
38105
@@ -61,7 +128,8 @@ def health
61128 status : "ok" ,
62129 timestamp : Time . now . utc . iso8601 ,
63130 sentry_initialized : Sentry . initialized? ,
64- log_file_writable : check_log_file_writable
131+ log_file_writable : check_log_file_writable ,
132+ structured_log_file_writable : check_structured_log_file_writable
65133 }
66134 end
67135
@@ -70,6 +138,24 @@ def trace_headers
70138 render json : { headers : headers }
71139 end
72140
141+ def logged_events
142+ if Sentry . logger . is_a? ( Sentry ::DebugStructuredLogger )
143+ events = Sentry . logger . logged_events
144+ render json : { events : events , count : events . length }
145+ else
146+ render json : { events : [ ] , count : 0 }
147+ end
148+ end
149+
150+ def clear_logged_events
151+ if Sentry . logger . is_a? ( Sentry ::DebugStructuredLogger )
152+ Sentry . logger . clear
153+ render json : { status : "cleared" }
154+ else
155+ render json : { status : "no_debug_logger" }
156+ end
157+ end
158+
73159 private
74160
75161 def check_log_file_writable
@@ -80,6 +166,127 @@ def check_log_file_writable
80166 false
81167 end
82168
169+ def check_structured_log_file_writable
170+ if Sentry . logger . is_a? ( Sentry ::DebugStructuredLogger )
171+ log_file_path = Sentry . logger . log_file
172+ File . writable? ( File . dirname ( log_file_path ) ) &&
173+ ( !File . exist? ( log_file_path ) || File . writable? ( log_file_path ) )
174+ else
175+ false
176+ end
177+ rescue
178+ false
179+ end
180+
181+ def set_cors_headers
182+ response . headers [ 'Access-Control-Allow-Origin' ] = '*'
183+ response . headers [ 'Access-Control-Allow-Methods' ] = 'GET, POST, PUT, DELETE, OPTIONS'
184+ response . headers [ 'Access-Control-Allow-Headers' ] = 'Content-Type, Authorization, sentry-trace, baggage'
185+ end
186+ end
187+
188+ class PostsController < ActionController ::Base
189+ before_action :set_cors_headers
190+ before_action :ensure_database_setup
191+
192+ def index
193+ posts = Post . all . to_a
194+
195+ Sentry . logger . info ( "Posts index accessed" , posts_count : posts . length )
196+
197+ render json : {
198+ posts : posts . map { |p | { id : p . id , title : p . title , content : p . content } }
199+ }
200+ end
201+
202+ def create
203+ post = Post . create! ( post_params )
204+
205+ Sentry . logger . info ( "Post created" , post_id : post . id , title : post . title )
206+
207+ render json : { post : { id : post . id , title : post . title , content : post . content } } , status : :created
208+ rescue ActiveRecord ::RecordInvalid => e
209+ render json : { error : e . message } , status : :unprocessable_entity
210+ end
211+
212+ def show
213+ post = Post . find ( params [ :id ] )
214+ render json : { post : { id : post . id , title : post . title , content : post . content } }
215+ rescue ActiveRecord ::RecordNotFound
216+ render json : { error : "Post not found" } , status : :not_found
217+ end
218+
219+ private
220+
221+ def post_params
222+ params . require ( :post ) . permit ( :title , :content )
223+ end
224+
225+ def ensure_database_setup
226+ unless ActiveRecord ::Base . connection . table_exists? ( 'posts' )
227+ setup_database
228+ end
229+ end
230+
231+ def set_cors_headers
232+ response . headers [ 'Access-Control-Allow-Origin' ] = '*'
233+ response . headers [ 'Access-Control-Allow-Methods' ] = 'GET, POST, PUT, DELETE, OPTIONS'
234+ response . headers [ 'Access-Control-Allow-Headers' ] = 'Content-Type, Authorization, sentry-trace, baggage'
235+ end
236+ end
237+
238+ class JobsController < ActionController ::Base
239+ before_action :set_cors_headers
240+ before_action :ensure_database_setup
241+
242+ def sample_job
243+ job = SampleJob . perform_later ( "Hello from Rails mini app!" )
244+
245+ Sentry . logger . info ( "SampleJob enqueued" , job_id : job . job_id )
246+
247+ render json : {
248+ message : "SampleJob enqueued successfully" ,
249+ job_id : job . job_id ,
250+ job_class : job . class . name
251+ }
252+ end
253+
254+ def database_job
255+ title = params [ :title ] || "Test Post from Job"
256+ job = DatabaseJob . perform_later ( title )
257+
258+ Sentry . logger . info ( "DatabaseJob enqueued" , job_id : job . job_id , post_title : title )
259+
260+ render json : {
261+ message : "DatabaseJob enqueued successfully" ,
262+ job_id : job . job_id ,
263+ job_class : job . class . name ,
264+ post_title : title
265+ }
266+ end
267+
268+ def failing_job
269+ should_fail = params [ :should_fail ] != "false"
270+ job = FailingJob . perform_later ( should_fail )
271+
272+ Sentry . logger . info ( "FailingJob enqueued" , job_id : job . job_id , should_fail : should_fail )
273+
274+ render json : {
275+ message : "FailingJob enqueued successfully" ,
276+ job_id : job . job_id ,
277+ job_class : job . class . name ,
278+ should_fail : should_fail
279+ }
280+ end
281+
282+ private
283+
284+ def ensure_database_setup
285+ unless ActiveRecord ::Base . connection . table_exists? ( 'posts' )
286+ setup_database
287+ end
288+ end
289+
83290 def set_cors_headers
84291 response . headers [ 'Access-Control-Allow-Origin' ] = '*'
85292 response . headers [ 'Access-Control-Allow-Methods' ] = 'GET, POST, PUT, DELETE, OPTIONS'
@@ -89,12 +296,43 @@ def set_cors_headers
89296
90297RailsMiniApp . initialize!
91298
299+ def setup_database
300+ ActiveRecord ::Schema . define do
301+ create_table :posts , force : true do |t |
302+ t . string :title , null : false
303+ t . text :content
304+ t . timestamps
305+ end
306+
307+ create_table :users , force : true do |t |
308+ t . string :name , null : false
309+ t . string :email
310+ t . timestamps
311+ end
312+ end
313+
314+ Post . create! ( title : "Welcome Post" , content : "Welcome to the Rails mini app!" )
315+ Post . create! ( title : "Sample Post" , content : "This is a sample post for testing." )
316+ User . create! ( name : "Test User" , email : "test@example.com" )
317+ end
318+
319+ setup_database
320+
92321RailsMiniApp . routes . draw do
93322 get '/health' , to : 'events#health'
94323 get '/error' , to : 'error#error'
95324 get '/trace_headers' , to : 'events#trace_headers'
325+ get '/logged_events' , to : 'events#logged_events'
326+ post '/clear_logged_events' , to : 'events#clear_logged_events'
327+
328+ get '/posts' , to : 'posts#index'
329+ post '/posts' , to : 'posts#create'
330+ get '/posts/:id' , to : 'posts#show'
331+
332+ post '/jobs/sample' , to : 'jobs#sample_job'
333+ post '/jobs/database' , to : 'jobs#database_job'
334+ post '/jobs/failing' , to : 'jobs#failing_job'
96335
97- # Add CORS headers for cross-origin requests from JS app
98336 match '*path' , to : proc { |env |
99337 [ 200 , {
100338 'Access-Control-Allow-Origin' => '*' ,
0 commit comments