{"id":17558142,"url":"https://github.com/fractaledmind/rails-error-reporter-demo","last_synced_at":"2025-03-29T09:42:06.650Z","repository":{"id":221708699,"uuid":"755035556","full_name":"fractaledmind/rails-error-reporter-demo","owner":"fractaledmind","description":null,"archived":false,"fork":false,"pushed_at":"2024-02-09T15:07:20.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-04T00:41:51.014Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fractaledmind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-09T09:55:31.000Z","updated_at":"2024-02-09T09:55:53.000Z","dependencies_parsed_at":"2024-02-09T16:41:02.277Z","dependency_job_id":null,"html_url":"https://github.com/fractaledmind/rails-error-reporter-demo","commit_stats":null,"previous_names":["fractaledmind/rails-error-reporter-demo"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fractaledmind%2Frails-error-reporter-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fractaledmind%2Frails-error-reporter-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fractaledmind%2Frails-error-reporter-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fractaledmind%2Frails-error-reporter-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fractaledmind","download_url":"https://codeload.github.com/fractaledmind/rails-error-reporter-demo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246168097,"owners_count":20734389,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-21T09:43:22.033Z","updated_at":"2025-03-29T09:42:06.631Z","avatar_url":"https://github.com/fractaledmind.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# README\n\nThis is a sample Rails application to demonstrate the issue with the [error reporter]().\n\nAfter the [initial `rails new` command](https://github.com/fractaledmind/rails-error-reporter-demo/commit/7b891493552adc25ef30fa9a3d5f1943dfb91a7e), the following steps were taken:\n\n1. [Add a basic error reporter](https://github.com/fractaledmind/rails-error-reporter-demo/commit/5c2881f46069b06710f096ba011b9e341e1ff4ce)\n2. [Add a basic route that throws an error if query param set](https://github.com/fractaledmind/rails-error-reporter-demo/commit/d10454ee14b49297b1562bda01e851c238e7f668)\n3. [Allow non-SSL connections in production for local testing](https://github.com/fractaledmind/rails-error-reporter-demo/commit/3941949722eefb3d8242e2a05b3edb8ac392b105)\n\nYou can run the application in production mode via\n\n```bash\nRAILS_ENV=production rails server\n```\n\nIf you visit `http://localhost:3000` you will see a simple \"Hello, World!\" message. If you visit `http://localhost:3000?error=true` you will see an error message.\n\nIn production mode, you will note that the error reporter is not called:\n\n```bash\nI, [2024-02-09T16:03:36.566702 #2067]  INFO -- : [206187db-f3e4-4edb-a473-d277dda95fb8] Started GET \"/?error=true\" for 127.0.0.1 at 2024-02-09 16:03:36 +0100\nI, [2024-02-09T16:03:36.569307 #2067]  INFO -- : [206187db-f3e4-4edb-a473-d277dda95fb8] Processing by ApplicationController#root as HTML\nI, [2024-02-09T16:03:36.569856 #2067]  INFO -- : [206187db-f3e4-4edb-a473-d277dda95fb8]   Parameters: {\"error\"=\u003e\"true\"}\nI, [2024-02-09T16:03:36.572014 #2067]  INFO -- : [206187db-f3e4-4edb-a473-d277dda95fb8] Completed 500 Internal Server Error in 2ms (ActiveRecord: 0.0ms | Allocations: 150)\nE, [2024-02-09T16:03:36.573985 #2067] ERROR -- : [206187db-f3e4-4edb-a473-d277dda95fb8]\n[206187db-f3e4-4edb-a473-d277dda95fb8] StandardError (This is a test exception):\n[206187db-f3e4-4edb-a473-d277dda95fb8]\n[206187db-f3e4-4edb-a473-d277dda95fb8] app/controllers/application_controller.rb:3:in `root'\n```\n\nContrast that with running the application in development mode via:\n\n```bash\nRAILS_ENV=development rails server\n```\n\nYou will see the error reporter is called:\n\n```bash\nStarted GET \"/?error=true\" for ::1 at 2024-02-09 16:05:33 +0100\n   (0.2ms)  CREATE TABLE \"schema_migrations\" (\"version\" varchar NOT NULL PRIMARY KEY)\n   (0.1ms)  CREATE TABLE \"ar_internal_metadata\" (\"key\" varchar NOT NULL PRIMARY KEY, \"value\" varchar, \"created_at\" datetime(6) NOT NULL, \"updated_at\" datetime(6) NOT NULL)\n  ActiveRecord::SchemaMigration Load (0.1ms)  SELECT \"schema_migrations\".\"version\" FROM \"schema_migrations\" ORDER BY \"schema_migrations\".\"version\" ASC\nProcessing by ApplicationController#root as HTML\n  Parameters: {\"error\"=\u003e\"true\"}\nCompleted 500 Internal Server Error in 1ms (ActiveRecord: 0.0ms | Allocations: 1374)\n\n\n****************************************************************************************************\nERROR REPORTED\n\nStandardError (This is a test exception):\n\napp/controllers/application_controller.rb:3:in `root'\n```\n\n## In-Depth Analysis\n\nI have dug in more, and I have a clearer picture of what is going on. The top-level point is that **it is mostly a happy coincidence that errors are reported in development**. But, let me explain.\n\nErrors are reported via the `ActionDispatch::Executor` middleware:\nhttps://github.com/rails/rails/blob/3b8222ccd0f38a95109db2e84ee228318d322877/actionpack/lib/action_dispatch/middleware/executor.rb#L11-L21\n\nYou will notice that only _raised_ errors are reported, as we are inside of a `rescue` block. The issue is that, by default, errors are not _raised_, they are _rendered_. This is what happens in the `ActionDispatch::ShowExceptions` middleware:\nhttps://github.com/rails/rails/blob/3b8222ccd0f38a95109db2e84ee228318d322877/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L30-L41\n\nInside of this middleware, inside of a `rescue` block, we see a condition—the error is either _rendered_ or _raised_. The determinant is the result of `ActionDispatch::ExceptionWrapper#show?`:\nhttps://github.com/rails/rails/blob/3b8222ccd0f38a95109db2e84ee228318d322877/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb#L177-L191\n\nYou can inspect the `config` variable, but you will find (as I did) that by default, for both `development` and `production` is `:all`. Thus, the `else` condition is hit and `true` is returned. This is the first major detail: **in both `development` and `production` environments, errors are _rendered_ by the `ShowExceptions` middleware**.\n\nSo, the question becomes, how are errors reported in `development`? The short answer is because `development` includes the `ActionDispatch::Reloader` middleware and `production` does not. But, for those looking to understand what is occurring with more detail, let me elaborate.\n\nIn a standard, default Rails application, you will find the following middleware stack in `development`:\n```irb\nirb(main):001\u003e Rails.application.middleware\n=\u003e\n#\u003cActionDispatch::MiddlewareStack:0x0000000105ce77e0\n @middlewares=\n  [ActionDispatch::HostAuthorization,\n   Rack::Sendfile,\n   ActionDispatch::Static,\n   ActionDispatch::Executor,\n   ActionDispatch::ServerTiming,\n   ActiveSupport::Cache::Strategy::LocalCache::Middleware,\n   Rack::Runtime,\n   Rack::MethodOverride,\n   ActionDispatch::RequestId,\n   ActionDispatch::RemoteIp,\n   Rails::Rack::Logger,\n   ActionDispatch::ShowExceptions,\n   WebConsole::Middleware,\n   ActionDispatch::DebugExceptions,\n   ActionDispatch::ActionableExceptions,\n   ActionDispatch::Reloader,\n   ActionDispatch::Callbacks,\n   ActiveRecord::Migration::CheckPending,\n   ActionDispatch::Cookies,\n   ActionDispatch::Session::CookieStore,\n   ActionDispatch::Flash,\n   ActionDispatch::ContentSecurityPolicy::Middleware,\n   ActionDispatch::PermissionsPolicy::Middleware,\n   Rack::Head,\n   Rack::ConditionalGet,\n   Rack::ETag,\n   Rack::TempfileReaper]\u003e\n```\n\nIn comparison, you will see this middleware stack in `production`:\n```irb\nirb(main):001\u003e Rails.application.middleware\n=\u003e\n#\u003cActionDispatch::MiddlewareStack:0x000000010943fc60\n @middlewares=\n  [Rack::Sendfile,\n   ActionDispatch::Static,\n   ActionDispatch::Executor,\n   Rack::Runtime,\n   Rack::MethodOverride,\n   ActionDispatch::RequestId,\n   ActionDispatch::RemoteIp,\n   Rails::Rack::Logger,\n   ActionDispatch::ShowExceptions,\n   ActionDispatch::DebugExceptions,\n   ActionDispatch::Callbacks,\n   ActionDispatch::Cookies,\n   ActionDispatch::Session::CookieStore,\n   ActionDispatch::Flash,\n   ActionDispatch::ContentSecurityPolicy::Middleware,\n   ActionDispatch::PermissionsPolicy::Middleware,\n   Rack::Head,\n   Rack::ConditionalGet,\n   Rack::ETag,\n   Rack::TempfileReaper]\u003e\n```\n\nDiffing these two arrays (`dev.map(\u0026:inspect) - prod.map(\u0026:inspect)`), we see that `development` includes these additional middlewares:\n```irb\n[\"ActionDispatch::HostAuthorization\",\n \"ActionDispatch::ServerTiming\",\n \"ActiveSupport::Cache::Strategy::LocalCache::Middleware\",\n \"WebConsole::Middleware\",\n \"ActionDispatch::ActionableExceptions\",\n \"ActionDispatch::Reloader\",\n \"ActiveRecord::Migration::CheckPending\"]\n```\n\nOne of these middlewares allows errors to be reported. When adding additional logging to the middlewares, it became clear that errors were reported in `development` because **2 instances of `ActionDispatch::Executor` are in the middleware stack**. The inner instance sees the raised exception before the `ShowExceptions` middleware swallows it to render an error HTTP response. This second, inner instance of the `Executor` is, in fact, the `ActionDispatch::Reloader` middleware:\nhttps://github.com/rails/rails/blob/3b8222ccd0f38a95109db2e84ee228318d322877/actionpack/lib/action_dispatch/middleware/reloader.rb#L3-L14\n\nIt inherits from the `Executor` and does nothing else. I confess that I don't presently understand how a second instance of the `Executor` middleware \"wraps the request with callbacks provided by `ActiveSupport::Reloader`\", but that is immaterial to our investigation. The essential details is that:\n\n\u003e [!IMPORTANT]\n\u003e Errors that occur within the HTTP request-\u003eresponse lifecycle are only reported in `development` _if_ `config.enable_reloading` is set to `true`. If set to `false`, you will observe in that the error reporter is **not** called.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffractaledmind%2Frails-error-reporter-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffractaledmind%2Frails-error-reporter-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffractaledmind%2Frails-error-reporter-demo/lists"}