warn, warnings and log levels

warn writes to stderr

puts is the program's output and warn is the program's complaint. They go to different streams, which is why a warning does not corrupt a report a script is printing:

ruby -e 'warn "careful"; puts "ok"'

careful
ok

Both lines appear in a terminal because both streams point at it. Split them and the difference is clear:

ruby -e 'warn "careful"; puts "ok"' 2>/dev/null

ok

warn adds a newline and returns nil. $stderr.puts does the same thing with the stream named, and STDERR is the constant version. A warning that a reader should see even when stdout is piped into a file belongs on stderr.

ruby -w

The interpreter has its own warnings, and -w turns the full set on. An assigned variable that is never read is the smallest example:

x = 1

ruby -w w.rb

w.rb:1: warning: assigned but unused variable - x

The warning names the file and the line, which is what makes a warning worth reading. A run of a script with -w is a cheap lint pass: it finds a shadowed variable, a redefined method, an unused argument and a constant assigned twice.

ruby -W0 turns them off, and -W:deprecated sets one category. Warning.warn is the hook a library overrides to route interpreter warnings into its own log:

ruby -e 'Warning.warn("note\n"); puts "after"'

note
after

Logger and levels

A long-running program wants more than a line on stderr: a level, a timestamp and a way to silence the noise. The standard library has Logger for exactly that:

require "logger" log = Logger.new($stderr) log.level = Logger::WARN log.info("hidden") log.warn("careful")

ruby -e 'require "logger"; log = Logger.new($stderr); log.level = Logger::WARN; log.info("hidden"); log.warn("careful")'

W, [2026-11-05T00:12:31.785495 #462] WARN -- : careful

The debug line never appears because the level is set to WARN. The timestamp and the process id in the line change on every run. The order is DEBUG, INFO, WARN, ERROR, FATAL; set the level from the environment (ENV.fetch("LOG_LEVEL", "INFO")) so the same build is quiet in production and loud on a laptop. Lesson 40, later in the course, covers the configuration side, and lesson 147 goes deeper into formatters and rotation.

Warning or exception

An exception means the operation cannot continue: the caller must do something. A warning means the operation continues and the reader should know: a deprecated option, a value that was clamped, a fallback that was used. The test is whether the program has a correct answer to give.

What to watch for

A warning that fires on every request is noise, and noise is ignored. Log it once, at the point where the configuration is read, or raise the level so it does not print in production.

puts in a library is a bug: it writes to the caller's stdout. A gem writes to its logger, and the application decides where that goes.

Next: Debugging with the debug gem

Article author: Arthur Isaev