After moving to Ruby 4.0, why can require "logger" or "ostruct" raise LoadError under Bundler when it worked on 3.3, and what fixes it?
answer
- default gem vs bundled gem
- Bundler loads only locked gems
- 3.4 moved csv and bigdecimal
- 4.0 moved logger, ostruct, benchmark
- the warning comes one release early
basics
~20 sRuby 4.0 turned logger, ostruct, benchmark and others from default gems into bundled gems. Bundled gems ship with Ruby, but under Bundler they load only if the Gemfile or a gemspec lists them; adding them fixes the LoadError.
solid answer
~50 sRuby ships two kinds of gem. **Default gems** (json, psych, openssl, prism) cannot be uninstalled and load under Bundler even when the Gemfile omits them. **Bundled gems** are installed with Ruby but are ordinary gems: under Bundler only gems in the lockfile are loadable, so they must be declared. Ruby moves libraries from default to bundled over time: 3.4 moved csv, bigdecimal, base64, observer and others, and **4.0 moved logger, ostruct, benchmark, pstore, irb, reline and rdoc**. So a 4.0 app whose Gemfile never listed `logger` hits `LoadError`, after a message saying it "is not part of the default gems since Ruby 4.0.0" and to add it to the Gemfile or gemspec. The fix is a Gemfile entry, or asking the gem that requires it to declare the dependency. The release before each move prints a warning, so upgrading through it surfaces the list early.
code
bash · 4 lines$ bundle exec ruby -e 'require "logger"'
-e:1: warning: logger used to be loaded from the standard library, but is not part of the default gems since Ruby 4.0.0.
You can add logger to your Gemfile or gemspec to fix this error.
... cannot load such file -- logger (LoadError)go deeper
Recall that some standard libraries, such as logger and ostruct in 4.0, now need a Gemfile entry when you use Bundler.
Explain default versus bundled gems: both ship with Ruby, but only default gems load under Bundler without being declared.
Plan for the moves: read the warnings on the release before, check which dependency does the require, fix upstream or pin in the Gemfile, and boot every process type in CI.
Treat the shrinking default set as a recurring upgrade cost and keep dependency declarations complete, so each yearly release is a small change rather than a hunt.
## Two kinds of gems that ship with Ruby A Ruby installation comes with more than the core classes. Much of the standard library is packaged as gems, in two groups that behave differently: | | Default gems | Bundled gems | |---|---|---| | Installed with Ruby | yes | yes | | Can be uninstalled | no | yes | | Loadable under Bundler without a Gemfile entry | yes | **no** | | Examples in 4.0 | json, psych, openssl, prism, fileutils, uri | minitest, rake, csv, bigdecimal, logger, ostruct | **Default gems** behave like part of Ruby: `require "json"` always works, and you may still pin a newer version in a Gemfile. **Bundled gems** are pre-installed copies of ordinary gems. Outside Bundler, RubyGems finds and activates them like any installed gem. Under Bundler, the load path is restricted to the gems in `Gemfile.lock`, so a bundled gem that is not listed simply is not there. ## The default-to-bundled pipeline Ruby shrinks its default set a few libraries at a time, and each move follows the same steps: 1. **Warning release.** The release before the move warns when you `require` the library under Bundler without declaring it: "... was loaded from the standard library, but will no longer be part of the default gems starting from Ruby X". 3.3 did this for csv, bigdecimal, base64 and others; 4.0.7 already does it for `tsort`, which moves in 4.1. 2. **Move release.** The library becomes a bundled gem, and the message changes to "... is not part of the default gems since Ruby X", followed by `LoadError`. 3. **Fix hint.** Under Bundler the message adds "You can add X to your Gemfile or gemspec to fix this error", and when the `require` comes from inside another gem it names that gem and asks you to contact its author. The moves that matter for a 3.x to 4.0 upgrade: - **3.4:** abbrev, base64, bigdecimal, csv, drb, getoptlong, mutex_m, nkf, observer, resolv-replace, rinda, syslog. - **4.0:** ostruct, pstore, benchmark, logger, rdoc, win32ole, irb, reline, readline, fiddle. These messages are printed through plain `Kernel#warn` without a category, so they appear with default settings; they do not need `-W:deprecated`. ## Fixing it properly - **Your own code requires it:** add the gem to the Gemfile (for an app) or as a runtime dependency in the gemspec (for a library), then `bundle install`. - **A dependency requires it:** first upgrade that dependency, since most maintained gems now declare what they need; if it has not, add the gem to your Gemfile as a stopgap and report it upstream, as the message suggests. - **Nothing should require it any more:** sometimes the right fix is removal. `OpenStruct` use has been officially discouraged since 3.0, and a `Struct`, `Data.define` value object or plain Hash is usually the better replacement. Two related 4.0 changes look similar but are different. `Set` and `Pathname` went the **other** way, becoming core classes, so `require "set"` is now unnecessary and harmless. And **CGI was removed** from the default gems outright; only `cgi/escape` (`CGI.escapeHTML` and friends) remains. ## Why Ruby keeps moving libraries A bundled gem is an ordinary gem that happens to be pre-installed. That brings three properties a default gem lacks: it can be **upgraded or pinned** independently of Ruby like any dependency, it can be **uninstalled** by people who do not need it, and it makes the dependency **visible** in the Gemfile and lockfile instead of implicit. The price is paid once per library, by every app that used it without declaring it. ## Finding who requires it - Read the message: under Bundler, when the `require` comes from inside another installed gem, RubyGems names that gem and asks you to contact its author. - Search your own code and `bin/` scripts for the `require` line; rake tasks and one-off scripts are the usual hiding places. - Check the lockfile: if another gem already depends on the library, it is locked and loads; if not, it must be declared somewhere. ## Catching it before production The safest time to learn this list is before the upgrade. Running the suite on each intermediate release, or at least on the release just before the target, prints the warnings while everything still works. After the move, `bundle exec` with a full test run (including boot of every process type: web, workers, console, rake tasks) catches the rest, because a library required only by a rarely used rake task fails only when that task runs.
- Why does require "ostruct" still work in a plain ruby script on 4.0?Outside Bundler, RubyGems can activate any installed gem, and bundled gems are installed with Ruby. The restriction comes from Bundler, which limits the load path to the gems in the lockfile. If someone runs `gem uninstall ostruct`, the plain script fails too, which is exactly what "can be uninstalled" means for bundled gems.
- How can you find these libraries before upgrading?Run the suite and boot every process type under Bundler on the release before the move. It prints "will no longer be part of the default gems starting from Ruby X" for each undeclared library, while everything still loads. On 4.0.7 that already happens for `tsort`, ahead of 4.1.
saying these in an interview costs you the question
- Bundled gems are part of Ruby, so the Gemfile never needs them
- Default gems must also be listed in the Gemfile to load under Bundler
- Ruby 4.0 removed logger and ostruct from Ruby entirely
- The bundled-gem message appears only with -W:deprecated enabled
- Adding require "set" is needed on 4.0 because Set left the default gems